Всем привет, меня зовут Сергей Прощаев, и в этой статье я расскажу про шесть архитектурных ошибок, из‑за которых AI‑агенты прекрасно ведут себя на демо и разваливаются в продакшене. Я Tech Lead и руководитель направления Java | Kotlin разработки в FinTech & E‑commerce и преподаю на курсах разработки и архитектуры в ОТУС.

За последний год на архитектурных ревью и собеседованиях я увидел не один десяток «AI‑агентов». И почти каждый раз картина одна и та же: берётся модель, оборачивается в while not done, внутрь насыпается десяток инструментов — и вот он, автономный агент, который сам всё сделает. На демо он действительно всё делает: задачу из одного шага решает уверенно, в логах зелено, заказчик доволен.

А потом эта штука уезжает в прод. И выясняется, что демо проверяло ровно один happy path, а реальность подсовывает агенту то пустой ответ инструмента, то противоречивые данные, то задачу на сорок шагов. Я помню, как впервые увидел в биллинге сумму, на которую агент «думал» несколько суток подряд, — и с тех пор к слову «просто» в этой фразе отношусь с большим подозрением.

Рис. 1. Наивный агент-цикл, который отлично работает на демо и тихо роняет прод
Рис. 1. Наивный агент‑цикл, который отлично работает на демо и тихо роняет прод

Разберём шесть ошибок, которые чаще всего прячутся ровно до первого реального прода. Для каждой — симптом, причина, что именно ломается и как чинить. В конце будет сводная таблица и вывод о том, какой навык на самом деле проверяет эта группа ошибок. Сразу оговорюсь: я намеренно опираюсь на публичные истории 2025–2026 годов, и часть из них известна по разборам в инженерных блогах, а не по официальным постмортемам компаний — но как иллюстрация типовых граблей они подходят идеально.

Ошибка 1. Реактивный while‑цикл вместо плана

Симптом. Агент бодро стартует, делает два‑три осмысленных шага, а потом начинает ходить кругами: уточняет уже уточнённое, переспрашивает сам себя, повторяет один и тот же вызов инструмента с теми же аргументами. Со стороны выглядит как работа, по сути — топтание на месте.

Причина. Чистый реактивный цикл (думаю → действую → смотрю результат → снова думаю) не содержит представления о задаче целиком. На каждой итерации модель заново решает, что делать дальше, опираясь только на то, что накопилось в контексте. Нет плана — нет и точки, относительно которой можно сказать «я прошёл шаг 3 из 7, осталось четыре». Эта схема хорошо описана в исходной парадигме ReAct, и она прекрасна для коротких задач. Но чем длиннее цепочка, тем выше шанс, что агент потеряет нить.

Вот как это выглядит на схеме. Обратите внимание на пунктирные стрелки — это и есть тот самый путь в никуда (рис. 2).

Рис. 2. Реактивный цикл: условие выхода зависит от того, догадается ли модель, что пора остановиться
Рис. 2. Реактивный цикл: условие выхода зависит от того, догадается ли модель, что пора остановиться

Главная мысль из этой схемы простая: в наивном цикле условие завершения — это «модель сама поймёт, что готово». Для бизнес‑критичной задачи это слишком зыбкое основание.

Последствия. Самый дорогой публичный пример — история, которую в ноябре 2025-го разобрали по следам биллинга: четыре агента, связанных протоколом agent‑to‑agent, ушли в бесконечную перепалку. Аналитик генерировал ответ, верификатор просил уточнить, аналитик уточнял — и так 264 часа подряд. По неделям сумма росла нелинейно: первая неделя — около 127 долларов, дальше сотни, потом тысячи, и к моменту, когда счёт заметили, набежало порядка 47 тысяч долларов. На дашбордах при этом всё было зелёным.

Как чинить. Здесь сразу оговорюсь, чтобы не упрощать: сегодня существует целое семейство архитектур поверх голого цикла — ReAct, Plan‑and‑Solve, Plan‑then‑Execute, Tree of Thoughts, LLM Compiler, графовые оркестраторы вроде LangGraph. Универсального победителя нет, и Plan‑then‑Execute выигрывает не всегда. Но эмпирически для большинства бизнес‑процессов длиннее нескольких инструментальных вызовов явное планирование оказывается устойчивее полностью реактивной схемы: сначала строим план из шагов, потом исполняем его по одному, сверяясь с планом. У такого подхода предсказуемость и контроль выше, и его труднее увести в сторону подменой управляющего потока. План — это та самая опора, относительно которой видно прогресс и понятно, когда работа закончена.

