Планирование и декомпозиция: когда агент должен остановиться и спросить
В главе «Промпт как спецификация» мы разбирали, как сформулировать задачу так, чтобы множество допустимых решений сузилось. Эта глава про следующий шаг: что делать, когда задача больше одного промпта. Когда её нельзя описать одним абзацем, потому что в ней пять файлов, две миграции и одно решение, которое вы ещё не приняли.
Начну с тезиса, который дальше буду защищать:
План агента — это гипотеза о работе, а не обязательство её выполнить. Ценность плана не в том, что агент ему последует, а в том, что вы можете отвергнуть плохой план за две минуты чтения вместо сорока минут ревью дифа.
Из этого тезиса следует всё остальное: как выглядит план, который вообще можно проверить; почему шаг должен заканчиваться прогоном, а не отчётом; и почему «остановись и спроси» — это не вежливость агента, а инженерное требование, которое вы прописываете в контракте и подкрепляете отсутствием прав.
Зачем нужен план, если агент и так что-то сделает
Агент действительно что-то сделает и без плана. Вопрос в том, во сколько вам обойдётся выяснение, что именно.
План даёт ровно три вещи, и полезно назвать их явно, потому что ему часто приписывают четвёртую, которой нет.
Первое: перенос ошибки в дешёвую фазу. Ошибка «не тот модуль» стоит в плане одну строку, а в дифе — весь диф. Это старая мысль, не имеющая отношения к ИИ: чем позже обнаружено неверное решение, тем дороже его отмена. Фред Брукс писал об этом в «Мифическом человеко-месяце» задолго до всякого машинного обучения. Агент не меняет здесь ничего, кроме скорости: он проходит фазу «написал код» за минуты, поэтому фаза «выяснили, что не то» наступает быстрее и больнее.
Второе: артефакт, который читается быстрее результата. План на двадцать строк читается за две минуты. Диф на четыреста строк — за сорок, и то с потерями (об этом подробно в главе про ревью кода от агента). Соотношение примерно на порядок в пользу плана — и это единственная причина, по которой планирование окупается.
Третье: граница ответственности. Согласованный план фиксирует, что именно вы разрешили. Всё, что агент сделал сверх плана, — это отклонение, которое надо объяснить, а не «инициатива». Для командной работы это критично; см. главу про агентов в команде.
А вот чего план не даёт: он не улучшает качество кода сам по себе. Распространённое убеждение «сначала попроси план, потом код — и код будет лучше» проверяется на вашей задаче, а не принимается на веру. Механизм, из-за которого это иногда работает, понятный: план попадает в контекст и влияет на последующую генерацию — то есть работает как расширенный промпт. Механизм, из-за которого это иногда не работает, такой же: план попадает в контекст, занимает место и начинает влиять даже там, где он неверен. Правдоподобный план, содержащий ошибочное допущение, хуже отсутствия плана, потому что дальше агент будет уверенно строить на этом допущении.
Когда план не нужен. Если задача обратима, локальна и проверяется одной командой — планирование это чистые накладные расходы. Переименовать поле, поправить регулярку, добавить лог. Требовать план на такую задачу — ритуал. Хуже того: если задача решается руками за пять минут, то и агент в ней не нужен, и глава про цену объясняет почему подробно.
Что агент называет планированием и почему это не планирование
Здесь нужна трезвость, иначе всё дальнейшее превратится в магию.
Когда классический планировщик (тот, что решает задачи STRIPS или PDDL) строит план, он выполняет поиск в пространстве состояний: у него есть модель мира, операторы с предусловиями и эффектами, и он гарантирует, что найденная последовательность применима. Когда языковая модель «строит план», она генерирует текст, который статистически похож на план. Это разные операции, и разница видна на замерах.
Группа Субарао Камбхампати систематически проверяла это на бенчмарке PlanBench: модели дают правдоподобно выглядящие планы, но доля планов, действительно исполнимых в модели мира, оказывалась низкой на задачах, где нельзя было угадать по формулировке. Их выводы стоит читать целиком, потому что они же предлагают конструктив: в работе LLMs Can’t Plan, But Can Help Planning in LLM-Modulo Frameworks авторы показывают схему, где модель генерирует кандидатов, а внешний верификатор отбраковывает неисполнимые. Практический перевод на наш язык: план от агента надо проверять внешним критерием, а не читать как обещание.
Честно назову и вторую сторону. Эти работы делались на формальных задачах планирования — блоки, логистика, — а не на «добавь идемпотентность в резервирование». В инженерной задаче пространство состояний не формализовано, и статистическое правдоподобие плана коррелирует с полезностью сильнее, чем в блоковом мире: агент видел тысячи похожих рефакторингов. Так что «модели не умеют планировать» — верное утверждение про формальное планирование и слишком сильное утверждение про декомпозицию рабочей задачи. Правильная позиция посередине: план от агента — приличный черновик и никудышная гарантия.
Есть и третья вещь, которую надо знать до того, как вы начнёте доверять объяснениям агента. Текст рассуждения не обязан отражать настоящую причину действия. Работа Language Models Don’t Always Say What They Think (Turpin et al., 2023) показала, что цепочка рассуждений может систематически не упоминать фактор, который на деле определил ответ. Anthropic воспроизвела похожий эффект уже на рассуждающих моделях — см. Reasoning models don’t always say what they think. Отсюда практическое правило, которое стоит держать в голове весь трек: «агент объяснил, почему так сделал» — это не доказательство, что он сделал поэтому. Проверять надо диф и прогон, а не объяснение. Подробнее — в главах про отказы и про проверяемость.
Единица работы: что считать хорошим шагом
Декомпозиция — это не «разбить на части поменьше». Это разбить так, чтобы каждая часть удовлетворяла четырём критериям. Если хотя бы один нарушен, шаг придётся резать дальше или переформулировать.
| Критерий | Формулировка | Как проверить, что выполнен |
|---|---|---|
| Проверяемость | У шага есть команда, чей успех означает «шаг сделан» | Вы можете написать эту команду до начала работы |
| Обратимость | Результат откатывается одним git revert или одной обратной миграцией |
Вы можете назвать процедуру отката в одном предложении |
| Локальность | Шаг трогает ограниченный набор файлов, названный заранее | Список файлов помещается в строку плана |
| Автономность | Внутри шага не требуется решение человека | В шаге нет слова «выбрать», «решить», «уточнить» |
Четвёртый критерий — самый пропускаемый и самый важный для этой главы. Если внутри шага спрятано решение, которое вы не приняли, агент его примет за вас. Не потому что он самонадеян, а потому что цикл «наблюдение — действие» (см. модель исполнения) обязан продолжаться: у агента нет состояния «жду», если вы его не создали. Он выберет то, что чаще встречалось в обучающих данных, и пойдёт дальше — молча.
Отсюда переформулировка задачи планирования: разложить работу так, чтобы все решения оказались на границах шагов, а не внутри них. Границы вы контролируете, внутренности — нет.
Разрез по слоям против разреза по сценариям
Классический вопрос декомпозиции: резать по архитектурным слоям или по пользовательским сценариям. Для человека это вопрос вкуса и планирования спринта. Для агента это вопрос того, когда он получит обратную связь.
Разрез по слоям («сначала все таблицы, потом весь домен, потом все ручки») даёт агенту три-четыре шага подряд, которые нечем проверить. Схема без кода не тестируется осмысленно, домен без ручек тоже. Значит, первый прогон случится в конце, и до него агент будет работать в режиме «пишу и надеюсь» — накапливая в контексте свои же непроверенные утверждения. Именно так рождаются сессии, где на четвёртом шаге выясняется, что на первом выбран не тот тип колонки, и откатывать надо всё.
Разрез по сценариям («сначала создание заказа насквозь, потом отмена») даёт зелёный прогон после каждого шага. Цена — часть слоёв придётся трогать по второму разу, и агент будет ворчать про дублирование. Это честная цена, и она обычно ниже.
Правило, к которому это сводится: шаг заканчивается прогоном, а не отчётом. Если шаг заканчивается фразой «реализовал слой репозиториев», у вас нет способа узнать, правда ли это, кроме чтения кода. Если он заканчивается строкой 12 passed от команды, которую вы назвали заранее, — способ есть. Про то, как устроены полезные критерии приёмки, стоит посмотреть главу «Приёмка» в треке системного анализа и «TDD и BDD» в треке тестирования — с агентом эти практики работают ровно так же, только цена их отсутствия выше.
План — это граф, а не список
Нумерованный список шагов скрывает зависимости. Из-за этого агент, столкнувшись с препятствием на шаге 3, тихо перескакивает к шагу 4 и делает его на неверном основании. Если зависимости выписаны явно, такой перескок становится видимым нарушением, а не «оптимизацией порядка».
+ падающий тест"] --> S2["2. Ключ идемпотентности
в схеме"] S1 --> S3["3. Ветка повторного
вызова в сервисе"] S2 --> S3 S3 --> S4["4. Обработка гонки
двух параллельных вызовов"] S3 --> S5["5. Метрика повторов"] S4 --> S6["6. Прогон нагрузочного
сценария"] S5 --> S6 D1{{"Решение человека:
окно дедупликации"}} -.-> S2 D2{{"Решение человека:
поведение при конфликте ключа"}} -.-> S4 classDef dec stroke-dasharray: 5 4; class D1,D2 dec;
Пунктирные узлы — это те самые решения, которые нельзя оставлять внутри шага. Они вынесены на границу и должны быть приняты до того, как шаг начнётся. Если человек не ответил, шаг не стартует — это и есть «остановиться и спросить», выраженное структурно, а не просьбой в промпте.
Такой граф можно проверять машинно, и это дешевле, чем проверять глазами. Ниже — минимальный валидатор плана: он ловит четыре вещи, которые люди пропускают при чтении.
Псевдокод.
validate(plan):
ошибки = []
если id шагов не уникальны: ошибки += "дубликат id"
для каждого шага:
если check пустой: ошибки += "шаг без критерия"
если файлы не заданы: ошибки += "шаг без границ дифа"
для каждой зависимости:
если её нет в плане: ошибки += "висячая зависимость"
порядок = топологическая_сортировка(plan) # алгоритм Кана
если порядок короче плана: ошибки += "цикл в зависимостях"
вернуть ошибки, порядок
Реализация.
from collections import deque
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Step:
"""Один шаг плана. Все поля обязательны, кроме зависимостей."""
id: str
goal: str # что становится правдой после шага
check: str # команда, чей успех означает выполнение
touches: tuple[str, ...] # границы дифа: файлы или каталоги
depends_on: tuple[str, ...] = ()
assumptions: tuple[str, ...] = () # допущения, принятые без проверки
def validate(plan: list[Step]) -> tuple[list[str], list[str]]:
"""Проверяет план и возвращает (ошибки, порядок исполнения).
Порядок пуст, если в графе зависимостей найден цикл.
Время O(V + E), память O(V + E), где V — шаги, E — зависимости.
"""
errors: list[str] = []
by_id: dict[str, Step] = {}
for step in plan:
if step.id in by_id:
errors.append(f"{step.id}: дубликат идентификатора")
by_id[step.id] = step
if not step.check.strip():
errors.append(f"{step.id}: шаг без исполняемого критерия")
if not step.touches:
errors.append(f"{step.id}: не заданы границы дифа")
# Строим граф и считаем входящие степени — основа алгоритма Кана.
outgoing: dict[str, list[str]] = {s.id: [] for s in plan}
indegree: dict[str, int] = {s.id: 0 for s in plan}
for step in plan:
for dep in step.depends_on:
if dep not in by_id:
errors.append(f"{step.id}: висячая зависимость {dep!r}")
continue
outgoing[dep].append(step.id)
indegree[step.id] += 1
queue = deque(sorted(sid for sid, deg in indegree.items() if deg == 0))
order: list[str] = []
while queue:
current = queue.popleft()
order.append(current)
for nxt in outgoing[current]:
indegree[nxt] -= 1
if indegree[nxt] == 0:
queue.append(nxt)
if len(order) != len(plan):
stuck = sorted(set(by_id) - set(order))
errors.append(f"цикл в зависимостях, не упорядочены: {stuck}")
return errors, []
return errors, order
Сложность — O(V + E) по времени и памяти: каждый шаг и каждая зависимость обрабатываются по разу. На планах из десятка шагов это, разумеется, не про производительность — это про то, что проверка формальная и не зависит от вашей внимательности в шесть вечера.
Что валидатор не ловит и не может поймать: осмысленность цели, достаточность критерия, реалистичность допущений. Автоматическая проверка отсеивает структурный мусор, а не плохие решения. Иллюзия «план прошёл валидацию, значит хороший» — ровно та ошибка, против которой написана эта глава.
Жизненный цикл задачи: где живёт состояние «спрашиваю»
У агента по умолчанию нет состояния ожидания. Оно появляется только если вы его создали — контрактом, режимом инструмента или отсутствием прав. Вот как выглядит цикл, в котором это состояние есть.
Обратите внимание на переход Check --> Ask по красному критерию второй раз подряд. Это лечение самого дорогого паттерна агентских сессий: попытка чинить падающий тест итерациями до победного. Вторая неудачная попытка — сигнал, что модель задачи неверна, а не что нужен третий вариант правки. Без явного лимита агент будет пробовать восьмой.
Семь триггеров остановки
Теперь главное. Вот признаки, по которым агент обязан прервать работу. Они сформулированы так, чтобы их можно было записать в контракт проекта и проверить в ревью: каждый триггер наблюдаем в тексте самого агента.
| Триггер | Как выглядит в выводе агента | Что должен спросить | Цена молчания |
|---|---|---|---|
| Двоякое требование | «Вероятно, имелось в виду…», «буду считать, что…» | Прямой вопрос с двумя вариантами и последствиями каждого | Диф решает не ту задачу; ревью проходит, потому что код корректен |
| Нет контракта | Пишет код раньше, чем сформулировал пред- и постусловия | «Какое поведение считается верным вот в этом краевом случае?» | Тесты пишутся под реализацию, а не под требование |
| Необратимое действие | DROP, rm -rf, push --force, платёжный вызов, письмо клиенту |
«Подтвердите: операция необратима, вот что именно произойдёт» | Восстановление дороже всей задачи, иногда невозможно |
| Конфликт с существующим решением | «Здесь странная реализация, перепишу» | «Это сделано намеренно? Вижу вот такую конструкцию» | Снесён обход бага, о котором знали двое, и оба в отпуске |
| Нет доступа или данных | Собирается мокнуть то, что должно быть реальным | «Нужен доступ к X либо разрешение работать на фикстуре» | Зелёные тесты на выдуманной реальности |
| Факт разошёлся с планом | «Файла, указанного в плане, нет, поэтому я…» | «План опирался на неверную картину, вот расхождение» | Дальнейшие шаги строятся на неверном основании |
| Превышен бюджет | Пятая итерация одного шага, окно заполнено | «Потрачено N шагов, прогресса нет, вот где застрял» | Квадратичный рост стоимости сессии без результата |
Четвёртый триггер стоит развернуть, потому что он самый неочевидный. Агент читает код без истории. Он не видит, что вот эта неуклюжая проверка появилась после инцидента, а вот этот sleep(200) обходит баг драйвера. Для него это просто плохой код, и «улучшить» его — естественное продолжение. Именно поэтому в правилах проекта полезно требовать: прежде чем удалять непонятную конструкцию, найти коммит, который её внёс, и прочитать сообщение. git log -S и git blame — дешёвые команды, и они превращают догадку в наблюдение. Тема хорошо разобрана в главе «Восстановление и археология истории» трека git.
Третий триггер — про необратимость — единственный, где на текст надеяться нельзя вообще. Просьба «спрашивай перед опасными действиями» соблюдается не всегда, и стоимость единственного несоблюдения асимметрична. Поэтому необратимое действие блокируется правами: у агента просто нет доступа к продовой базе, токен read-only, а force push запрещён на уровне сервера. Подробнее — в главе про безопасность. Формулировка, которую стоит запомнить: контракт защищает от невнимательности, права — от ошибки.
Обратная ошибка: агент, который спрашивает слишком много
Если вы напишете в контракте «спрашивай при любых сомнениях», вы получите агента, который задаёт шесть вопросов подряд, включая «использовать ли двойные кавычки». Это не безопаснее — это дороже, потому что внимание человека и есть самый дефицитный ресурс всей схемы.
Нужен критерий. Вот он, и он простой: вопрос имеет смысл, только если разные ответы дают разный план. Если при любом ответе агент делает одно и то же — это не вопрос, это вежливость, и она стоит вашего переключения контекста.
Всё остальное оформляется как допущение. В нашем пакете products/workbench/templates/memory/ — файл REASONING-DISCIPLINE.md — это выражено двумя правилами, которые стоит перенести к себе, даже если пакет вы не берёте:
- Каждое допущение помечается явно маркером
assumedс указанием, чем оно оправдано и во что обойдётся проверка. Допущение внутри абзаца невидимо. - Все допущения собираются в один список в конце плана. Список можно атаковать за тридцать секунд; допущения, размазанные по тексту, не читаются никогда.
Практический эффект от второго правила больше, чем кажется. Когда агент вынужден собрать свои догадки в один блок, часть из них он снимает сам — потому что рядом друг с другом они выглядят взаимно противоречивыми. Это дешёвый способ поднять качество плана без единого дополнительного вопроса к человеку.
Куда какая задача попадает, удобно смотреть на двух осях: во что обойдётся ошибка и насколько ясно требование.
Правый нижний угол — важный и непопулярный вывод. Задача, где цена ошибки высока, а требование ещё не оформилось, не делегируется агенту вообще, и никакой объём уточняющих вопросов этого не исправит: вы будете диктовать решение по частям и платить за посредника. Сформулируйте решение сами, потом отдайте исполнение.
Как заставить агента останавливаться
Способность спрашивать — не свойство модели, а свойство окружения, которое вы построили. Механизмов ровно четыре, и они разной надёжности.
1. Контракт проекта. CLAUDE.md, AGENTS.md, правила репозитория — тот самый стабильный префикс из главы про контекст. Формулировать надо наблюдаемо, а не пожеланиями.
## Остановки
- Перед первым изменением файлов выдай план: цель, границы дифа, шаги
с командой проверки, список допущений. Не начинай, пока план не одобрен.
- Если требование допускает два прочтения, дающие разный диф, — задай один
вопрос с обоими вариантами и последствиями. Не выбирай сам.
- Необратимые операции (миграции, удаление данных, вызовы внешних платных
API, push --force) не выполняй. Выведи команду и остановись.
- Если критерий шага не прошёл дважды подряд, остановись и опиши расхождение
между ожиданием и наблюдением. Третью попытку не делай.
- Если факт противоречит плану — остановись. Не правь план молча.
- Не удаляй непонятную конструкцию, не прочитав git log -S по ней.
Наблюдаемость здесь ключевая. «Будь осторожен» проверить нельзя. «Не делай третью попытку» — можно, и нарушение видно в транскрипте.
2. Режим инструмента. Claude Code, Cursor и Codex — все имеют тот или иной режим «читать и планировать, но не писать». Названия и горячие клавиши у них разные и меняются от версии к версии, поэтому смотрите документацию своего инструмента в день работы, а не мою формулировку. Общая механика везде одна: инструменты записи временно недоступны, поэтому агент физически не может начать. Это надёжнее контракта, потому что не зависит от того, дочитал ли он инструкцию до конца.
3. Разрешения на инструменты. Белый список команд, подтверждение на запись вне указанных каталогов, отсутствие сетевого доступа. Это уровень, который работает даже против инъекции в промпт, — единственный из четырёх.
4. Внешние ворота. Тесты, линтер, типы, CI. Они не заставляют агента спросить, но не дают неверному результату уехать дальше. Про их устройство — глава про проверяемость.
И теперь честная оговорка ко всем четырём. Ни один из механизмов не даёт гарантии соблюдения, а первые два — вообще вероятностные. Хуже того, у моделей есть известная склонность к угодливости: на вопрос «ты понял задачу?» ответ «да, понял» вероятнее правильного «нет, тут неоднозначность». Поэтому вопрос «понял?» бесполезен. Полезен вопрос, ответ на который нельзя дать угодливо: «перескажи задачу своими словами и назови три места, где ты мог понять её неправильно». Пересказ либо совпадает с вашим замыслом, либо нет, и это видно сразу.
Формат плана, который можно ревьюить
План на две страницы прозой никто не читает — а значит, он не выполняет свою единственную функцию. Работающий формат жёсткий и короткий.
План: идемпотентность резервирования
Цель: повторный вызов reserve с тем же ключом не создаёт вторую бронь
и возвращает тот же идентификатор.
Границы дифа: services/billing/reserve.py, services/billing/models.py,
migrations/, tests/billing/. Ничего вне этого списка.
Не делаем: не трогаем отмену брони, не меняем публичный контракт HTTP.
Шаги:
1. Тест на повторный вызов, ожидаемо красный.
check: pytest tests/billing/test_reserve.py::test_repeat -x
2. Колонка idempotency_key + уникальный индекс, миграция вперёд и назад.
check: alembic upgrade head && alembic downgrade -1 && alembic upgrade head
3. Ветка повторного вызова в reserve.
check: pytest tests/billing -x
4. Гонка двух параллельных вызовов с одним ключом.
check: pytest tests/billing/test_reserve.py::test_race -x
Допущения:
- [assumed] ключ приходит от клиента и стабилен между ретраями;
не проверено, проверяется чтением клиента за 10 минут.
- [assumed] окно дедупликации не ограничено по времени;
если нужно ограничить — меняется шаг 2, скажите до его начала.
Откат: git revert диапазона + alembic downgrade -1.
Оценка: 4 шага, ориентировочно 15-25 вызовов инструментов.
Что делает этот формат читаемым: раздел «не делаем» (без него агент расширяет объём), явные границы дифа (без них правки расползаются), команда проверки у каждого шага (без неё шаг не проверяем) и список допущений отдельно (без него они невидимы). Оценка в вызовах инструментов, а не в часах, — потому что это единственная величина, которую можно сравнить с фактом в конце.
Антипример, который выглядит как план, но им не является:
1. Изучить текущую реализацию
2. Спроектировать решение
3. Реализовать
4. Покрыть тестами
5. Проверить, что всё работает
Здесь нет ни одного проверяемого утверждения. Такой план нельзя отвергнуть — а значит, нельзя и одобрить: одобрение, которое невозможно не дать, ничего не значит.
Как это выглядит в диалоге
От ответа зависит шаг 2 и миграция Note over А: состояние ожидания, токены не тратятся Ч-->>А: от клиента, заголовок Idempotency-Key А-->>Ч: план: 4 шага, границы дифа, 2 допущения Ч-->>А: шаги 1-3 да, шаг 4 отложить А->>Р: шаг 1, красный тест А->>CI: pytest ...::test_repeat -x CI-->>А: 1 failed, как и ожидалось А->>Р: шаг 2, миграция А->>CI: alembic upgrade / downgrade / upgrade CI-->>А: ошибка: индекс конфликтует с существующими дублями А-->>Ч: Факт разошёлся с планом: в таблице уже есть дубли.
Варианты: чистка, частичный индекс, ключ с датой Note over А,Ч: остановка по триггеру «факт против плана»,
а не тихая правка плана
Ключевых мест два: шаг 5, где вопрос задан до плана и с указанием, на что влияет ответ; и последний обмен, где агент остановился на расхождении вместо того, чтобы самостоятельно выбрать «просто удалю дубли». Второе — именно та ситуация, где молчаливая инициатива стоит данных.
Цена планирования
Планирование не бесплатно, и об этом надо говорить прямо, иначе получится реклама.
Токены. Разведка кодовой базы перед планом — это чтение файлов, grep, дерево репозитория. Всё это остаётся в окне и оплачивается на каждом последующем ходу как входные токены (механика — в главе про контекст). Сам план — сотни выходных токенов, это мелочь. Дорога разведка, а не план.
Время человека. Чтение плана, ответ на вопрос, повторное чтение исправленного плана — три переключения контекста. Переключение стоит дороже, чем кажется по календарю: вы вышли из своей задачи и вернётесь в неё не сразу.
Внимание. Самый дефицитный ресурс. План, который вы прочитали по диагонали и одобрили, хуже отсутствия плана: он создаёт ощущение контроля и снимает бдительность на ревью. Если нет сил читать план внимательно — не запрашивайте его, а сделайте задачу руками или отложите.
Числа приводить не буду, кроме одного, у которого есть источник. Организация METR провела рандомизированное исследование на опытных контрибьюторах открытых репозиториев: разработчики выполняли задачи в своих же проектах с доступом к ИИ-инструментам и без. Результат — участники выполняли задачи в среднем на 19% медленнее с инструментами, при этом сами оценивали, что стали быстрее примерно на 20%. Оговорки существенные: 16 разработчиков, 246 задач, зрелые незнакомые агенту кодовые базы, инструменты начала 2025 года. Это не приговор агентам и не измерение вашей ситуации. Но одно следствие переносится хорошо: субъективное ощущение скорости — плохой измеритель, и планирование стоит оценивать по попаданию в цель, а не по ощущению «пошло бодро».
Отсюда практическое правило соразмерности:
- задача до получаса руками — плана не нужно, агента, скорее всего, тоже;
- задача на несколько часов, обратимая — короткий план на пять строк;
- задача с миграцией, внешним контрактом или деньгами — полный план с допущениями и точками остановки, либо не агенту.
Типичные ошибки
Просить план и не читать его. Самая частая. Ритуал вместо контроля, худший из возможных исходов.
План после кода. Агент пишет диф, потом «оформляет план» по написанному. Такой план всегда идеально согласован с дифом и не содержит информации. Признак: план появился в том же ответе, что и правки.
Шаги без критериев. «Отрефакторить сервис» — не шаг. Проверить нельзя, значит, выполнение недоказуемо.
Слишком мелкие шаги. Двадцать шагов по три строки — это не план, это транскрипт. Накладные расходы на согласование съедают выигрыш; ориентир — шаг, который человек проверяет за пару минут.
Решения внутри шагов. Слово «выбрать» в теле шага означает, что выбирать будет агент, молча.
Молчаливая правка плана. Агент столкнулся с препятствием и переписал план под то, что получилось. Ловится сверкой дифа с исходным планом; поэтому план надо хранить, а не держать в окне (см. главу про память агента).
Планирование ради планирования. Пятистраничный документ на задачу в двадцать строк. Если план читать дольше, чем сделать задачу, — не планируйте.
Считать план обязательством. Возвращаемся к началу: план не обязывает агента. Он обязывает вас — сверить результат с тем, что было согласовано.
Мини-итог
- План агента — гипотеза о работе, а не обязательство. Его ценность в том, что он отвергается за две минуты, тогда как диф проверяется за сорок.
- Модели не выполняют формального планирования с гарантией исполнимости (PlanBench), а текст рассуждения не обязан отражать настоящую причину действия (Turpin et al.). Проверяют диф и прогон, а не объяснение.
- Хороший шаг проверяем, обратим, локален и автономен. Автономность — самый пропускаемый критерий: решение внутри шага агент примет за вас, потому что состояния «жду» у него по умолчанию нет.
- Резать задачу лучше по сквозным сценариям, а не по архитектурным слоям: так каждый шаг заканчивается прогоном, а не обещанием.
- План полезно записывать графом с явными зависимостями и вынесенными на границы решениями; структурные дефекты плана ловятся валидатором за
O(V + E), смысловые — только человеком. - Семь триггеров остановки: двоякое требование, отсутствие контракта, необратимое действие, конфликт с намеренным решением, нехватка доступа, расхождение факта с планом, превышение бюджета.
- Вопрос стоит задавать, только если разные ответы дают разный план. Остальное — помеченное допущение в списке в конце плана.
- Контракт защищает от невнимательности, права — от ошибки. Необратимое блокируется правами, а не просьбой.
- Планирование стоит токенов на разведку, времени на согласование и внимания на чтение. Если задача мелкая и обратимая — не планируйте; если требование туманно, а цена ошибки высока — не делегируйте.
Что дальше
План согласован, шаги выполнены, критерии зелёные. Остаётся то, что нельзя автоматизировать: прочитать диф. Следующая глава — про то, куда смотреть в первую очередь, какие дефекты характерны именно для кода от агента и почему привычный чек-лист ревью здесь работает не полностью.
Ревью кода от агента: на что смотреть в первую очередь
Источники
- PlanBench: An Extensible Benchmark for Evaluating Large Language Models on Planning and Reasoning about Change — Valmeekam et al. Систематическая проверка планирующих способностей на формальных задачах.
- LLMs Can’t Plan, But Can Help Planning in LLM-Modulo Frameworks — Kambhampati et al., 2024. Конструктив: генератор плюс внешний верификатор вместо доверия к плану.
- Language Models Don’t Always Say What They Think — Turpin et al., 2023. Про неверность цепочки рассуждений как объяснения.
- Reasoning models don’t always say what they think — Anthropic, 2025. То же на рассуждающих моделях; читать с поправкой на то, что это текст производителя.
- Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity — METR, 2025. Единственное число в главе и все оговорки к нему.
- ReAct: Synergizing Reasoning and Acting in Language Models — Yao et al., 2022. Откуда взялся цикл «рассуждение — действие — наблюдение».
- Building effective agents — Anthropic. Разбор схем планирования и оркестрации; полезно, но это текст производителя.
- Фредерик Брукс, «Мифический человеко-месяц» — про стоимость поздних решений; ничего из этого не устарело.
products/workbench/templates/memory/— наш пакет:REASONING-DISCIPLINE.mdзадаёт маркерыverified/derived/assumed/uncheckedи требование выносить допущения в отдельный список,VERIFIED-CODE.md— правило «контракт до реализации, а если контракт не формулируется, правильный следующий вывод — вопрос».