Как ставить задачи AI-агенту: одна фича за раз, список фич и критерии готовности
Задачу AI-агенту ставят по одной: в работе всегда ровно одна фича, у неё есть наблюдаемое поведение и исполняемая команда проверки, а всё замеченное по пути агент записывает в бэклог, а не чинит. Состояние всех фич хранится в машиночитаемом списке (например, feature_list.json), и перевести фичу в «готово» может только скрипт проверки, а не сам агент.
Почему агент берётся за всё сразу
Вы просите агента сделать регистрацию. Через пару часов в диффе двенадцать изменённых файлов и восемьсот новых строк: модель пользователя, верификация почты, подключённый почтовый сервис, хеширование паролей, отрефакторенный middleware и переложенные тесты. Всё сильно связано между собой, и ни один сценарий не проходит целиком. Следующая сессия открывает этот код и не понимает, с чего начать.
Причина не в «плохой модели». У человека есть чувство «на сегодня достаточно» и обычная усталость — они сами ограничивают размах работы. У агента нет ни того, ни другого. Дописать «и заодно поправлю обработку ошибок» стоит ему несколько десятков токенов, а цена для проекта — реальная: внимание размазывается, связанность растёт, доделанного нет.
Есть и простая арифметика. Пусть C — полезная ёмкость контекста, k — число задач, которые агент держит одновременно. На каждую приходится примерно C/k ресурса рассуждения. Когда это значение падает ниже того, что нужно для завершения одной задачи, не завершается ни одна. Поэтому правильный ответ на вопрос, как ставить задачи AI-агенту, начинается не с формулировки промпта, а с ограничения: одна задача в работе.
Правило одной фичи (WIP=1)
WIP — work in progress, число задач в работе. Для агента его ставят равным единице: в любой момент активна ровно одна фича, следующая начинается только после того, как текущая прошла проверку. Правило записывают в файл инструкций проекта (AGENTS.md или CLAUDE.md), чтобы агент видел его в каждой сессии:
## Правила работы
- В любой момент времени в состоянии `active` находится РОВНО ОДНА фича.
- Следующую фичу начинать только после того, как текущая перешла в `passing`.
- Запрещено «заодно» рефакторить, переименовывать, реорганизовывать каталоги
и чинить несвязанные баги. Нашли проблему — запишите в `BACKLOG.md` и идите дальше.
- Изменения вне скоупа активной фичи считаются нарушением, даже если они полезны.
Последний пункт важнее, чем кажется. Агент искренне считает свои «заодно» полезными, и часто так и есть. Но полезная правка вне скоупа всё равно смешивает две задачи в одном диффе, и откатить или проверить их по отдельности уже нельзя.
BACKLOG.md: куда девать «заодно»
Импульс «заодно починить» нельзя просто запретить — ему нужен легальный выход. Таким выходом служит файл BACKLOG.md: агент записывает туда замеченную проблему одной-двумя строками и возвращается к текущей фиче. Информация не теряется, а скоуп не разрастается. Без бэклога правило одной фичи агент рано или поздно начнёт обходить, потому что выбор «молча оставить баг» кажется ему хуже нарушения.
# BACKLOG.md
- [ ] middleware/errors.py: разный формат ошибок в /auth и /orders
- [ ] tests/: фикстуры пользователя дублируются в трёх файлах
- [ ] нужна ли верификация e-mail при регистрации? — спросить
Критерии готовности: «готово» — это команда
Вторая половина проблемы — агент сам решает, что задача закончена. «Код выглядит нормально», «функция существует», «ошибок компиляции нет» — всё это ощущения, а не факты. Критерий готовности задачи для агента должен быть исполняемой командой с однозначным результатом: прошла или нет.
F01: регистрация пользователя
verification: pytest tests/e2e/test_auth.py::test_register_flow
state: active
Разница между слабым и строгим критерием хорошо видна на примерах:
| Слабый критерий | Строгий критерий |
|---|---|
| Код компилируется | pytest tests/e2e/test_cart.py -q проходит |
| Нет синтаксических ошибок | curl к /cart возвращает код 200 |
Функция addToCart существует | После добавления товара GET /cart показывает его и верную сумму |
| Выглядит правильно | npx playwright test cart.spec.ts проходит |
Хорошая команда проверки детерминирована (одинаковый результат при одинаковом коде), запускается без участия человека и проверяет поведение снаружи — так, как его увидит пользователь или клиент API. Если такую команду написать не получается, это сигнал, что задача сформулирована нечётко.
Декомпозиция: какого размера должна быть фича
Самый частый вопрос после «одна фича за раз» — а что считать фичей. Слишком широкая задача не закончится за одну сессию, слишком узкая тонет в накладных расходах на запуск, контекст и проверку.
- Слишком широко: «реализовать корзину». Это неделя работы и десяток сценариев.
- В самый раз: «пользователь может добавить товар в корзину и увидеть его там». Одно наблюдаемое поведение, один сценарий проверки.
- Слишком узко: «создать поле quantity в модели CartItem». Это шаг реализации, а не поведение; проверять его отдельно бессмысленно.
Удобная проверка размера: одна фича = одна сессия агента = один осмысленный коммит = один сквозной (E2E) сценарий. Если фича не укладывается в сессию — делите дальше. Если для неё нельзя написать отдельный сценарий — она слишком мелкая, объедините её с соседней.
Пример декомпозиции задачи «корзина»
Широкое требование разбивается на атомарные единицы, у каждой из которых одно поведение и своя команда проверки:
- Добавить товар в корзину и увидеть его в списке.
- Изменить количество товара — сумма пересчитывается.
- Удалить товар из корзины.
- Корзина гостя сохраняется после входа в аккаунт.
- Нельзя добавить товар, которого нет в наличии, — понятная ошибка.
Финальная проверка декомпозиции: ни одна единица не требует «попутно» тронуть другую. Если для пункта 4 нужно переписать пункт 1, порядок или границы выбраны неудачно.
Список фич: feature_list.json
Правило одной фичи держит порядок внутри сессии. Но кто помнит между сессиями, что уже готово? Запись в чате «auth и каталог сделаны, корзина почти готова, но есть баги» новой сессии ничего не даёт: что значит «почти», какие тесты прошли, что блокирует оплату — неизвестно. Каждая новая сессия — как новый водитель той же машины: если заметки жили только в голове прежнего, новый не знает ничего.
Поэтому состояние выносят наружу, в машиночитаемый файл. В курсе это .agent/feature_list.json:
[
{
"id": "F01",
"behavior": "Пользователь регистрируется по e-mail и паролю и сразу входит",
"verification": "pytest tests/e2e/test_auth.py::test_register_flow",
"state": "passing",
"evidence": "a3f9c12: 1 passed in 2.31s"
},
{
"id": "F02",
"behavior": "Пользователь добавляет товар в корзину и видит его в списке",
"verification": "pytest tests/e2e/test_cart.py::test_add_item -q",
"state": "active"
},
{
"id": "F03",
"behavior": "Оплата заказа картой",
"verification": "pytest tests/e2e/test_payment.py -q",
"state": "blocked",
"blocker": "нет тестовых ключей платёжного шлюза"
}
]
У каждой записи три обязательных поля: описание поведения (наблюдаемое снаружи), команда проверки (исполняемая, без человека) и состояние. Необязательное четвёртое — evidence: хеш коммита и вывод проверки, доказательство того, что переход в passing действительно был.
Почему JSON, а не Markdown-список? Его читают не только агент, но и скрипты: планировщик выбирает следующую фичу в not_started, верификатор берёт команду проверки, отчёт о сессии собирается автоматически, а распределение состояний даёт метрики здоровья проекта. Список фич — позвоночник харнеса, а не памятка.
Четыре состояния и разрешённые переходы
| Переход | Когда |
|---|---|
not_started → active | Фичу берут в работу; другой активной нет |
active → passing | Скрипт проверки завершился с кодом 0 |
active → blocked | Внешний блокер; причина записана в blocker |
blocked → active | Блокер снят |
Других переходов нет. В частности, нет пути в passing в обход проверки и нет состояния «почти готово».
Гейт passing: агент может запросить, но не объявить
Центральное правило всей схемы: агент не редактирует поле state руками. Он может запустить проверку, но объявить результат сам не может — это делает скрипт. Разница между «агент отчитался» и «скрипт подтвердил» — разница между текстом и фактом. Правила тоже записывают в файл инструкций:
## Правила списка фич
- Файл: `.agent/feature_list.json`. Это единственный источник истины по скоупу.
- Ровно одна фича в состоянии `active`.
- Ты НЕ редактируешь поле `state` вручную. Его меняет `scripts/verify.sh`.
- Чтобы перевести фичу в `passing`, запусти `./scripts/verify.sh F02` и добейся кода 0.
- Если фича заблокирована — переведи в `blocked` и обязательно укажи `blocker`.
- Новые фичи добавляются только в `not_started` и только с командой верификации.
Сам скрипт проверяет на двух уровнях: сначала общий прогон проекта (фича не должна сломать остальное), потом команду конкретной фичи. Локальный успех при глобальной регрессии успехом не считается.
#!/usr/bin/env bash
# Единственный компонент, имеющий право ставить passing.
set -uo pipefail
FID="$1"
FILE=".agent/feature_list.json"
CMD=$(jq -r --arg id "$FID" '.[] | select(.id==$id) | .verification' "$FILE")
if [ -z "$CMD" ] || [ "$CMD" = "null" ]; then
echo "FAIL: нет команды верификации для $FID"; exit 2
fi
echo "== global check =="
make check || { echo "FAIL: общая проверка красная, $FID не может быть passing"; exit 1; }
echo "== feature check: $CMD =="
if OUT=$(eval "$CMD" 2>&1); then
SHA=$(git rev-parse --short HEAD)
LAST=$(printf '%s\n' "$OUT" | tail -n 1)
TMP=$(mktemp)
jq --arg id "$FID" --arg ev "$SHA: $LAST" \
'map(if .id==$id then .state="passing" | .evidence=$ev else . end)' \
"$FILE" > "$TMP" && mv "$TMP" "$FILE"
echo "PASS: $FID"
else
printf '%s\n' "$OUT"
echo "FAIL: $FID"; exit 1
fi
Правило «не редактируй state» в тексте — это всё ещё просьба. Чтобы она стала ограничением, запись в файл можно закрыть механически: проверкой в CI, которая сверяет evidence с историей коммитов, или ограничениями прав агента в вашем инструменте. Принцип тот же, что и везде в харнесе: важное правило подкрепляется механикой.
Как измерить, что агент доводит задачи до конца
Соблазнительная метрика — объём кода за сессию. На практике она работает наоборот: большой дифф чаще означает, что агент распылился на несколько направлений. Считайте фичи в состоянии passing, а не строки.
Удобная метрика из курса — VCR (Verified Completion Rate): число проверенных задач, делённое на число активированных. Правило простое: пока VCR меньше единицы, новые задачи активировать нельзя. Это обратное давление, которое харнес прикладывает к агенту, и заодно способ сделать недоделанное видимым числом, а не ощущением.
Типичные ошибки при постановке задач агенту
- Задача без команды проверки. «Сделай регистрацию» без сценария — агент сам решит, где остановиться, и решит неправильно.
- Несколько фич в одном запросе. «Сделай корзину и оплату» — это две активные задачи, и ни одна не будет доведена до конца.
- Состояние только в чате. Прогресс, записанный в переписке, исчезает с концом сессии. Он должен жить в файле.
- Самоотчёт вместо проверки. «Агент сказал, что тесты прошли» — не то же самое, что код возврата скрипта.
- Нет бэклога. Запрет «заодно» без места, куда записать замеченное, агент обходит.
Когда одной фичи за раз уже мало
Схема «одна фича — одна сессия — одна проверка» хорошо работает для одного агента в одном цикле. Когда задач становится много и появляются независимые ветки, её расширяют до системы из нескольких циклов: отдельный узел планирования, узлы реализации, узел проверки и рёбра между ними — параллельные, условные, с возвратом к реализации, если проверка не прошла. Но и там каждый узел реализации работает по тому же правилу: одна задача, исполняемый критерий готовности, состояние вне головы агента. Граф не отменяет дисциплину одной фичи, а строится поверх неё.
Важное предупреждение из того же разбора: цикл честно оптимизирует ту метрику, которую ему дали. Если критерий готовности выбран неудачно — например, «тикет закрыт» вместо «проблема решена», — агент научится выполнять критерий, а не задачу. Поэтому формулировка поведения и команды проверки заслуживает больше времени, чем кажется.
Итог
Если коротко, как ставить задачи AI-агенту: по одной, с наблюдаемым поведением и исполняемой командой проверки, с бэклогом для всего замеченного по пути и со списком фич в файле, где переход в «готово» делает скрипт, а не агент. Тормоза у агента ставит харнес — сам он их не включит. Правило WIP=1, feature_list.json и verify.sh разобраны на практике в курсе по harness engineering на примере сквозного проекта.
Частые вопросы
Можно ли дать агенту сразу несколько задач, если они маленькие?
Можно сложить их в список фич, но в работе одновременно должна быть одна. Агент берёт следующую только после того, как текущая прошла проверку, — так каждая задача доводится до конца.
Что делать, если для задачи невозможно написать автоматическую проверку?
Обычно это значит, что задача сформулирована как действие, а не как поведение. Переформулируйте её через то, что увидит пользователь или клиент API; для визуальной части подойдут e2e-тесты в браузере.
Кто пишет feature_list.json — человек или агент?
Начальный список удобно составить вместе: агент предлагает декомпозицию, человек правит. Дальше агент может добавлять новые фичи, но только в состоянии not_started и с командой проверки.
Чем feature_list.json отличается от задач в трекере?
Трекер рассчитан на людей, а список фич — на машинное чтение: у каждой записи есть исполняемая команда и строгое состояние, которое меняет скрипт. Их можно синхронизировать, но агенту нужен файл в репозитории.
Не замедляет ли правило одной фичи работу?
Сессии становятся короче, но доля доведённых до конца задач растёт. Время, которое раньше уходило на разбор полуготового кода из трёх смешанных фич, экономится.
Эта тема подробно разобрана с практикой в курсе «Harness Engineering: инженерия надёжности AI-агентов».
- feature_list.json для AI-агента: WIP=1, статусы фич и верификация через verify.sh
- Graph Engineering для AI-агентов: когда одного цикла мало и нужен граф задач