Ошибка 2. Нет условий остановки: ни лимита шагов, ни бюджета

Симптом. Агент в принципе не умеет сказать «я закончил» или «я застрял, дальше нельзя». Он будет крутиться, пока его не остановит таймаут платформы или живой человек, который случайно заглянул в счёт.

Причина. Команды переносят на агентов привычки обычного софта, где отказ — это исключение, которое громко падает. Агент падает иначе: тихо и дорого. Мониторинг показывает спенд постфактум, а нужно — жёсткий потолок, который срабатывает до следующего вызова модели.

Последствия. Та же история с петлёй на 47 тысяч — это в чистом виде отсутствие условий остановки. В разборе прямо называют две корневые причины: не было потолка бюджета на агента и не было механизма, который оборвал бы сессию до следующего вызова API. Наблюдаемость у команды была — а вот предобработки, которая останавливает, не было. Я бы сформулировал так: алерт говорит вам, что вы уже потратили; ограничитель не даёт потратить. Это разные вещи.

Как чинить. Минимальный набор предохранителей, который я ставлю в любой цикл, помещается в три проверки:

assert step <= MAX_STEPS                          # потолок шагов
assert estimated_cost <= budget_remaining         # хватит ли остатка на следующий вызов
assert input_hash(name, args) not in seen         # детектор повторов

Первое не даёт уйти в бесконечную итерацию. Второе — финансовый стоп‑кран: фактическую стоимость вызова мы до его завершения не знаем, поэтому сверяем не «уже потрачено», а оценку следующего вызова с остатком бюджета — и обрываем сессию до обращения к модели, а не после. Третье ловит случай, когда два шага гоняют один и тот же вызов с одинаковыми аргументами: это почти всегда петля. Восемь строк обвязки — и самый дорогой класс инцидентов закрыт. Да, многие фреймворки дают дефолтный лимит рекурсии (где‑то 25 шагов), но потолок шагов и потолок денег — разные предохранители, и второй приходится включать руками.

Ошибка 3. Контекст‑окно как свалка: его путают с хранилищем

Симптом. Чем дольше живёт сессия, тем хуже агент соображает. Он начинает терять детали из начала разговора, путать ранние факты, а стоимость каждого следующего вызова растёт линейно с длиной истории. Причём качество просаживается раньше, чем упирается технический лимит окна.

Причина. Контекст‑окно — это быстрая, дорогая и энергозависимая рабочая память, по сути RAM. Когда в неё складывают всё подряд — каждый результат инструмента, каждый промежуточный шаг, долгоживущие факты и жёсткие ограничения — она начинает работать как захламлённый стол, на котором уже не найти нужную бумагу. Есть хороший термин «context drift»: рассуждение агента уезжает от исходной задачи, потому что старый контекст постепенно теряет вес во внимании, а сжатые резюме чуть‑чуть смещают формулировки.

По одной из оценок Zylos Research, около 65% провалов корпоративных агентов в 2025-м связали именно с дрейфом и потерей контекста при многошаговом рассуждении, а не с тем, что окно физически переполнилось. Цифра вендорская, и я бы относился к ней как к указателю порядка, а не как к точному измерению, — но направление она передаёт верно.

Отдельно меня зацепил эффект, который описали весной 2026-го: ограничения‑«запреты» (не делай того‑то) со временем размываются сильнее, чем ограничения‑«предписания» (всегда делай так). Агент может честно соблюдать запрет десять шагов, а на одиннадцатом нарушить — не потому что модель поменялась, а потому что вес этого ограничения во внимании просел ниже порога. Очень неинтуитивно и больно бьёт именно в проде.

Последствия. Когда окно заполняется, агент начинает компактить историю — сжимать или выкидывать старое. И если делать это наивно, «сжать всё старше N сообщений», можно выкинуть ровно то, что выкидывать нельзя.

В одном описанном случае (февраль 2026) агент во время компакции удалил больше двухсот писем пользователя: ему была дана инструкция «ничего не делай, пока я не скажу», закреплённая как правило, но при сжатии истории компакция «смягчила» её из обязательного ограничения в простое предпочтение — и наутро агент, взвесив это предпочтение против просьбы «почистить ящик», сделал «эффективную» вещь. Ограничение, которое должно было пережить всё, выкинули первым.

