Проектируем агентов, которые не уходят в бесконечный цикл
Самая частая форма провала продакшен-агента — бесконечные или псевдобесконечные циклы: агенты ретраят, ветвятся и жгут токены, не двигаясь вперёд. Какие архитектурные паттерны это предотвращают и дают агентов, которые доходят до конца даже на сложных задачах.
Типичная форма дорогостоящего продакшен-провала: 3 часа ночи во вторник, агент клиентской поддержки уходит в бесконечный цикл. Он вызывает один и тот же инструмент, получает одну и ту же ошибку, ретраит с чуть изменёнными параметрами, получает ту же ошибку, повторяет. Сотни раз в минуту. К утру у команды счёт с четырьмя-пятью знаками — в зависимости от модели и частоты вызовов.
Это не редкость. Циклящиеся агенты — один из самых частых и самых дорогих продакшен-провалов. Они коварны тем, что часто «выглядят как работают»: агент совершает действия, вызовы успешно проходят (или предсказуемо проваливаются). Только когда вы заглянете в трейс, становится виден повторяющийся паттерн.
Чтобы строить агентов, которые не циклятся вечно, нужны осознанные архитектурные решения. Большинство агентских провалов предсказуемы; паттерны их предотвращения хорошо известны. Команды, выкатывающие надёжных агентов, — это те, которые внедряют эти паттерны строго.
Эта статья — о том, почему агенты циклятся, какие архитектурные паттерны это предотвращают и какие операционные предохранители ловят циклы, которые всё-таки проскочили.
Почему агенты циклятся
Несколько механизмов, стоящих за агентскими циклами.
1. Путаница насчёт прогресса
У агента нет ясного ощущения «продвинулся ли я?». Он что-то пробует, наблюдает результат, решает попробовать что-то ещё. Без явного отслеживания прогресса «попробовать что-то ещё» может означать «то же самое, но чуть иначе».
2. Отсутствие критериев завершения
В промпте у агента написано «помоги пользователю», но не написано «остановись, когда X истинно». Без чёткого условия остановки агент продолжает «помогать»: искать ещё одну информацию, пробовать ещё один инструмент, дополнительно полировать.
3. Патология ретраев
Когда что-то падает, агенты естественным образом ретраят. Без бюджета ретраев один и тот же сбой можно ретраить бесконечно. Агент воспринимает это как «у меня пока не получилось», а не «я уже пробовал это 10 раз».
4. Амнезия по состоянию
В рабочей памяти агента — только недавние ходы. Если цикл прокрутил 20 попыток, агент может видеть в контексте только последние 5, теряя паттерн, очевидный наблюдателю снаружи.
5. Несогласованность инструмента
Инструмент возвращает запутанные или противоречивые результаты. Агент пробует снова. Инструмент по-прежнему возвращает запутанное. Агент рассуждает «может, я неправильно понял в прошлый раз» и пробует иначе. Инструмент по-прежнему возвращает запутанное. Цикл.
6. Избыточный оптимизм
Обучение делает агента настойчивым — он продолжает пробовать, тогда как лучшей стратегией было бы остановиться и попросить помощи. Особенно плохо в долгих задачах, где маленькие непонимания накапливаются.
7. Дрейф цели
Агент постепенно теряет из виду, что вообще пытался сделать. Уходит в подзадачи, потом в под-подзадачи, исследует околотемные области и не возвращается к главной цели.
Разные агенты валятся по-разному. Защиты — пересекаются.
Паттерн 1. Жёсткие бюджеты шагов
Самая простая и важная защита: максимальное число шагов. У агента есть, скажем, 20 вызовов инструментов. После 20 он обязан выдать финальный ответ (или эскалировать).
Реализация:
def agent_loop(query, max_steps=20):
messages = [{"role": "user", "content": query}]
for step in range(max_steps):
response = call_llm(messages, tools=available_tools)
if response.is_final_answer:
return response.content
result = execute_tool(response.tool_call)
messages.append(response)
messages.append({"role": "tool", "content": result})
# Hit budget — force final answer
return force_final_answer(messages)Бюджет надо калибровать под задачу. Простые задачи: 5–10 шагов. Сложные мультиисточные: 20–30. Открытое исследование: 50+. Но всегда ограничено.
Когда бюджет исчерпан, агент выдаёт лучший возможный ответ с тем, что знает. Или эскалирует на человека.
Один этот паттерн предотвращает большинство катастрофических циклов. Внедряйте его всегда.
Варианты
Бюджет по токенам. Вместо счётчика шагов (или в дополнение к нему) ограничивайте суммарные токены. Защищает от агентов, которые делают меньше шагов, но каждый шаг — это reasoning-трейс на 50K токенов.
Бюджет по стоимости. Переводит бюджеты шагов/токенов в евро. Перерасход перестаёт быть абстракцией.
Бюджет по времени. Лимит по wall-clock. Полезно для пользовательских флоу («ответить за 30 секунд»).
У большинства продакшен-агентов так или иначе есть все четыре бюджета. Срабатывание любого из них завершает запуск.
Паттерн 2. Отслеживание прогресса
Один бюджет не сообщает агенту, что тот застрял, — он просто рано или поздно его остановит. Отслеживание прогресса помогает агенту распознать цикл и вырваться из него.
Простая реализация: агент ведёт явный «лог прогресса». На каждом шаге он формулирует, какую новую информацию получил или что изменилось.
Step 1: Searched for customer "Smith". Found 12 matches.
Step 2: Filtered to active accounts. 4 remain.
Step 3: Checked recent activity. Customer 234 had a recent ticket about pricing.
Step 4: Pulled the ticket details. The complaint was about a recent price change.
Step 5: Drafted response. Ready to send.Каждый шаг добавляет новую информацию. Если на шестом шаге лог прогресса ничего не приобретает — тот же запрос, те же результаты, тот же вывод — агент крутится на месте.
В промпт можно добавить:
Before deciding the next action, summarize what you've learned in the last few steps. If you haven't gained new information in the last 3 steps, stop and either:
- Produce your best answer with current information.
- Escalate the issue: explain what you've tried and what's missing.Это делает «отсутствие прогресса» видимым для самого агента — и он может на это среагировать.
Паттерн 3. Детектирование повторов
Иногда агенты повторяют ровно тот же вызов инструмента. Это легко детектировать программно.
def detect_repeat(history):
recent_calls = [c for c in history[-5:] if c.is_tool_call]
if len(recent_calls) < 3:
return False
call_signatures = [(c.tool, json.dumps(c.args, sort_keys=True)) for c in recent_calls]
return len(set(call_signatures)) < len(call_signatures) / 2Когда повтор обнаружен — вмешайтесь:
- Подсуньте сообщение: «Ты недавно вызывал этот инструмент с такими параметрами. Результаты не изменились. Попробуй другой подход или заверши работу».
- Или принудительно завершите.
Это автоматически ловит самые очевидные циклы.
Паттерн 4. Детектирование застревания
Помимо точных повторов, можно детектировать и более тонкие застрявшие состояния.
Распознавание паттернов. Отдельным LLM-вызовом оцените: «Глядя на последние 5 шагов, продвигается ли этот агент?». Если нет — выламывайтесь.
def is_stuck(history):
recent = format_history(history[-5:])
response = call_llm(
system="You are evaluating whether an agent is making progress.",
user=f"Recent agent steps:\n{recent}\n\nIs the agent making meaningful progress or stuck in a loop? Answer: progressing | stuck."
)
return response.content.strip() == "stuck"Запускайте такую проверку каждые несколько шагов. Если возвращает «stuck» — вмешивайтесь.
Разнообразие инструментов. Если агент 5+ шагов подряд вызывает только один инструмент, это подозрительно. Заставьте попробовать что-то ещё или остановиться.
Паттерны ошибок. Если один и тот же инструмент 3+ раз вернул одну и ту же ошибку, прекращайте им пользоваться. Из новых ретраев агент не выведет недостающий вход.
Паттерн 5. Точки рефлексии
В определённых точках работы агента принудительно заставьте его рефлексировать.
After every 5 steps, the agent must produce a reflection:
1. What was my original goal?
2. What have I learned so far?
3. What do I still need to know?
4. Am I making progress, or repeating?
5. Should I continue or stop?Рефлексия вынуждает агента отойти от мышления «что делать прямо сейчас» и оценить картину шире.
Это особенно эффективно в длинных задачах. Без принудительной рефлексии агенты дрейфуют; с ней — ловят дрейф сами.
Паттерн 6. Якорение цели
В длинных запусках исходная цель теряется. Контекстное окно агента заполняется промежуточными шагами; исходный вопрос становится небольшой частью большого контекста.
Лечится повторным якорением цели:
- Включайте исходную цель в начало каждого системного сообщения.
- Заставьте агента переформулировать цель каждые N шагов.
- Используйте отдельный «трекер цели», который подтверждает, что каждый шаг согласуется с целью.
Пример добавления в промпт:
Original goal: [verbatim user request]
Before each action, confirm:
- Is this action helping me toward the original goal?
- If yes, proceed.
- If no, return to the goal directly.Паттерн 7. Разграничение подзадач
Длинные агенты естественно разбиваются на подзадачи. Без структуры подзадачи могут рекурсивно порождать под-подзадачи, пока агент окончательно не потеряется.
Дайте структуру:
- Агент явно идентифицирует подзадачи.
- У каждой подзадачи свой бюджет.
- После завершения (или провала) подзадачи агент возвращается к главной.
- Подзадачи не могут плодить неограниченные под-подзадачи.
Это то, что пытаются формализовать фреймворки вроде LangGraph — конечный автомат, где каждый узел является чётким шагом с явными переходами.
Для сложных агентов такая структура необходима. Для простых — это избыточно.
Паттерн 8. Аварийные выходы
Когда агент застрял, ему нужны явные способы остановиться.
Эскалация. «Я не могу завершить эту задачу. Вот что я пробовал и чего не хватает». Агент останавливается и поднимает проблему наверх.
Частичное выполнение. «Я сделал части A и B. C заблокирована X». Агент не обязан полностью преуспеть — он может выдать полезный частичный результат.
Уточнение. «Мне нужна дополнительная информация от пользователя: …». Агент паузится и спрашивает.
Это должны быть полноценные опции для агента, а не последние средства. Промпт должен упоминать их и поощрять использование при застревании.
Полезное добавление в промпт:
If you encounter any of these situations, stop trying and respond appropriately:
- A tool consistently returns the same error.
- You've tried 3 different approaches without progress.
- You need information only the user can provide.
- The task is more complex than your tools support.
In these cases:
- For tool errors: explain the issue, suggest the user contacts support.
- For lack of progress: report what you've tried and ask for guidance.
- For missing information: ask the user a specific question.
- For complexity: escalate to human assistance with a summary.Паттерн 9. Действия с учётом уверенности
Агент должен понимать, когда он уверен, а когда нет. Действия на низкой уверенности — это так и зарождаются циклы.
Паттерн: каждое значимое действие требует явной оценки уверенности.
Before calling delete_record, state your confidence on a 1-5 scale that this is the right action. If <4, do not call. Instead, ask for human confirmation.Это особенно хорошо работает для деструктивных или дорогих действий. Агент должен закоммититься на высокую уверенность, прежде чем их совершить.
В сочетании с рефлексией это ловит ситуации, когда агент «пробует что-то», а не «выполняет план».
Паттерн 10. Защита на уровне инструментов
Помимо паттернов на уровне агента, защитниками могут быть и сами инструменты.
Rate limiting на сессию. Инструмент можно вызвать не более N раз за сессию. После N — возврат «rate limit». Заставляет агента делать что-то другое.
Идемпотентность. Повторяющиеся одинаковые вызовы возвращают закешированный результат, не выполняясь повторно. Защищает от циклов, бьющих по инструменту.
Кэп по стоимости. У дорогих инструментов (тяжёлые DB-запросы, сторонние API с usage-стоимостью) есть лимит на сессию.
Circuit breaker по ошибкам. Инструмент, упавший 3 раза в этой сессии, отключается. Агент больше не может его вызвать.
Это дополняет агентские паттерны. Агент может пытаться зациклиться, но инструмент этого не даст.
Паттерн 11. Внешний мониторинг
При всех in-agent-паттернах внешний монитор ловит то, что проскочило.
Процесс мониторинга следит за всеми запущенными агентами. Проверяет:
- Число шагов на агента.
- Расход токенов на агента.
- Стоимость на агента.
- Время на агента.
- Паттерны вызовов инструментов.
Когда какой-то агент превысил пороги — убивайте его. Шлите алёрт.
Это последняя линия обороны. Даже если сам агент сломан, монитор остановит его, прежде чем тот навредит кошельку.
В реализации:
- Time-series-база собирает метрики агентов.
- Правила выдают приказы на kill («если агент работает > 5 минут, убить»).
- Небольшой сервис следит и принудительно завершает.
Для систем, где параллельно крутится много агентов, это обязательно.
Паттерн 12. Контрольные точки с человеком в петле
Для высокорисковых агентов встройте человеческие контрольные точки. Агент работает до точки, затем ждёт одобрения.
Типичные точки:
- Перед деструктивными действиями.
- После решения, которое агент не сможет откатить.
- На крупных вехах в длинной задаче.
- Когда уверенность падает.
Это не про недоверие — это про то, чтобы ловить ошибки, пока их дёшево исправить.
Практичный флоу: агент автономно делает подготовительную работу, выдаёт сводку и предлагаемые действия, человек одобряет, агент исполняет. Человек в петле для решений, а не в петле на каждый шаг.
Разобранный пример: долгоживущий research-агент
Чтобы было предметно — паттерны, применённые к реальному агенту.
Задача. Исследовать конкурента и подготовить бриф.
Оценочный объём работы. 10–30 веб-поисков, 20–50 чтений страниц, синтез в бриф на 1000 слов.
Применённые паттерны:
- Бюджет шагов: 60 шагов всего.
- Бюджет токенов: 300K токенов (контекст + действия). При превышении — суммируем текущие выводы и продолжаем.
- Бюджет по стоимости: €2 за запуск. При превышении — стоп и возврат частичного брифа.
- Бюджет по времени: 5 минут wall-clock.
- Отслеживание прогресса: каждый шаг агент обновляет «findings log» новой информацией. Если 3 шага без новых findings — аварийный выход.
- Детектирование повторов: если один и тот же поисковый запрос с похожими результатами выполнен дважды — принудительно сменить подход.
- Точки рефлексии: каждые 10 шагов агент рефлексирует над прогрессом и оставшейся работой.
- Якорение цели: исходное задание на бриф — в начале каждого системного сообщения.
- Аварийные выходы: «У меня достаточно информации» или «Я не могу найти достаточно информации» — оба корректно завершают агента.
- Внешний монитор: независимый наблюдатель убивает агентов, превысивших бюджеты.
Результат. Медианное время запуска — 3 минуты. Медианная стоимость — €0,40. Доля провалов (циклы или таймауты) — меньше 1%. Брифы получаются 700–1200 слов, фактически обоснованы, полезны как стартовая точка.
Без этих паттернов: периодические 30-минутные запуски, периодические €20+ счета, периодические «кирпичные» сессии. Паттерны драматически режут хвост.
Детектирование в продакшене
Даже с паттернами иногда что-то проскакивает. Детектируйте.
Алёрты на долгоживущих агентов. Любой агент > 2× медианной длительности — алёрт.
Алёрты на всплески стоимости. Стоимость на агента или агрегированная — выше порога.
Алёрты на повторяющиеся паттерны. Паттерны вызовов инструментов, которые намекают на циклы.
Ежедневный обзор длинных трейсов. Человек глазами проходится по топ-10 самых длинных трейсов за день. Ловит то, что evals пропускают.
Агрегированные метрики: частота циклов во времени. Ловит моменты, когда что-то меняется (апгрейд модели, изменение промпта) и циклы учащаются.
Полезный дашборд: распределение длительностей запусков агентов. Хвост рассказывает об инцидентности циклов.
Частые ошибки
Несколько паттернов, которые мы видим регулярно.
Ошибка 1. Нет бюджета шагов. «Добавим, если понадобится». Потом агент зацикливается в 3 часа ночи — и вы жалеете, что не добавили. Добавляйте с первого дня.
Ошибка 2. Слишком высокие бюджеты. «100 шагов — с запасом» — но цикл заполняет их. Ставьте бюджеты как медиана + 2–3×, а не как худший случай.
Ошибка 3. Нет внешнего монитора. Доверять агенту самому остановиться. Иногда он не остановится. Внешний монитор — must-have для продакшена.
Ошибка 4. Циклы ловят, но не разбирают. Цикл случился, монитор убил, команда поехала дальше. Через неделю — тот же цикл. Всегда делайте пост-мортем по пойманным циклам: что триггернуло, что изменилось, можно ли предотвратить класс отказов?
Ошибка 5. Агрессивная рефлексия на простых задачах. Принудительная рефлексия каждые 5 шагов на 5-шаговой задаче — оверхед без выгоды. Калибруйте под сложность задачи.
Ошибка 6. Цели теряются в длинных контекстах. Цель, упомянутая один раз на шаге 1, не доживает до шага 50. Якорите регулярно.
Ошибка 7. Доверие самооценкам прогресса от агента. Агенты скажут, что продвигаются, когда они не продвигаются. По возможности верифицируйте снаружи.
Ошибка 8. Разрешать агентам вызывать самих себя рекурсивно. «Декомпозируй задачу на под-агентов» способно дать экспоненциальный взрыв запусков. Если разрешаете — бюджетируйте строго.
Когда циклы допустимы
Не все циклы — это плохо. Некоторые задачи легитимно требуют много итераций:
- Итеративное доведение кода (написал, протестировал, починил, повторил).
- Многошаговое исследование с ветвлениями.
- Задачи оптимизации (попробовать варианты, оценить, доработать).
Здесь циклы — это работа, а не провал. Паттерны сдвигаются:
- Щедрые бюджеты шагов (50–200).
- Явная рамка «итерация», а не «цикл».
- Отслеживание улучшения качества — каждая итерация должна двигать метрику.
- Жёсткий стоп, когда улучшение упёрлось в плато.
Принцип: отличайте «запланированную итеративную работу» от «непреднамеренных циклов». Применяйте паттерны соответствующим образом.
Итог
Агенты, циклящиеся вечно, предсказуемы, часты и предотвратимы. Паттерны известны: бюджеты шагов, отслеживание прогресса, детектирование повторов, рефлексия, якорение цели, аварийные выходы, защита инструментов, внешние мониторы.
Это не опциональная полировка. Это разница между агентами, которые доезжают до продакшена, и агентами, которые выдают неожиданные четырёхзначные счета.
Для любого продакшен-агента — чек-лист:
- [ ] Максимальный бюджет шагов.
- [ ] Максимальный бюджет токенов.
- [ ] Максимальный бюджет по стоимости.
- [ ] Максимальный бюджет по времени.
- [ ] Детектирование повторных вызовов.
- [ ] Отслеживание прогресса.
- [ ] Периодическая рефлексия.
- [ ] Якорение цели.
- [ ] Несколько аварийных выходов.
- [ ] Внешний монитор с возможностью kill.
Каждый пункт несложен в реализации. Вместе они дают разницу между «оставлять этого агента запущенным опасно» и «этот агент надёжно работает в продакшене».
Встройте паттерны. Протестируйте их. Хвостовой риск, который вы убираете, многократно окупает работу.