Ошибка

Агент написал «готово», а фича не работает: почему так и как проверять работу AI-агента

Обновлено: · Skillaria.Top

Агент пишет «готово», потому что оценивает работу сам и видит только то, что проверил: обычно это юнит-тесты, которые не видят стыков между модулями, миграций, конфигурации и сквозного сценария. Лечится структурой, а не промптом: Definition of Done в AGENTS.md, обязательный E2E-прогон, скрипт верификации, который один имеет право поставить статус passing, и независимый оценщик с чистым контекстом.

Почему так происходит

Ситуация знакома всем, кто работает с агентами: агент написал «готово», а фича не работает. Он не врёт специально. Он сообщает о том, что сам успел проверить, и искренне уверен, что этого достаточно. Разрыв между заявлением и реальностью — свойство самой схемы «исполнитель сам оценивает свою работу», а не дефект конкретной модели.

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

Хроника типичного ложного «готово»

Агенту поручили сброс пароля. Он меняет схему базы, добавляет эндпоинт, подключает шаблон письма, запускает юнит-тесты — всё зелёное. В чате появляется уверенное «Готово».

На деле произошло следующее:

  • миграция упала на середине и оставила схему в несогласованном состоянии;
  • шаблон письма есть, но почтовый сервис (SMTP) в окружении не сконфигурирован — письмо не уходит;
  • сценарий «пользователь нажал ссылку → ввёл новый пароль → вошёл» ни разу не запускался.

Ни одну из этих проблем юнит-тесты поймать не могли: базу замокали, отправку письма замокали, а сквозного прогона не было вовсе.

Почему зелёные тесты не доказывают, что фича работает

Юнит-тест проверяет модуль в изоляции: зависимости заменены моками, внешний мир отключён. Это даёт скорость и точность, но и систематическую слепоту. Слепота — не баг тестов, а их дизайн. Зелёные юниты доказывают только то, что каждая деталь работает отдельно, а не то, что детали работают вместе.

Есть четыре класса дефектов, которые юнит-тест не видит в принципе:

Класс багаПримерПочему юнит не ловит
Несоответствие интерфейсовОдин компонент отдаёт относительный путь, другой ждёт абсолютныйКаждый тестируется со своим моком, который «соглашается» на любой формат
Распространение состоянияСхема БД поменялась, а кэш ORM держит старуюВ юните нет ни реальной базы, ни кэша
Жизненный цикл ресурсовФайловые дескрипторы и соединения не освобождаются, память течёт на больших файлахТест работает на маленьких данных и завершается раньше, чем ресурсы кончаются
Окружение и конфигурацияНет переменной окружения, не настроен SMTP, миграция не накатиласьТест не запускает приложение в реальной конфигурации

Уровни проверки вложены: всё, что ловят юниты, ловят и интеграционные тесты, а всё, что ловят интеграционные, ловит сквозной (E2E) прогон. Каждый следующий уровень видит строго больше. Поэтому право сказать «готово» должно быть только у верхнего уровня.

Как измерить проблему: verification gap

Прежде чем чинить, посчитайте. Возьмите последние 10 задач, закрытых агентом. По каждой ответьте на два вопроса: сказал ли агент «готово» и подтвердила ли это независимая проверка (вы сами прошли сценарий, E2E-тест зелёный). Доля расхождений — ваш verification gap.

Это число полезно по двум причинам. Оно показывает масштаб проблемы без споров «да вроде нормально работает». И оно даёт базовую линию: после каждого изменения в процессе вы пересчитываете его на похожем наборе задач и видите, помогло ли.

Как проверять по-настоящему: три слоя валидации

Удобная аналогия — автосервис. Машина проходит три поста, и пропустить пост нельзя.

  1. Диагностика (секунды): синтаксис, типы, линтер. Ловит опечатки и грубые ошибки.
  2. Стенд (минуты): юнит- и интеграционные тесты, приложение стартует. Двигатель работает, но машина ещё стоит на месте.
  3. Тест-драйв: сквозной сценарий так, как его проходит пользователь, — через API, браузер или CLI, с реальной базой и конфигурацией.

Правило прогрессии: нельзя ехать на тест-драйв, пока горят ошибки диагностики, и нельзя выпускать машину, пропустив тест-драйв. Зелёные юнит-тесты — это пройденный второй пост, а не третий.

Удобно свести всё в один вход:

# Makefile
check:
	ruff check .
	mypy app
	pytest tests/unit tests/integration

e2e:
	docker compose up -d db mailhog
	alembic upgrade head
	pytest tests/e2e

verify: check e2e

Здесь E2E поднимает реальную базу и тестовый почтовый сервер, накатывает миграции и проходит сценарий целиком. Упавшая миграция или ненастроенная почта проявятся сразу, а не у клиента.

Definition of Done в AGENTS.md

Агент не должен решать сам, что значит «готово». Критерий записывается в файл инструкций и формулируется проверяемо:

## Definition of Done
Фича готова, только если выполнены ВСЕ пункты:
1. make check завершился с кодом 0
2. make e2e завершился с кодом 0, сценарий фичи есть в tests/e2e
3. Миграции применяются на чистой базе и откатываются
4. Новые переменные окружения описаны в .env.example
5. В PROGRESS.md записано, что сделано и как проверено

НЕ являются признаком готовности:
- «код написан и выглядит правильно»
- «юнит-тесты проходят»
- «у меня локально работает»

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

Кто имеет право поставить «passing»