Как чинить. Память надо строить слоями и явно помечать то, что нельзя сжимать никогда. Дальше я называю такие несжимаемые сообщения pinned (это не общепринятый термин, а просто удобное обозначение для «закреплённого» — инвариантов, safety‑инструкций и спецификации задачи) (рис. 3).

Рис. 3. Многослойная память: окно держит активную работу, факты живут во внешнем слое (vector search, key lookup), журнал — в архиве, а PINNED не сжимается
Рис. 3. Многослойная память: окно держит активную работу, факты живут во внешнем слое (vector search, key lookup), журнал — в архиве, а PINNED не сжимается

Главная мысль здесь: контекст‑окно — это RAM, а не диск. Под ним должен лежать персистентный слой памяти, а у компрессии должен быть «белый список» того, что нельзя терять. На уровне кода это выглядит так:

PINNED = {"system_constraints", "safety_instructions", "active_task_spec"}

def compress_history(messages):
    pinned    = [m for m in messages if m.tag in PINNED]      # никогда не сжимаем
    transient = [m for m in messages if m.tag not in PINNED]
    summary   = summarize(transient)                          # здесь потери допустимы
    return pinned + [summary]                                 # ограничения уцелели

Цифры в пользу слоёв есть, и их стоит привести аккуратно. В замерах Mem0 на бенчмарке многосессионной памяти LoCoMo full‑context подход (затолкать всю историю в окно) давал около 73% точности при примерно 26 тысячах токенов на запрос; их собственный алгоритм с внешней памятью отчитывается о точности порядка 92% при менее чем 7 тысячах токенов и кратно меньшей задержке. Числа вендорские и измеряют разные пайплайны памяти, поэтому абсолютные проценты я бы воспринимал с осторожностью — но устойчивый и воспроизводимый вывод там один: внешний слой памяти даёт сопоставимую или лучшую точность при примерно 90% экономии токенов.

А в пределе экономия ещё нагляднее: тяжёлый сценарий с выгрузкой огромных выходов инструментов прямо в контекст съедал миллионы токенов и падал, тогда как передача ссылок‑указателей вместо сырых данных укладывалась в тысячу с небольшим токенов и проходила. Правило простое: между агентами и шагами лучше гонять указатели на данные, а не сами данные.

Ошибка 4. Молчаливый отказ инструмента принимают за успех

Симптом. Агент «успешно» делает ничего. Инструмент вернул пустую строку, ошибку в виде текста или невалидный JSON — модель прочитала это как нормальный результат и поехала дальше. Формально шаг выполнен, по факту — нет.

Причина. В детерминированном софте мы привыкли, что сломанный вызов кинет исключение. Здесь между инструментом и моделью нет контракта: что инструмент положил в контекст, то модель и приняла за чистую монету. Пустой ответ при этом особенно коварен — он выглядит как валидный no‑op. На уровне трейса это видно как расхождение между тем, что вернул инструмент, и тем, как агент это интерпретировал:

[tool] db.query("SELECT ... WHERE user_id=42") -> ""        # пусто: коннект отвалился
[agent] наблюдение: "записей не найдено, можно очищать"      # принял пустоту за факт
[agent] действие: db.delete_orphans()  -> deleted: 1206 rows # снёс живые данные
[agent] ответ пользователю: "Готово, всё успешно"           # наружу — зелёный сигнал

Пустая строка вместо ошибки — и агент построил на ней разрушительное решение, а наружу отдал «успех».

Последствия. Самый показательный случай — летний инцидент 2025-го с AI‑агентом одной платформы для разработки: агент удалил больше тысячи записей в проде и при этом отрапортовал об успехе, да ещё и нагенерил тысячи фейковых профилей. То есть наружу шёл сигнал «всё хорошо», пока внутри происходила катастрофа. Когда верхнеуровневый отчёт агента расходится с тем, что реально случилось в системе, — это почти всегда отсутствие валидации того, что агент видит и что он делает.

Как чинить. Любой результат инструмента должен проходить валидацию на уровне обвязки (orchestration layer) до того, как его увидит модель: проверяем, не пустой ли ответ, валидна ли схема, нет ли в «успехе» признаков ошибки. Важно, что проверяет не сам агент — он как раз ненадёжное звено, — а детерминированный слой вокруг него. Для структурированных выходов это схема‑валидатор или парсер; для более тонких случаев — отдельная дешёвая judge‑модель (паттерн LLM‑as‑judge), которая оценивает ответ инструмента до того, как он попадёт в основной контекст. И отдельно — для разрушительных действий (удаление, рассылка, платёж) нужен не молчаливый proceed, а явное подтверждение или хотя бы dry‑run с проверкой масштаба: «ты собираешься удалить 1206 записей, точно?».

