Перейти к содержанию
Публикация AiManual

Как разработчик делает игру с ИИ-агентом: правила, тесты и проверка результата на примере Age of Poleis

Автор Age of Poleis показывает, как отдал написание кода ИИ-агенту: какие правила ушли в постоянные инструкции, почему 30 зелёных тестов ничего не доказывали и

Коротко

Что будет в материале

  1. 01

    Кто что делает: код пишет ИИ-агент, разработчик придумывает и проверяет

  2. 02

    Правило, которое надоело объяснять: сгенерированные TypeScript-типы руками не правим

  3. 03

    Правило без проверки - это пожелание: шаг в скрипте проверки проекта

  4. 04

    Тридцать зелёных тестов, которые ничего не доказывали

Кто что делает: код пишет ИИ-агент, разработчик придумывает и проверяет

В Age of Poleis, стратегии про древнегреческий полис, код пишет ИИ-агент. Автор проекта придумывает механики, принимает решения и играет. Разделение выглядит простым ровно до момента приёмки очередного результата. Работа человека остаётся, только смещается: формулировка правил, проверка их соблюдения и доказательство того, что нужное поведение действительно реализовано.

Сцепленность механик здесь высокая. Строительство, ресурсы, рабочие и производство связаны: поменяешь одну механику, поедут ещё три. Стек обычный для веба: бэкенд на Django, интерфейс на React и TypeScript, между ними API. Автор разбирает свой опыт в отдельном материале, и по описанию видно: промах агента проявляется в балансе и в интерфейсе, а не в отвлечённом учебном примере.

Дальше речь о рутине, которую пришлось формализовать, чтобы код агента можно было принимать без перечитывания каждого диффа. Опор три: постоянные инструкции, автоматическая проверка сгенерированных файлов и тесты, которые падают на старом коде. Ответственность за результат никуда не делегируется, и именно эта граница вызывает сопротивление части разработчиков.

Почему в сцепленной стратегии ошибка агента стоит дорого

Локальная правка в одном месте проявляется в ресурсах, рабочих и производстве. Агент, которому разрешено «починить» симптом, ломает три соседние механики, и ловить это приходится уже в игре, а не в диффе. Цена такой ошибки растёт с каждым коммитом, потому что связи между системами не видны в одном файле.

Отсюда набор запретов в постоянных инструкциях агента. Заглушать падение нельзя пропущенными тестами, широкими утверждениями, дублированием потоков, правкой сгенерированных файлов или альтернативной базой данных. В оригинале первая строка звучит так: «Fix root causes; do not silence failures with skipped tests, broad assertions, duplicated flows, generated-file edits, or an alternate database». Смысл простой: чинить причину, а не отчёт о ней.

Для проекта с одной связанной экономикой это не бюрократия. Пропущенный тест или широкое утверждение в проверке скрывают именно те места, где ломается цепочка «ресурс, рабочий, производство». Диагностика потом стоит дороже, чем честно упавший тест сегодня.

Правило, которое надоело объяснять: сгенерированные TypeScript-типы руками не правим

Цепочка типов в проекте выстроена от контракта. Контракт описывается на бэкенде, из него получается OpenAPI-схема, из схемы генерируются TypeScript-типы, из них дальше идёт код интерфейса. Сокращённо: Django → OpenAPI → TypeScript-типы → код интерфейса.

Соблазн возникает на первом же расхождении. Тип на фронте не сходится с тем, что пришло из API, и его хочется просто поправить руками: одна строка, минута работы. При следующей генерации правка тихо исчезнет, и баг вернётся. Хуже того, вернётся он не сразу, а после следующего прогона генерации, когда причина уже забыта.

Правило автор сформулировал в трёх пунктах:

  1. Сгенерированные файлы руками не трогать.
  2. Поменялся контракт, меняй источник и перегенерируй.
  3. Повторный запуск генерации не должен ничего менять.

Третий пункт часто пропускают, а он превращает генерацию в проверяемое свойство: если после повторного запуска в репозитории появился дифф, значит кто-то правил сгенерированный файл. Дальше правило переехало в постоянные инструкции агента. Поводом стал не инцидент, а усталость: автор поймал себя на том, что объясняет это в чате не в первый раз. Правило, которое повторяешь третий раз, дешевле записать один раз, чем повторять.

Что ещё зафиксировано в постоянных инструкциях агента

Правило про типы стоит в одном списке с более общими принципами. Вот как выглядит фрагмент инструкций:

- Fix root causes; do not silence failures with skipped tests, broad assertions, duplicated flows, generated-file edits, or an alternate database.
- API-owned TypeScript types come from generated OpenAPI. Client-owned types remain local.
- API boundary: backend source change, OpenAPI/type regeneration, frontend validation.

Вторая строка разделяет владение типами. Типы, которые описывают контракт API, приходят из сгенерированного OpenAPI, их нельзя объявлять вручную. Клиентские типы, которые нужны только интерфейсу, остаются локальными: их никто не генерирует, и правки в них законны.

Третья строка задаёт порядок действий на границе API. Сначала меняется бэкенд-источник, потом регенерируются OpenAPI и типы, и только затем идёт валидация фронтенда. Обратный порядок, когда сначала правят интерфейс под желаемый ответ, приводит к той самой ручной правке типа, которая исчезнет при следующей генерации.

Это пример структуры инструкций, а не универсальный шаблон. В проекте без формального контракта, например без OpenAPI-схемы, копировать такой список бессмысленно: проверять нечего, а агент получит правила, которые невозможно проконтролировать автоматически.

Правило без проверки - это пожелание: шаг в скрипте проверки проекта

