Во всех предыдущих заходах я был внутри цикла: ставил задачу, читал план, смотрел дифф, принимал результат. Здесь выхожу из цикла реализации. Задача уходит агенту, я возвращаюсь к готовой локальной ветке, проверяю её и только после этого сам отправляю ветку и создаю пулл-реквест.
Девятый заход серии. Проект тот же: Symfony 8, 96 тысяч строк, 830 тестов, CI на 8 минут.
Задача для автономного прогона
Выбирал специально: достаточно большая, чтобы автономность имела смысл, и достаточно очерченная, чтобы у неё был проверяемый критерий.
Метки на задачах. Сущность метки, привязка многие-ко-многим, управление в интерфейсе проекта, фильтр по метке в списке задач, отображение на карточке. Примерно 400 строк в знакомом паттерне: миграция, сущность, связь, интерфейс и фильтр.
Что важно: у задачи нет архитектурных развилок. Всё, что нужно решить, уже решено в проекте на других сущностях. Автономный прогон на задаче с развилкой я пробовал позже, и это был один из двух выброшенных прогонов.
Три слоя ограничителей
Автономность — это не «разрешить всё». Это заранее описанные границы, внутри которых можно не спрашивать.
Слой первый: разрешения на инструменты. Что агент может делать без вопроса, а что требует подтверждения.
{
"permissions": {
"allow": [
"Bash(make test)",
"Bash(make check)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git checkout -b *)",
"Edit(src/**)",
"Edit(tests/**)",
"Edit(templates/**)",
"Edit(migrations/**)"
],
"deny": [
"Bash(git push *)",
"Bash(git reset --hard *)",
"Bash(git rebase *)",
"Bash(docker compose down *)",
"Bash(php bin/console doctrine:database:drop *)",
"Bash(php bin/console doctrine:schema:drop *)",
"Edit(.env*)",
"Edit(config/packages/security.yaml)",
"Edit(phpstan-baseline.neon)"
]
}
}Запреты интереснее разрешений. Отправка в удалённый репозиторий закрыта — PR создаю я командой после просмотра. Жёсткий сброс и перебазирование закрыты, потому что они уничтожают историю, а история автономного прогона — единственный способ понять, что там происходило.
Базовая линия статического анализа закрыта отдельной строкой. Без этого запрета у агента есть простой способ сделать проверку зелёной: дописать туда новое замечание.
Слой второй: хуки. То, что должно происходить всегда, независимо от решений агента. Ниже фрагмент конфигурации инструмента на момент эксперимента; названия событий и схема файла зависят от установленной версии, поэтому копировать его без сверки с документацией нельзя.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [{ "type": "command", "command": "make format" }]
}
]
}
}В этом Symfony-проекте цель make format запускает принятый проектом PHP-форматтер. Форматирование после каждой правки убирает из финального диффа шум по стилю. Насколько именно оно ускоряет чтение, я не замерял; практическая разница была заметна глазами.
Слой третий: условия остановки. Самый важный и единственный, который пишется словами.
Условия остановки
ОСТАНОВИСЬ И СПРОСИ, ЕСЛИ:
- нужно изменить публичный контракт: сигнатуру существующего сервиса,
схему существующей таблицы, формат существующего сообщения очереди
- нужна новая зависимость в composer.json
- нужно тронуть модуль Core
- тест, который был зелёным до начала работы, стал красным и причина
не очевидна из твоего же диффа
- ты сделал три попытки исправить одно и то же и не вышло
- задача оказалась больше 600 строк диффа
При остановке: опиши состояние, что сделано, в чём затык. Ничего не откатывай.Последний пункт про откат добавлен после первого выброшенного прогона. Агент упёрся, решил, что зашёл не туда, и откатил четыре часа собственной работы до чистого состояния. Формально разумно. Практически я потерял возможность посмотреть, где именно он свернул.
Пункт про три попытки — из наблюдения: после третьей неудачной попытки исправить одно и то же дальше идёт не решение, а перебор. Признак того, что модель проблемы неверна, и продолжение только увеличивает дифф.
Гигиена коммитов
Автономный прогон легко превращается в один коммит на 400 строк с сообщением «реализована функция меток». Такой PR невозможно ревьюить.
Требование в постановке:
Коммить по смысловым шагам, не в конце.
Порядок: миграция и сущность → репозиторий и сервис → тесты →
контроллер и маршруты → шаблоны.
Каждый коммит оставляет проект в рабочем состоянии: make test проходит.
Сообщение: что изменилось и зачем, без перечисления файлов.Результат — шесть коммитов, каждый читается отдельно. Требование зелёных тестов на каждом коммите даёт ещё и точку возврата: если пятый шаг оказался неудачным, откат до четвёртого не ломает ничего.
Что получилось
Прогон занял 2 часа 40 минут почти без моего участия: один раз я ответил на условие остановки. На выходе была готовая локальная ветка:
- 6 коммитов, 430 строк, 14 файлов
- 21 новый тест, все зелёные
make checkпроходит, базовая линия не выросла- одна остановка с вопросом
Остановка была правильной: агент дошёл до фильтра по меткам в списке задач и обнаружил, что существующий построитель запроса не поддерживает фильтр по связи многие-ко-многим без изменения публичного метода репозитория. Сработало первое условие остановки.
Я ответил тремя строками, он продолжил. Общее время моего участия — четыре минуты.
Два выброшенных прогона
Честно про то, что не сработало.
Первый. Задача с архитектурной развилкой: «добавь версионирование настроек проекта». Развилок там две, обе неочевидные — хранить снимки целиком или дельты, версионировать на уровне настройки или всего набора. Агент выбрал по варианту в каждой развилке, ни разу не остановившись: формально ни одно условие остановки не сработало, публичные контракты не менялись.
Через три часа я получил рабочую реализацию не той архитектуры. Выбросил.
Вывод: автономный прогон подходит для задачи без развилок. Развилка должна быть закрыта до старта, в постановке — либо решением, либо явным условием остановки на неё.
Второй. Тот, где агент откатил свою работу. Из него выросло правило «ничего не откатывай при остановке».
Два выброшенных из семи прогонов. В моём случае это оказалось приемлемо: такие прогоны почти не требовали моего участия до финального разбора. Но они всё равно расходуют вычислительные ресурсы, а неудачный результат нужно диагностировать, поэтому универсально бесплатной такую автономность считать нельзя.
Чего я не отдаю
Отправку в удалённый репозиторий и создание PR. Не из недоверия к коду, а потому что описание PR — это документ для человека, и его пишу я, посмотрев на дифф.
Слияние. Даже при зелёном CI.
Решение, что задача сделана. Агент отвечает на вопрос «выполнены ли критерии», а не на вопрос «решена ли задача». Это разные вопросы, и второй остаётся мне.
Замер
| Показатель | Значение |
|---|---|
| Время прогона | 2 ч 40 мин |
| Моё участие | 4 мин |
| Дифф | 430 строк, 6 коммитов |
| Остановок с вопросом | 1 |
| Правок на ревью | 3 мелких |
| Выброшенных прогонов из семи | 2 |
Три правки на ревью — переименование метода, лишний индекс в миграции и один тест, дублирующий соседний. Мелочь на пятнадцать минут.
Что дальше
Локальная ветка готова и зелёная. После просмотра я создаю PR; в следующем, последнем заходе серии он поедет в CI, и выяснится, что «сделай зелёным» — самая опасная формулировка из всех, что были в этой серии.