Я попробовал заменить разработчиков ИИ-агентами

Четыре ИИ-субагента нашли ошибку, которую пропустил одиночный агент. После успешной оплаты заказ всё ещё можно было оплатить повторно. В скрытом наборе тестов одиночный запуск набрал 7/8, а агентная команда получила 8/8. При этом обе версии успешно прошли production build и свои тесты. Значит ли это, что команду разработчиков уже можно заменить?
Что именно я проверял
Разработчику всё чаще предлагают не одного ИИ-помощника, а группу исполнителей. OpenAI описывает Codex как приложение для работы с несколькими агентами на полном цикле разработки. Я решил проверить, даст ли команда субагентов результат лучше одиночной работы над тем же инцидентом.
У меня уже был локальный монорепозиторий с React и TypeScript, двумя сервисами на Java 21 и Spring Boot, PostgreSQL, Redis Streams и mock-провайдером. Реальных карт и внешних платёжных API в проекте не было.
Я сделал две копии одного коммита. В обеих использовались модель Koda Pro на основе GLM 5.3, режим «Очень высокий» и внешний набор скрытых тестов для независимой проверки результата.

В контроле делегирование было запрещено. Во втором запуске основной агент получил четыре read-only-роли, а менять файлы мог только он.
Первая попытка контрольного запуска не засчиталась. Хотя четыре проектные роли были выключены, основной агент сам делегировал две исследовательские задачи обычным read-only-субагентам. Я остановил запуск до изменения файлов и повторил его с прямым запретом на делегирование.
Как выглядела команда
Я разделил не файлы, а области неопределённости. И всем рекомендую так делать.
frontend-tracerпрослеживал timeout, повтор запроса и жизненный цикл Idempotency-Key;payment-backendразбирался со Spring Boot, Spring JDBC, транзакциями и ограничениями PostgreSQL;webhook-workerисследовал подписи, Redis Streams, повторную доставку и fulfillment;adversarial-qaпроектировал конкурентные и негативные сценарии.
Роли были настроены в Kodacode и получили только инструменты чтения. Документация позволяет задать специализацию и ограничения каждой роли. Исследование распараллелено, но ответственность за единый патч остаётся у основного агента.

Четыре роли до запуска. У каждой своя специализация и только два инструмента чтения.
Инцидент, который нельзя закрыть одним if
В исходном проекте клиент отправлял запрос на оплату, а mock-провайдер создавал платеж и только потом отвечал. Для медленного тестового токена ответ занимал больше 700 мс, поэтому API получал read timeout. Клиент видел 504 и, конечно же, повторял запрос. А что ему еще остается делать?
А дальше по цепочке.
Frontend создавал новый Idempotency-Key внутри функции каждой попытки.
Spring Boot API принимал ключ, но игнорировал его и записывал новый
UUID.До провайдера ключ вообще не доходил, поэтому повтор создавал новый платёж.
Провайдер доставлял одно событие дважды, что является нормальным поведением для at-least-once-доставки.
Worker не дедуплицировал
event_id, не оборачивал изменения в транзакцию и повторно создавал fulfillment.Новый checkout не проверял, что заказ уже оплачен.
В результате повтор запроса после таймаута превращался в новую логическую попытку оплаты, а повторная доставка события в повторное исполнение заказа. Обычный happy path при этом выглядел исправно.
Обоим режимам я отправил одно задание.
В локальном checkout произошёл платёжный инцидент. Иногда запрос к провайдеру завершается таймаутом уже после фактического списания, клиент повторяет оформление заказа, а провайдер может доставить один webhook несколько раз. В отдельных случаях заказ исполняется повторно.
Исследуй весь репозиторий, найди причинную цепочку и реализуй production-grade исправление. Решение должно выдерживать повторы и конкурентные запросы, не создавать повторный бизнес-эффект и при этом сохранять корректные легитимные повторные попытки оплаты. Учитывай границы доверия. Данные клиента и внешние события нельзя считать заведомо корректными.
Можешь менять код и тесты. Не меняй публичные HTTP-контракты без необходимости. Перед завершением выполни полный
./gradlew test,./gradlew build, frontendtests/buildи доступные smoke-тесты. В финале перечисли найденные причины, изменённые инварианты и результаты проверок.
В командном режиме перед ним были добавлены четыре упоминания @agent:.... В контрольном запуске действовал запрет делегирования. Во время реализации я не вмешивался.
Что субагенты увидели лучше
frontend-tracer показал, что повтор запроса создавал новый ключ, API заменял его ещё одним UUID, а провайдер не получал заголовок. Одна логическая попытка должна использовать один ключ на всех транспортных повторах.
webhook-worker описал проблему как M повторов checkout × N доставок webhook. Дедупликации только в Redis недостаточно. Последняя гарантия нужна в PostgreSQL, на event_id, payment_id и order_id.
payment-backend нашёл и побочные дефекты. In-memory lastSeenId приводил к переобработке после рестарта, а отложенные webhook приходили после reset.
adversarial-qa потребовал проверить разные ключи для одного заказа и новый checkout уже оплаченного заказа. Второй сценарий впоследствии провалил одиночный агент.