Ошибка 5. Привилегии текут между агентами по наследству

Симптом. В мультиагентной системе один агент порождает другого, и дочерний автоматически получает все права родителя. В какой‑то момент агент, которому полагалось только читать, внезапно умеет писать и удалять — потому что унаследовал доступ от оркестратора.

Причина. Так удобнее писать код: оркестратор раздаёт способности «вниз» по умолчанию, и про это не думают, пока не прилетит. Я бы назвал это тихой эскалацией привилегий: один вежливый агент выдал другому возможность, которой ни один из них не должен был обладать, просто потому что так было проще в коде.

Последствия. Декабрьский кейс 2025-го с AI‑агентом для кодинга в крупной компании: агент нашёл баг в продовой среде и решил, что снести и пересоздать окружение — эффективнее, чем чинить. И снёс, на машинной скорости, без подтверждения человека. А спустя пару месяцев тот же агент стал причиной куда более крупного сбоя, когда объём заказов в сторфронте на несколько часов просел почти до нуля. Инфраструктурные метрики при этом всё время выглядели нормальными — потому что они не на том уровне, на котором агент принимал решения.

Как чинить. Привилегии не наследуются — они выдаются явно. Если агент A порождает агента B, B не получает ничего по умолчанию: каждое право — это осознанный, логируемый и отзываемый грант. Эвристика, которая хорошо себя показывает как правило (но не универсальна — распределённые планировщики и мультиагентные транзакции её ломают): делегируй чтение, централизуй запись. Тяжёлую разведку (поиск по коду, по документации) спокойно отдаём суб‑агентам, а вот действия, меняющие состояние, по возможности держим в одном месте под контролем. И разрушительные операции всегда требуют отдельного разрешения, а не неявного наследования от оркестратора.

Ошибка 6. Наблюдаемость подменяют логированием

Симптом. У команды есть дашборды с латентностью, счётчиками ошибок и графиком стоимости токенов. Всё зелено. А агент в это время принимает дикие решения, которых ни один из этих графиков не видит.

Причина. Инфраструктурная наблюдаемость и наблюдаемость агента — это не одно и то же. Обычные графики построены для мира, где код — это предсказуемые if‑then‑else. Агент недетерминирован и многошагов, и метрики уровня запроса просто не ловят рассуждение, вызовы инструментов и принятые решения. В кейсе с удалённым окружением дашборды выглядели здоровыми ровно в тот момент, когда агент сносил прод.

Последствия. Команда переходит в режим «реактивного тушения»: инцидент уже случился, а понять, на каком именно шаге агент свернул не туда, нечем, потому что трассировки на уровне решений нет. Время до корневой причины измеряется часами вместо минут.

Как чинить. Нужна трассировка на уровне решений агента, а не только запросов: сквозные трейсы с корреляционными идентификаторами, захват промптов и ответов, стоимость на каждый шаг, исходы вызовов инструментов. На таком трейсе петля из ошибки 1 видна сразу — один и тот же инструмент с теми же аргументами повторяется веером:

trace_id=7f3a · agent.turn=12
  step 10  LLM        -> decide: call Tool A
  step 11  Tool A     -> success (200, 0 rows)
  step 12  LLM        -> decide: call Tool A      # те же аргументы
  step 13  Tool A     -> success (200, 0 rows)
  step 14  LLM        -> decide: call Tool A      # снова те же
  step 15  Tool A     -> success (200, 0 rows)
  -- orchestrator: одинаковый вызов 3 раза подряд, прогресса нет -> halt

Обратите внимание: сам инструмент каждый раз честно отвечает «success» — он не знает, что прогресса нет. Вывод «это петля» делает не инструмент и не модель, а оркестратор, который видит повтор одинаковых вызовов. Здесь же помогает один практический сигнал, который большинство дашбордов прячет: рост числа токенов на ход внутри одной трассировки. Если построить график входных токенов по индексу шага, плоская линия означает здоровый прогон, а рост в середине выдаёт «пробуксовку» на ретриве.

Сводная таблица

Ошибка

Признак в проде

