Orkas Orkas
Главная Блог Архитектура
Архитектура

Обнаружение циклов не выявляет застой: как поймать агентов, которые топчутся на месте без повторов

В Orkas три механизма защиты от зацикливания. Ни один не замечает, когда способная модель застревает: все три ищут повторения, а застрявшая модель не повторяется. Разбираем эту слепую зону, определение прогресса по результатам из BEACON и то, что планируем измерить, прежде чем что-либо менять.

Это первая заметка из серии о проектировании агентов для длительных задач, появившаяся после внимательного изучения BEACON (Чжэцзянский университет, arXiv:2605.06078).

Чтобы агент выдерживал длительную задачу, на наш взгляд, нужны две вещи:

  • Он продолжает двигаться к цели. Это включает сжатие контекста (что можно безопасно забыть), проектирование контрольных точек (как понять, что шаг действительно завершён) и предотвращение застоя (когда агент застрял, что-то должно это заметить).
  • Он умеет анализировать свою работу и улучшаться.

Эта статья посвящена предотвращению застоя, потому что именно он обошёлся нам дороже всего.

Коротко Агента, который топчется на месте без повторений, тоже нужно обнаруживать Обнаружение застоя входит в Orkas: длительный запуск сообщит, что застрял, вместо того чтобы молча потратить остаток бюджета.
Скачать Orkas — бесплатно

Симптом

Пользователи сообщали об этом, и мы видели то же самое внутри команды: некоторые очень длинные задачи на полпути перестают продвигаться.

В журналах агент занят : читает файлы, ищет, выполняет команды, ни минуты простоя. Но через полчаса ничего не сдвинулось.

Сначала мы объясняли это потерей контекста: сжатие удалило запись о том, что путь уже проверяли, и агент пошёл проверять его снова. Изучив вместе код и журналы, мы поняли, что это лишь половина истории.

У нас уже была защита — целых три уровня

УровеньКритерийПороговые значения
Точный повторИмя инструмента + канонизированные аргументы, побайтовое совпадениеLOOP_WARN=3 предупреждение / LOOP_HARD=5 принудительная остановка
Почти дубликатСовпадает всё, кроме изменчивых полей идентификатора / временной меткиNEAR_DUP_LOOP_WARN=6 / NEAR_DUP_LOOP_HARD=12
Совокупные признаки зацикливания≥2 сжатий и израсходовано ≥75% бюджета цикла инструментовSPIN_CONVERGENCE_MIN_COMPACTIONS=2
SPIN_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 — метод обучения. Он формирует градиенты так, чтобы обученная политика меньше блуждала, но у него нет собственного механизма обнаружения или вмешательства во время выполнения. Он даёт определение прогресса, а не контроллер. Пороговые значения, ступени усиления мер и условия остановки предстоит спроектировать нам.

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

Первый шаг — измерить, а не исправить

Прежде чем менять логику решений, мы хотим добавить измерения и ответить на вопрос, на который пока не можем: сколько застоев в рабочей среде связано с амнезией, а сколько — с отсутствием градиента?

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

Эти два вывода требуют совершенно разных вложений. Сначала измерить дешевле, чем сначала проектировать, а добавление измерений почти бесплатно, потому что наблюдения уже есть.

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