Featured image of post Главная задача — научить агента вовремя остановиться

Главная задача — научить агента вовремя остановиться

Почему автономный агентский pipeline дрейфует и как удержать его дисциплину: JSON-контракт между фазами, независимый VERIFY, специализация моделей через router.

В этой статье


Где ломается автономия

Технический долг — задачи, на которые всегда нет времени. Идеальная цель для автоматизации: распределить роли (PM, системный аналитик, разработчик, QA) и запустить автономный цикл обработки тикетов.

На бумаге это работает. На практике автономия превращается в тихий ремонт.

Агент сталкивается с проблемой в окружении — и не отчитывается о блоке. Вместо этого начинает править соседний код, лезть в конфиги, “оптимизировать” то, о чём его не просили. На выходе “Done”, но половина окружения уехала со спецификации.

Я называю это скрытым дрейфом. Каждая отдельная правка локально выглядит как улучшение. Накапливаются они в “тихий ремонт”, который никто не запрашивал — и который очень трудно откатить, потому что Git diff давно перестал отражать одну задачу.

Пример провала

Сегодня pipeline закрыл задачу как DONE. Developer честно сделал работу, запустил check_command, получил exit=0, отчитался passed=true.

Независимая параллельная проверка дала exit=1.

Никто не соврал. check_command содержал паттерн test, который ведёт себя по-разному на linux и macOS. Артефакт сборки оказался несовместим со средой проверки. Агент видел “успех” со своей колокольни. С другой колокольни — провал.

Этот один случай задаёт всю архитектуру дальше. Если мы доверяем self-report агента, мы доверяем фрагменту окружения, в котором он находится. А окружение — переменная.

Решение 1: контракт между фазами как JSON

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

  • модель додумывает поля, которые не запрашивались
  • следующая фаза вынуждена парсить свободный текст
  • “я подумал и решил сделать ещё” — реальная фраза в логах

Перешли к JSON-схеме между каждой парой фаз. Constrained generation через tool_use заставляет модель заполнить именно те поля, которые ожидает следующая фаза, и не заполнять ничего сверх.

stateDiagram-v2 [*] --> PLAN PLAN --> SPEC: JSON plan SPEC --> DEV: JSON spec DEV --> QA: JSON diff + report QA --> VERIFY: JSON review VERIFY --> DONE: independent pass VERIFY --> BLOCKED: mismatch PLAN --> BLOCKED: cannot plan SPEC --> BLOCKED: ambiguity DEV --> BLOCKED: env issue QA --> BLOCKED: review fail BLOCKED --> [*]: handover to human DONE --> [*]

Каждый переход — структурный артефакт по схеме. Никакого “свободного творчества”. Хочешь добавить мысль — нет поля в схеме, мысль не передаётся. Свободный текст остаётся только в одном месте — в комментариях для человека, и они не влияют на следующий шаг.

graph LR A[Issue] -->|JSON: goal, constraints| B[PLAN] B -->|JSON: steps, owners| C[SPEC] C -->|JSON: contract, acceptance| D[DEV] D -->|JSON: diff, check_command, passed| E[QA] E -->|JSON: review verdict| F[VERIFY] F -->|JSON: status| G[DONE or BLOCKED]

Что важно: схема — это не “лучший формат” для людей, она для следующей фазы. Если на ревью вы хотите видеть что-то ещё — отдельный человеко-читаемый рендер из той же JSON. Не смешивайте каналы.

Решение 2: независимый VERIFY

Из примера с check_command я вытащил жёсткое правило: проверка должна жить отдельно от агента.

graph TB subgraph agentenv ["Agent environment"] A1[Developer agent] A2[Local config] A3[Cache, env vars] A4[Build artifact] A1 --- A2 A1 --- A3 A1 --- A4 end subgraph verifyenv ["Verify environment"] V1[Fresh clone] V2[Clean container] V3[Independent check_command] V1 --- V2 V2 --- V3 end A1 -->|reports passed=true| C{Compare} V3 -->|independent exit code| C C -->|match| OK[DONE] C -->|mismatch| BL[BLOCKED]

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

Справа — verify-среда. Свежий клон репозитория, чистый контейнер, никаких артефактов от агента, никаких “уже собранных” бинарников. Запускает тот же check_command из независимой точки. Если результаты расходятся — задача в BLOCKED.

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

Решение 3: специализация моделей через router

Один универсальный agent на все задачи — анти-паттерн. У каждой фазы своя нагрузка, и универсальная модель либо переплачивает, либо недоезжает.

graph LR IN[Phase request] --> R{Local router} R -->|structural JSON| M1[Planner] R -->|deep reasoning| M2[Reasoning specialist] R -->|long document| M3[Long-context] R -->|short patch| M4[Executor] R -->|simple reply| M5[Fast chat] M1 --> OUT[Structural artifact] M2 --> OUT M3 --> OUT M4 --> OUT M5 --> OUT

Пять ролей под пять типов нагрузки:

  • planner — структурные решения, последовательность и точность над JSON
  • reasoning specialist — тяжёлое обоснование, разбор архитектурных вопросов
  • long-context — суммаризация документов и длинных тикетов
  • executor — короткие патчи, рутинные операции
  • fast chat — диалоговые реплики, простые ответы

Router смотрит на тип фазы и сложность артефакта, направляет вызов в соответствующую модель. Локально, без привязки к одному провайдеру. Это и про стоимость, и про то, что разные модели сильны в разном — нет смысла делать структурный план моделью, заточенной под длинный диалог.

Принципы

  1. Дисциплина контракта важнее скорости генерации. Свободный текст между фазами — приглашение к дрейфу.
  2. Не доверяй self-report. Verify живёт вне периметра агента.
  3. Останавливайся явно. BLOCKED — это статус, а не fallback. Если что-то не сходится — задача стоит и ждёт человека. Не закрывается с “Done”.
  4. Специализация важнее универсальности. Pipeline из специализированных моделей дешевле и стабильнее, чем одна большая модель на всё.

Итог

Автономия агентов — это не свобода. Это контракт. Чем плотнее контракт и чем независимее проверка, тем меньше дрейфа и тихих закрытий.

Останавливаться — это умение. Pipeline, который умеет сказать “не знаю”, безопаснее pipeline’а, который всегда говорит “готово”.

Архитектурный вопрос: где в вашем процессе self-report агента сейчас считается за истину, и какой минимальный шаг добавит туда независимую проверку?

Создано при помощи Hugo
Тема Stack, дизайн Jimmy