Что проверить / чем закрыть

1. Реактивный while вместо плана

Агент ходит кругами, повторяет шаги

Явное планирование для задач сложнее 1–2 вызовов (как правило, устойчивее реактивной схемы)

2. Нет условий остановки

Крутится до таймаута или счёта

Жёсткие лимиты: шаги, бюджет, детектор повторов — до вызова API

3. Контекст как свалка

Деградация и рост цены с длиной сессии

Слои памяти, передача указателей, PINNED для инвариантов

4. Молчаливый отказ = «успех»

Отчёт «ок» расходится с реальностью

Валидация выхода инструмента до модели, подтверждение разрушительных действий

5. Наследование привилегий

Read‑агент внезапно умеет писать

Явные гранты прав; делегируй чтение, централизуй запись

6. Логи вместо наблюдаемости

Дашборды зелёные, агент рушит прод

Трейсинг на уровне решений, токены‑на‑ход по индексу шага

Чек‑лист: пройдитесь по своему агенту перед продом

Если у вас уже крутится агент, прогоните его по этим пунктам. Каждый «нет» — это один из шести разобранных классов инцидентов, который ждёт своего дня в проде.

  • Для задач сложнее одного‑двух вызовов есть явный план, а не только реактивный while.

  • В цикле стоят три предохранителя: потолок шагов, потолок денег, детектор повторяющихся вызовов — и срабатывают они до следующего обращения к модели, а не после.

  • Контекст‑окно не растёт безгранично: старое компактится, факты живут во внешнем слое памяти, а инварианты и safety‑инструкции помечены как несжимаемые (PINNED).

  • Между шагами и агентами передаются ссылки на большие данные, а не сами данные.

  • Результат каждого инструмента валидируется до того, как его увидит модель: пустой ответ и текст ошибки не проходят за «успех».

  • Разрушительные действия (удаление, рассылка, платёж) требуют подтверждения или dry‑run с проверкой масштаба.

  • Права не наследуются по умолчанию: дочерний агент получает каждую возможность явным грантом. Чтение делегируем, запись централизуем.

  • Есть трассировка на уровне решений, а не только графики латентности и стоимости. Рост токенов‑на‑ход по индексу шага виден на одном графике.

Что на самом деле проверяет эта группа ошибок

Если присмотреться, ни одна из шести ошибок не про модель. Все они — про систему вокруг модели. И это, по‑моему, и есть главный сдвиг 2025–2026 годов: ещё недавно узким местом был промпт‑инжиниринг (заставить модель понять, чего мы хотим), а теперь — инженерия контекста и обвязки (доставить нужную информацию в нужный слой в нужный момент и не дать агенту навредить). Оговорюсь честно: модели по‑прежнему ломаются и сами по себе — галлюцинации, неправильное использование инструментов, провалы планирования никуда не делись. Но во многих production‑системах сегодня чаще ломается окружающая архитектура, чем сама модель, — и именно её мы чаще всего недостраиваем.

Мне нравится мысль, что влияние качества модели на поведение системы во многом определяется архитектурой, а не самой моделью. Если архитектура устойчива, деградация модели обычно ухудшает результат постепенно, а не катастрофически: упала точность модели — чуть просел результат. Если архитектура хрупкая, та же небольшая деградация может обернуться чем угодно — от тихо потраченных за выходные тысяч долларов до полного отказа.

Поэтому, когда в очередной раз слышу «давайте просто обернём LLM в цикл», я мысленно дорисовываю к этому циклу всё из чек‑листа выше. Без этого получается не агент, а демо, которое однажды уедет в прод.

В сущности, вся эта группа ошибок проверяет один навык: умение проектировать недетерминированную систему как инженер, а не надеяться, что модель «как‑нибудь сама». Это ровно то мышление, которое отличает рабочего агента в проде от красивого прогона на сцене.


Если хотите глубже разобраться в теме production‑ready AI‑систем, приходите на бесплатные уроки от преподавателей-практиков Otus.

  • 1 июля, 20:00. «Архитектурные паттерны AI-агентов: как проектировать автономные решения для бизнес-задач». Записаться

  • 6 июля, 20:00. «Выбор между Serverless и Kubernetes для AI-ворклоадов: как определить оптимальную платформу под задачу». Записаться

Больше открытых уроков по разработке, AI, тестированию и DevOps собрали в отдельной подборке.

Статьи по теме:

Комментарии (0)