Это первая заметка из серии о проектировании агентов для длительных задач, появившаяся после внимательного изучения BEACON (Чжэцзянский университет, arXiv:2605.06078).
Чтобы агент выдерживал длительную задачу, на наш взгляд, нужны две вещи:
- Он продолжает двигаться к цели. Это включает сжатие контекста (что можно безопасно забыть), проектирование контрольных точек (как понять, что шаг действительно завершён) и предотвращение застоя (когда агент застрял, что-то должно это заметить).
- Он умеет анализировать свою работу и улучшаться.
Эта статья посвящена предотвращению застоя, потому что именно он обошёлся нам дороже всего.
Симптом
Пользователи сообщали об этом, и мы видели то же самое внутри команды: некоторые очень длинные задачи на полпути перестают продвигаться.
В журналах агент занят : читает файлы, ищет, выполняет команды, ни минуты простоя. Но через полчаса ничего не сдвинулось.
Сначала мы объясняли это потерей контекста: сжатие удалило запись о том, что путь уже проверяли, и агент пошёл проверять его снова. Изучив вместе код и журналы, мы поняли, что это лишь половина истории.
У нас уже была защита — целых три уровня
| Уровень | Критерий | Пороговые значения |
|---|---|---|
| Точный повтор | Имя инструмента + канонизированные аргументы, побайтовое совпадение | LOOP_WARN=3 предупреждение / LOOP_HARD=5 принудительная остановка |
| Почти дубликат | Совпадает всё, кроме изменчивых полей идентификатора / временной метки | NEAR_DUP_LOOP_WARN=6 / NEAR_DUP_LOOP_HARD=12 |
| Совокупные признаки зацикливания | ≥2 сжатий и израсходовано ≥75% бюджета цикла инструментов | SPIN_CONVERGENCE_MIN_COMPACTIONS=2SPIN_CONVERGENCE_TOOL_LOOP_RATIO=0.75 |
Комментарий к третьему уровню дословно гласит: Составной сигнал «возможно зацикливание после потери контекста». Кто-то уже предвидел эту проблему. Значит, дело было не в отсутствии защиты: построенная нами защита не могла её обнаружить.
Слепая зона: все три детектора смотрят на входные данные
Каждый уровень опирается на сигнатуру tool name + canonicalized args. Она отвечает ровно на один вопрос: делаете ли вы одно и то же дважды?
Но способная модель, застряв, не повторяет вызовы. Она делает вот что:
прочитать файл A → grep X → перечитать A с другим диапазоном строк
→ выполнить немного другую команду → прочитать файл B → снова grep …У каждого вызова своя сигнатура. Все три уровня молчат. А на всём этом отрезке число проверяемых изменений состояния равно нулю: ни один файл фактически не переписан, ни одна команда не дала нового результата, вообще не произошло ничего необратимого.
Обнаружение цикла — не обнаружение застоя. Первое смотрит на входные данные. Второе должно смотреть на результаты.
Статья даёт именно такое определение по результатам
Награда внутри сегмента в BEACON:
r_t = R_ms · γ^(t_k − t) if the segment ends in a milestone
= 0 otherwiseСегмент, который не заканчивается достижением контрольной точки, получает ровно нольнезависимо от числа действий внутри него. Число действий вообще не входит в формулу. Это самая чёткая формализация принципа занятость ≠ продвижение из всех, что мы видели.
Базовый уровень ещё строже. Базой служит средняя по группе отдача на шаг, поэтому у сегмента, занявшего 8 шагов при среднем по группе 5, значение преимущества целиком сдвигается в отрицательную сторону, а штраф растёт с превышением. Топтание на месте не просто не вознаграждается — оно активно и пропорционально штрафуется.
На рисунке 8 статьи показана неудачная траектория, где последние два действия получают одинаковые −2,20. Этот хвост — отрезок после последней контрольной точки, который так и не достигает следующей, — математический образ топтания на месте. И встречается он часто: траектории, выполняющие хотя бы одну подцель, но проваливающие задачу, стабильно составляют 39–47% выборки.
Один абляционный эксперимент исключает триггеры по числу шагов
| Разбиение | Результат | Относительно базового уровня (72,8) |
|---|---|---|
| Случайное разбиение на 5 частей | 74,2 | +1,4 |
| Реальные контрольные точки | 91,4 | +17,2 |
Разбиение по произвольному числу шагов почти ничего не даёт. Разбиение по реальной структуре даёт много.
Теперь вернёмся к критерию нашего третьего уровня: израсходовано 75% бюджета цикла инструментов. Это триггер по произвольному числу шагов. Он спрашивает, сколько вы потратили, а не чего достигли. Правильная форма такая:
✗ if steps > N → intervene
✓ if steps > N AND zero verified milestones → interveneВторой вариант не сработает ложно на действительно долгой задаче, которая продвигается, потому что она достигает контрольных точек по пути. Первый сработает.
Есть и положительная обратная связь
Поставим константы рядом. Сжатие срабатывает при заполнении 82% окна контекста. Для срабатывания детектора совокупных признаков зацикливания нужны два сжатия плюс 75% бюджета цикла.
топтание на месте → контекст заполняется → срабатывает сжатие → долговременное состояние теряется в сводке
→ заново восстановить утраченное → ещё больше топтаться на местеДетектор делает вывод о зацикливании по повторным сжатиям, но именно сжатие и вызывает амнезию. Он обнаруживает вторичный симптом, и ему приходится ждать двух оборотов цикла.
Хуже того, его вмешательство — подтолкнуть модель снова опереться на сохранённое состояние. Если именно это состояние и было удалено при сжатии, опереться уже не на что. Это исправление на уровне промпта для проблемы на уровне состояния.
Что мы добавляем: обнаружение застоя по результатам
Четвёртый уровень, построенный на двух счётчиках. Оба механически вычисляются из наблюдений за инструментами, которые приложение уже записывает; ни один не требует суждения модели.
Шаги после последней проверенной контрольной точки. Простейшая метрика прогресса, используемая в составном критерии выше, а не сама по себе.
Новые изменения состояния с исключением дубликатов. Более полезный из двух счётчиков:
- Чтение файла, хеш содержимого которого совпадает с предыдущим чтением, не даёт новой информации: чтение того же файла в другом диапазоне строк не меняет хеш.
- Команда, у которой имя, код завершения и хеш вывода совпадают с предыдущим запуском, не даёт новой информации.
- Запись, у которой хеш после операции равен хешу до неё, означает, что фактически ничего не записано.
Этот счётчик нацелен именно на случай, который пропускает сравнение сигнатур: все действия разные, прирост информации нулевой. Критерий механический и не требует понимания смысла.
Далее воздействие должно быть постоянным, а не разовым напоминанием:
показать счётчик в контексте, чтобы модель его видела
→ принудительно пересмотреть план (признать, что этот путь зашёл в тупик)
→ спросить пользователя
→ прервать, сохранив уже достигнутые этапыПоследняя ступень важна. Если запуск предстоит остановить, он должен сохранить достигнутое — те самые 39–47% частичного прогресса, которые, по измерениям статьи, отбрасываются.
Чего статья нам не даёт
BEACON — метод обучения. Он формирует градиенты так, чтобы обученная политика меньше блуждала, но у него нет собственного механизма обнаружения или вмешательства во время выполнения. Он даёт определение прогресса, а не контроллер. Пороговые значения, ступени усиления мер и условия остановки предстоит спроектировать нам.
Его базовый уровень на шаг также требует средней по группе длины сегмента в качестве ориентира. В рабочей среде конкретная задача пользователя обычно выполняется ровно один раз, поэтому группы нет. Лучшая доступная замена — историческая статистика по похожим задачам, но она значительно шумнее и не даёт гарантии изоляции дисперсии из статьи.
Первый шаг — измерить, а не исправить
Прежде чем менять логику решений, мы хотим добавить измерения и ответить на вопрос, на который пока не можем: сколько застоев в рабочей среде связано с амнезией, а сколько — с отсутствием градиента?
- Чаще сопровождается тем, что прирост информации нулевой, хотя сигнатуры всех вызовов различаются → тип без градиента. Существующие три уровня структурно не могут его обнаружить; решение — обнаружение по результатам.
- Чаще сопровождается тем, что повторно читается содержимое, уже удалённое при сжатии → тип с амнезией. Исправлять нужно то, что сохраняет сжатие.
Эти два вывода требуют совершенно разных вложений. Сначала измерить дешевле, чем сначала проектировать, а добавление измерений почти бесплатно, потому что наблюдения уже есть.
Всё выше зависит от понятия, на которое эта заметка опиралась, не давая определения: проверенная контрольная точка. Об этом — следующая заметка. Почему объявление шага завершённым не доказывает его завершение, какие две половины уже есть в продукте, но так и не соединены, и один критерий, которого нет в статье: смотреть на необратимость, а не на важность.