Следующий шаг — убрать статус готовности из рук агента совсем. Фичи хранятся в списке (например, feature_list.json) с полями «поведение», «команда проверки», «состояние». Перевести фичу в passing может только скрипт: он запускает общий make check, затем команду проверки конкретной фичи и при успехе фиксирует статус вместе с хешем коммита как доказательство. Не прошло — фича остаётся активной, никакого «почти получилось».

{
  "id": "F04",
  "behavior": "Пользователь получает письмо со ссылкой и меняет пароль",
  "verification": "pytest tests/e2e/test_password_reset.py",
  "state": "active"
}

Здесь есть ловушка: скрипт проверяет ровно то, что записано в поле verification. Если архитектурная проверка входит в Definition of Done, но не в команду фичи, дыра останется невидимой. Поэтому общий make check должен включать все обязательные проверки, а не только тесты.

Независимый оценщик

Самоотчёт исполнителя никогда не бывает независимым источником истины. Решение структурное — разделить роли:

  • планировщик разворачивает требование в фичи с критериями приёмки и не пишет код;
  • генератор реализует одну фичу и прогоняет тесты, но не решает, готово ли;
  • оценщик выносит вердикт по чек-листу критериев.

Оценщик не должен видеть рассуждений генератора, иначе унаследует его уверенность. Он получает только требование, дифф и результаты прогонов. В Claude Code это удобно сделать сабагентом: у него свой чистый контекст. Ещё лучше, если оценщик — другая модель: у неё другие слепые пятна. Оценка «выглядит нормально» запрещена: каждый вывод должен ссылаться на файл, строку или лог.

Сообщения об ошибках, которые агент может исправить сам

Проверка полезна, только если её вывод ведёт к исправлению. «Test failed» или голый AssertionError агенту ничего не говорят. Хорошее сообщение содержит, что сломалось, почему это правило существует и как исправить:

ERROR arch-check: app/services/refund.py импортирует app/db/session.py
WHY: сервисы не обращаются к базе напрямую, только через репозитории
FIX: используйте RefundRepository из app/repositories/refund.py

Такой формат превращает проверку из запрета в подсказку: агент чинит нарушение без вашего участия.

Ручная проверка: где она всё ещё нужна

Автоматика не отменяет человека, но сужает его роль. Ручная проверка нужна там, где критерий трудно формализовать: внешний вид интерфейса, тексты, удобство сценария. Делайте её по чек-листу из критериев приёмки, а не «потыкать». Если вы второй раз пишете агенту один и тот же комментарий, это сигнал превратить его в автоматическую проверку: тест, правило линтера или grep-гвард в make check.

Как E2E меняет поведение агента ещё до прогона

Ценность сквозного теста не только в пойманных багах. Если агент заранее знает, что его работу проверит E2E-сценарий, он пишет код иначе: думает о контракте с соседним компонентом, а не только о своей функции, обрабатывает пути ошибок и не нарушает архитектурные границы, потому что их нарушение всплывёт в прогоне. Проверка работает как ограничение на входе, а не только как фильтр на выходе.

Есть условие: E2E осмыслен, когда у системы понятные границы. На архитектуре-спагетти сквозной тест докажет лишь, что спагетти работают, и не покажет, где нарушен замысел. Поэтому сначала закрепляют слои и правила зависимостей, а потом строят сквозные сценарии. Агенты охотно копируют существующие паттерны репозитория, включая плохие, так что ограничения лучше вводить с первого дня.

Типичные ошибки

  • Просить агента «проверить внимательнее». Контекст тот же, оценка останется завышенной.
  • Считать 100% покрытие гарантией. Покрытие юнитами не покрывает стыки.
  • Рефакторить до подтверждения фичи. Порядок жёсткий: корректность → производительность → стиль.
  • Мокать в E2E то, что ломается чаще всего. Базу, миграции и конфигурацию в сквозном тесте нужно запускать по-настоящему; внешние платные API можно подменить тестовым сервером.

Итог

Когда агент написал «готово», а фича не работает, проблема почти всегда в процессе проверки, а не в модели. Посчитайте verification gap, закрепите Definition of Done в AGENTS.md, сделайте E2E обязательным, отдайте статус готовности скрипту и добавьте независимого оценщика. Эта схема подробно разобрана на практике в курсе по харнес-инжинирингу.

Частые вопросы

Можно ли заставить агента самого запускать E2E-тесты?

Да, если команда записана в AGENTS.md как обязательная часть Definition of Done и агенту доступны нужные инструменты. Надёжнее дополнительно запускать её хуком или в CI, чтобы не зависеть от памяти агента.

E2E-тесты медленные, нужно ли гонять их на каждую правку?

Нет. Линт, типы и юнит-тесты — всегда, E2E — при изменениях, затрагивающих больше одного слоя, и перед объявлением фичи готовой.

Как проверять фронтенд, который агент сделал?

Сквозным тестом через браузер (например, Playwright) с кликами по реальному интерфейсу, а внешний вид — ручной проверкой по чек-листу или скриншотами, которые просматривает оценщик.

Достаточно ли код-ревью вместо оценщика?

Ревью человеком полезно, но оно дорогое и не масштабируется на часы автономной работы. Оценщик с рубрикой снимает очевидные проблемы до человека, а человек решает спорные вопросы.

Разберите тему на видео

Эта тема подробно разобрана с практикой в курсе «Harness Engineering: инженерия надёжности AI-агентов».

Перейти к курсу