В коде рекомендации превратились не только в проверки Java, но и в инварианты БД. Ниже показан фактический фрагмент командного патча.
CREATE UNIQUE INDEX IF NOT EXISTS payments_request_key_uidx
ON payments (request_key);
CREATE UNIQUE INDEX IF NOT EXISTS payments_active_order_uidx
ON payments (order_id)
WHERE status IN ('processing', 'unknown');
CREATE UNIQUE INDEX IF NOT EXISTS webhook_events_event_id_uidx
ON webhook_events (event_id);
CREATE UNIQUE INDEX IF NOT EXISTS fulfillments_order_uidx
ON fulfillments (order_id);Основной агент также добавил HMAC-проверку webhook по сырому телу, транзакционную обработку события, consumer group с XACK, reconciliation зависших платежей и стабильный ключ на фронтенд. Получился большой патч из 19 изменённых или новых файлов против 15 у одиночного агента.
Независимая проверка
Самоотчёт бездушной машины агента я не считал результатом. После каждого запуска я отдельно выполнял чистую сборку, считал JUnit XML, запускал фронтенд и внешний evaluator из другого каталога.
Evaluator содержал восемь HTTP-сценариев. В него входили 20 конкурентных запросов с одним ключом, повтор запроса после таймаута, повтор webhook, повторная оплата заказа, новая попытка после отказа, один ключ для разных заказов, подмена суммы и неверная подпись.

Одиночный агент завершил план и сообщил об успешных проверках. Внешний evaluator позже нашёл один незакрытый сценарий.
Проверка | Один агент | Основной агент + 4 субагента |
|---|---|---|
Скрытые HTTP-сценарии | 7/8 | 8/8 |
./gradlew clean test build | успешно | успешно |
Backend tests | 31/31 | 16/16 |
Frontend tests и build | 6/6, успешно | 7/7, успешно |
Расширенный smoke | 10 проверок | 11 проверок |
Изменённых и новых файлов | 15 | 19 |
Вмешательства человека в патч | 0 | 0 |
Интересно то, что число тестов здесь несопоставимо. Одиночный вариант добавил больше интеграционных классов, а командный больше живых скриптов.
Единственный провал одиночного агента проявился после оплаты. Второй запрос с новым ключом возвращал 200. Повторного списания не было, но бизнес-контракт оказался нарушен. Новый checkout paid-заказа должен быть отклонён. adversarial-qa сформулировал это заранее, и командный вариант вернул 409.

Внутренний отчёт Kodacode был зелёным. Внешняя проверка затем независимо подтвердила 8/8.
Время и стоимость я специально не стал сравнивать. А все потому, что время, показанное в интерфейсе, не отражало продолжительность работы, ведь между сессиями были паузы (да, я отходил пить кофе), один запуск достиг лимита обращений и продолжился в новом сообщении, а командный агент дополнительно тратил время на поиск доступного Docker runtime. Так что судить о скорости команды было бы не совсем честно, согласитесь.
Так удалось заменить людей или нет
Если под командой понимать несколько независимых исследовательских функций, то частично да. Четыре роли быстрее разложили инцидент по слоям, сформулировали проверяемые инварианты и дали ведущему агенту более широкий обзор. В общем затащил в этом кейсе дополнительный QA-взгляд. Получилось 8/8 вместо 7/8.
Но четыре субагента не стали четырьмя автономными разработчиками. Выдыхаем. Они не владели релизом (чести много), не согласовывали изменения между собой и не отвечали за финальную сборку. После нескольких минут параллельного исследования основной агент ещё долго сводил выводы, менял архитектуру, исправлял несовместимости Spring Data Redis, переписывал smoke-тест и искал рабочую инфраструктуру. Узким местом стала интеграция.
Человек тоже никуда не исчез. Мне пришлось обеспечить честный контроль, заметить непредвиденное делегирование, держать evaluator вне репозитория, не принять внутренний зелёный отчёт на веру и отдельно воспроизвести все восемь сценариев. Да, код я не правил, но без человеческой постановки и независимой приёмки эксперимент был бы просто демонстрацией интерфейса.
Но давайте будем честны. Из одного запуска нельзя делать статистический вывод о превосходстве субагентов. Для этого нужны серии прогонов, фиксированный бюджет и нормальная телеметрия. Но как инженерный кейс результат даёт несколько практических правил.
Делите работу по независимым областям риска, а не по папкам репозитория.
Просите субагентов возвращать инварианты, контрпримеры и ссылки на код, а не четыре конкурирующих патча.
Выделяйте атакующую роль отдельно. Её задача не подтвердить план, а найти способ его сломать.
Оставляйте одного владельца изменений и единый чек-лист приёмки.
Всегда проверяйте результат тестами, которых агенты не видели.
Не считайте число ролей числом заменённых сотрудников.
По итогам эксперимента агентная команда уже способна взять на себя параллельное исследование, аудит разных слоёв и генерацию негативных сценариев. Но она не заменяет командную ответственность. Один разработчик действительно может работать шире. Просто его главной работой становится не набор кода, а постановка задач, интеграция и право сказать финальное «го в прод».
И да, использовал я Kodacode. Скачать для IDE, CLI и Desktop.
Анонсы обновлений, практические разборы и новости продукта в нашем Telegram‑канале.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.