Инструкция живёт в контексте агента, а контекст имеет свойство уезжать. Поэтому в общем скрипте проверки проекта есть шаг, который генерирует схему и типы во временные файлы и сравнивает их с тем, что лежит в репозитории. Разошлось, значит проверка красная.

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

Граница у механизма тоже есть. Проверка сравнивает сгенерированное с репозиторием, то есть отвечает на вопрос «не правили ли файлы руками». Корректность самого контракта она не оценивает: если в бэкенд-источнике описано неверное поле, генерация пройдёт чисто, а баг останется. Этот класс задач закрывают тесты поведения. Похожая логика лежит в основе средового подхода к ИИ-агентам: инженер проектирует условия и приёмку по проверкам, а не полагается на аккуратность модели.

Тридцать зелёных тестов, которые ничего не доказывали

В проекте было тридцать зелёных тестов на окна приложения. Они проходили, выглядели солидно и проверяли старое поведение. Когда понадобилось менять жизненный цикл окон, выяснилось, что эти тесты не доказывают ничего про нужное состояние: они подтверждали, что прежняя реализация ведёт себя как прежде, и всё.

Дальше появился новый тест жизненного цикла окна. Он падал на старом коде, и только после переделки стал проходить. Именно падение на старой версии превращает тест в доказательство: он показывает, что проверяемое поведение раньше отсутствовало, а теперь реализовано.

Принцип формулируется жёстко. Тест сначала должен упасть по правильной причине. Зелёный тест, написанный после переделки, легко подгоняется под то, что получилось: в нём фиксируется фактическое поведение кода, а не требуемое.

Как выглядит тест, который падает на старом коде

Порядок действий практический и короткий. Сначала формулируется ожидаемое поведение жизненного цикла окна: что должно происходить при открытии, что при закрытии, какое состояние сохраняется. Потом пишется тест на это поведение, и запуск на текущем коде должен дать красный результат. Только после этого агенту отдаётся задача на переделку, а критерий приёмки уже зафиксирован.

Фреймворк тестирования в описанном кейсе не указан, и это не мешает перенести подход: важна последовательность «красный тест, затем реализация», а не инструмент. Тридцать старых тестов при этом не обязательно выбрасывать. Часть из них можно переписать так, чтобы они проверяли поведение, которое проекту нужно сейчас, а не то, что исторически сложилось.

Скриншот от агента - не доказательство: зачем в отчёте писать, что не проверялось

Скриншот фиксирует один кадр в один момент. Он показывает, что окно отрисовалось, и молчит о том, что происходит при повторном открытии, при ошибке API и при закрытии с несохранёнными данными. Поэтому скриншот от агента сам по себе не служит доказательством нужного состояния, и в каждом отчёте указывается, что не проверялось.

Формулировка отчёта меняется по смыслу. Вместо «покажи, что получилось» задача звучит как «перечисли, что осталось вне проверки». На выходе человек получает не подтверждение успеха, а список конкретных мест для ручной проверки, и это полезнее любого количества удачных кадров.

Логика совпадает с принципом unknown ≠ OK из методологии Agent-Ops 0.4.0: непроверенный факт остаётся неизвестным, а не превращается в «работает» по умолчанию. В связке с предыдущими разделами получается четыре уровня: инструкции задают правила, проверка сгенерированных файлов ловит расхождение с контрактом, тесты доказывают поведение, отчёт обозначает границу доказанного.

Тесты не ловят скучную механику: зачем разработчик играет в свою игру

Тест подтверждает, что механика работает, и ничего не говорит о том, интересна ли она. Зелёный прогон спокойно пропускает петлю, в которой игрок выполняет формально корректные действия и не понимает, зачем продолжать. Такое не выражается ни в одном утверждении.

Поэтому автор играет в Age of Poleis. Пример из практики: запуск ради одной мелочи в онбординге, чтобы проверить, передаёт ли он смысл продолжать разведку, заканчивается исследованием всего острова в поисках артефакта для старта. Наблюдение в игре показывает не баг, а место, где теряется цель, и подсказывает, что стоит усиливать.

Граница честная и её важно обозначить. Личные впечатления одного разработчика не заменяют прохождений других игроков. Автор знает проект насквозь и всё равно залипает в собственной игре, но его опыт не равен опыту человека, который видит игру впервые. Выводы из собственной игры остаются гипотезами для следующей итерации, а не финальным вердиктом о механике.

Что в этом подходе работает, а что остаётся на человеке

ПрактикаЧто даётГде граница
Постоянные инструкции агентаСнимают повторяющиеся объяснения в чатеНе мешают агенту нарушить правило
Проверка сгенерированных типовДелает правило про Typescript проверяемымНе судит о корректности контракта
Тест, падающий на старом кодеДоказывает изменение поведенияНе оценивает удобство механики
Отчёт с разделом «что не проверялось»Задаёт границу доказанногоТребует ручной проверки перечня
Игра в собственный проектПоказывает, где теряется цельНе заменяет прохождений других игроков

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

Опыт ограничен одним проектом на стеке Django, React и TypeScript. Перенос на другой стек потребует адаптации, а не копирования: в проектах без генерации кода из контракта шаг сравнения сгенерированных файлов просто не появится, а в проектах без окон не будет и теста жизненного цикла.

Ещё одна опора, которая окупается в таких проектах, это явные границы задачи. Постановка с перечнем того, чего в системе не будет, и обоснованием по каждому пункту превращает договорённости с агентом в проверяемые утверждения, как в кейсе системы управления корпоративной архитектурой. Для Age of Poleis это означает список механик, которые в игре не появятся, с причиной отказа.

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

Подписаться на канал