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

—

от автора

Агент пишет: “Готово, все тесты проходят”. Дальше у меня два пути: поверить или показать тому, кто разбирается. Показать некому. Разработчика в проекте нет, а сам я код не читаю.

Я продакт. С января я так делаю расширение для Chrome. Весь код написали два ИИ-агента, Claude Code и Codex. Размер работы на 28 октября:

  • 593 коммита;

  • 78 478 строк кода в самом продукте;

  • 177 511 строк тестов;

  • версия 1.0.1 в Chrome Web Store.

Тесты писал тот же агент, что и код, поэтому их число само по себе ничего не доказывает. Почему я им верю, а где не верю, — урок 3.

За эти месяцы я стал требовать от агента больше, чем сообщение “готово”:

  • проверки, которые падают, если правило нарушено;

  • отчет о том, что агент сам увидел в браузере;

  • процесс, в котором я веду задачу по шагам: от обсуждения до приемки.

Модели за это время тоже поумнели. Насколько результат улучшился благодаря им, а насколько благодаря обвязке, я не измерял. Дальше расскажу, как устроил проверки и работу с агентом:

  1. Обвязка. Правила, проверки, запреты и скиллы вокруг агента. Девять уроков с датами из истории проекта.

  2. Как я с ней работаю. Шесть шагов от идеи до приемки и промпты, которые я вставляю агенту на каждом шаге.

  3. Что я изменю в следующем проекте. Пять улучшений. В каждом есть довод против.

  4. Вопросы к вам.

Статья для двух читателей:

  • Менеджер или продакт хочет строить сам, не вставая в очередь к разработчикам.

  • Разработчик хочет отдать агенту задачу на несколько часов, доверяя результату.

Если вы менеджер, программировать не придется. Окно для ввода команд (CLI), которым пользуются программисты, тоже не понадобится. Все, что описано ниже, делается обычным текстом в чате с агентом: вы пишете, что нужно, а агент сам запускает программы и показывает результат.

Обвязку я выложил открытым шаблоном — набором файлов, который любой может взять себе: https://github.com/yesgenius/agent-harness. В нем есть все, о чем пойдет речь в первой части, и демонстрация. Она ломает копию проекта сорока двумя способами и показывает, какая проверка поймала каждую поломку. На моем компьютере она идет меньше минуты.

Шаблон целиком на английском, и это держит сторож: строка с буквами не латинского алфавита роняет тесты. С вами агент при этом говорит на том языке, на котором вы ему пишете. Как читать его страницы по-русски прямо на GitHub, сказано в разделе “Как попробовать шаблон”.

На чем это проверено

Обвязка выросла на одном продукте — расширении Chrome под названием Chat with Multiple AI. Оно отправляет один запрос сразу в 15 чатов с ИИ: ChatGPT, Claude, Gemini и другие.

Эти чаты — чужие сайты, и они меняются без предупреждения. Поэтому описание того, как работать с каждым чатом, лежит в отдельном файле настройки. Когда чат меняется, я исправляю этот файл, и исправление доходит до пользователей без новой версии в сторе.

Что уже было:

  • две версии в Chrome Web Store;

  • два исправления этого файла, 19 октября и 1 ноября;

  • 70 сценариев приемки, которые агент сам прошел в браузере перед версией 1.0.1.

Слова, которые встретятся

Термины собрал под спойлером. Если они вам знакомы, пропускайте таблицу.

Словарь

Слово

Что это значит

Агент

ИИ, который не только отвечает, но и сам работает в папке вашего проекта: читает и меняет файлы, запускает программы

Ветка

Отдельная линия истории. Разработка идет в одной ветке, версия для выпуска живет в другой

Дифф

Перечень измененных строк: было и стало

Зеленая, упала

Зеленая проверка прошла. Упавшая нашла нарушение и остановила работу

Коммит

Одна запись в истории: что изменилось, кто и когда это сделал

Консоль

Служебная панель браузера, где видны логи

Контекст

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

Конфигурация

Файл настроек отдельно от кода. Мое расширение скачивает его и читает, как работать с каждым чатом

Лог

Журнал, куда программа пишет, что она делает

Модель

ИИ внутри агента

Наряд (work order)

Задание агенту на работу: что должно получиться и как это проверить

Обвязка (harness)

Все, что окружает агента в проекте: AGENTS.md, проверки, запреты, скиллы

Провайдер

Внешний сервис, на который опирается продукт. У меня это чаты с ИИ

Промпт

Текст задания для агента

Пуш

Отправка новых коммитов на GitHub

Расширение

Дополнение к браузеру Chrome

Ревью

Проверка чужой работы. Ревьюер — тот, кто проверяет

Репозиторий

Папка проекта вместе с этой историей

Сборка

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

Сессия

Один чат с агентом. Новая сессия не помнит предыдущий чат

Сжатие контекста

Когда контекст заполнен, агент кратко пересказывает содержание чата и продолжает работу с этим пересказом. Место снова есть, но подробности чата теряются

Скилл

Файл с указаниями для агента на одно повторяемое действие, например коммит

Слайс

Тонкий кусок новой функции, который проходит через весь продукт и дает видимый результат

Спецификация

Документ в проекте, где записано, как продукт должен работать

Сторож (guard)

Тест, который защищает правило проекта: обещание пользователю или порядок работы. Имя сторожа — само правило, записанное фразой

Тест

Маленькая программа, которая запускает кусок продукта и сравнивает результат с ожидаемым

AGENTS.md

Файл с правилами проекта для агента. Его читают и Claude Code, и Codex

Chrome Web Store, стор

Магазин расширений Chrome

Claude Code, Codex

Два таких агента, от Anthropic и OpenAI. У обоих есть приложение для компьютера с обычным чатом

Git

Программа, которая хранит всю историю изменений проекта

GitHub

Сайт, где лежит копия репозитория

Часть 1. Обвязка

Кто что проверяет

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

Вот как у меня поделена проверка:

Кто проверяет

Что проверяет

Сборка, тесты и сторожа

Правила, которые записаны в коде и документах, и то, что код с документами не разошелся

Агент в браузере

Что продукт делает на самом деле

Независимый ревьюер

Дифф, который писал не он

Я

Видимое поведение по списку из отчета. И решаю, что строить

Код я не читаю. Открываю расширение и проверяю по списку из отчета: что происходит после нажатия, появился ли результат, что будет, если что-то пойдет не так. Если проверку можно поручить машине, поручаю. Но решать, устраивает ли меня поведение продукта, все равно приходится самому.

Часть правил записана только в AGENTS.md, и агент может их нарушить. Как я с этим работаю, расскажу во второй части.

1. AGENTS.md не останавливает агента

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

  • общие принципы вроде KISS (“делай проще”) и DRY (“не повторяй одно и то же дважды”);

  • список технологий;

  • схема папок.

В марте добавилось правило “никаких магических чисел”. Магическое число — это число в коде, смысл которого нигде не назван: например, 3600 вместо “один час”. Файл рос: 61 строка в феврале, 179 в октябре.

Агент мог нарушить любое из этих правил, и без проверки я бы об этом не узнал. Первый сторож появился только 11 мая.

Покажу на примере, почему одного текста мало. Мой продукт обещает: если я исправил файл настроек, расширение проверяет его не реже раза в час. Как часто расширение перечитывает этот файл, задает одно число в коде.

Допустим, агент поменял час на сутки. Тест взял новое число из того же кода, сравнил его с самим собой и остался доволен. Я смотрю: все зеленое. Пользователь ждет сутки.

По зеленому тесту кажется, что частота под контролем. Хотя предел “не реже раза в час” в нем вообще не задан.

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

test('the configuration is read at least once an hour', () => {  assert.ok(PROVIDER_CONFIG_REFRESH_MS > 0);  assert.ok(PROVIDER_CONFIG_REFRESH_MS <= ONE_HOUR_MS);});

Как это читать:

  • в кавычках название сторожа: “конфигурация читается минимум раз в час”;

  • PROVIDER_CONFIG_REFRESH_MS — то самое число: как часто перечитывать файл настроек, в миллисекундах;

  • ONE_HOUR_MS — один час в миллисекундах;

  • assert.ok значит “должно быть верно”: число больше нуля и не больше часа.

Если агент поставит сутки, второе требование не выполнится, и сторож упадет.

Сторож держит ровно то, что записано числом, — частоту. Скачивает ли программа файл на самом деле, он не проверяет. Пример взят из шаблона, а в шаблоне нет участка кода, который перечитывает файл, при этом сторож держит само обещание пользователю “пора перечитать”.

Имя файла сторожа — само обещание, записанное обычной фразой. Поэтому список файлов читается как список обещаний продукта:

a-published-provider-fix-reaches-a-user-within-an-hourno-reminder-leaves-before-the-intent-is-recordedno-provider-name-outlives-the-provider-config

Другими словами:

  1. Опубликованное исправление для провайдера доходит до пользователя в течение часа.

  2. Напоминание не уходит, пока не записано, что его собираются отправить. Без этой записи продукт после перезапуска не узнает, что уже отправил, и отправит второй раз.

  3. Название провайдера встречается только в файле настроек: ни в коде, ни в тестах, ни в документах его нет. Сервис меняется без предупреждения, и строка с его названием вне файла настроек продолжала бы описывать то, чего уже нет.

Сейчас у меня 89 сторожей. Больше всего сторожей я добавил в августе и сентябре:

Месяц

Новых сторожей

Май

2

Июль

9

Август

33

Сентябрь

40

Октябрь

5

Что попросить у агента в своем проекте:

Обещание продукта: <опишите словами пользователя>. Найди, что в коде его держит.Напиши тест-сторож этого обещания, назови файл так, чтобы имя читалось как обещание, и покажи,что сторож падает, если обещание нарушить.

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

2. Две копии правил обязательно разойдутся

Claude Code и Codex читали правила из разных файлов. Поэтому с февраля у меня было два файла правил, CLAUDE.md для Claude Code и AGENTS.md для Codex, и они должны были совпадать полностью. Каждое правило приходилось записывать дважды. Вроде ничего сложного: поправил один файл, поправь второй. Но расхождения копились:

Когда

Строк расходится

Март

9

Июнь

12

10 августа

21 (в файлах 102 и 85 строк)

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

25 сентября я оставил один файл. Теперь сторож падает, если кто-то заводит:

  • CLAUDE.md рядом с AGENTS.md;

  • отдельный файл правил в подпапке;

  • файл под запасным именем, который агент тоже прочитал бы как правила.

Скиллы у каждого агента лежат в своей папке, поэтому каждый скилл записан дважды. Сторож сверяет каждую пару и падает, если тексты разошлись.

С другими копиями правил я теперь поступаю так же:

  • держать правило в одном месте;

  • поручить машине сравнивать копии и падать, если они разошлись.

3. Зеленая сборка может не проверять ничего

У меня есть программа, которая ищет в коде ошибки типов. Ошибка типа — это когда код путает виды данных, например обращается с текстом как с числом. 27 июля счетчик таких ошибок дошел до нуля.

Ноль можно получить двумя способами:

  • исправить все ошибки;

  • перестать их искать.

Снаружи результат одинаковый. А перестать искать легко, хватит одной строки:

  • выключить строгий режим проверки, и она начнет прощать больше;

  • добавить папку, в которую проверка не заглядывает.

Сборка при этом остается зеленой. 27 июля я записал в истории проекта: нулю ошибок можно доверять, пока проверка ищет их в прежнем объеме и с прежней строгостью.

Против этого у меня два приема.

Минимальный порог. Проверка считает, сколько файлов она прочитала, и падает, если их меньше порога. Иначе сбой можно не заметить: проверка не нашла ни одного файла, не нашла ошибок и отчиталась об успехе.

Намеренная поломка. В копии проекта правило ломают специально, и сторож обязан упасть. Если он остался зеленым, значит, такое нарушение он пропускает. Разработчики знают этот прием как мутационное тестирование: в код вносят маленькую ошибку и смотрят, заметят ли ее тесты. В шаблоне каждая такая ошибка нарушает одно правило проекта, и для каждой заранее записано, какой сторож должен ее поймать.

Зачем это нужно, проще всего показать на пожарном датчике. Датчик с севшей батарейкой молчит точно так же, как исправный в доме, где нет дыма. Поэтому у датчика есть кнопка “Тест”: нажали — он должен запищать. Намеренная поломка — такая кнопка для каждого сторожа.

В демонстрации шаблона 42 намеренные поломки. Как ее запустить, описано в разделе “Как попробовать шаблон”. Вот три примера из вывода:

caught    The barrier looks in place but no longer refuses          the-push-barrier-guards-the-declared-release-branch.test.jscaught    The check stopped seeing files and turned green because of it          console-access-single-sink.test.jscaught    Two lines swapped: the send now runs before the record          no-reminder-leaves-before-the-intent-is-recorded.test.jsViolations: 42, caught: 42

В каждой паре строк сверху описание поломки, снизу файл сторожа, который ее поймал; caught значит “поймано”:

  1. Защита есть, но ничего не запрещает. Демонстрация поменяла в защите один ответ: вместо “нельзя” она теперь отвечает “можно”. Поймал сторож, который пробует отправить изменения в ветку выпуска и ждет отказа.

  2. Проверка не видит файлов и поэтому зеленая. Демонстрация направила проверку в пустую папку. Поймал минимальный порог.

  3. Две строки поменяли местами. Продукт стал отправлять напоминание раньше, чем записывать, что собирается его отправить. Поймал сторож “напоминание не уходит, пока это не записано”.

Что попросить у агента в своем проекте или для развернутой обвязки:

Для каждой проверки сборки сломай ее правило в копии проекта и покажи, что проверка упала.Проверку, которая не упала, считаем отсутствующей.

Агент вносит поломки в копию проекта.

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

4. Защиту проверяют попыткой ее обойти

Защита — это проверка, которая не просто предупреждает о нарушении, а не дает действию совершиться.

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

В спецификации было записано, что принудительный пуш запрещает правило в настройках репозитория на GitHub. Я попросил агента проверить, и он выяснил:

  • правило можно создать, и в настройках оно выглядит включенным;

  • на моем тарифе для закрытого репозитория GitHub его не исполняет.

Ограничение тарифа описано в документации GitHub. Но настройка выглядела рабочей, и я поверил ей, а не проверил отказом.

Я рассчитывал на защиту, которая не работала. 31 августа я удалил это правило и записал причину в истории проекта.

Теперь ветку выпуска защищает хук. Хук — это маленькая программа, которую git сам запускает перед определенным действием. Если хук отвечает “нельзя”, действие отменяется. Мой хук запускается перед каждой отправкой на GitHub и лежит в папке проекта, а не в настройках сайта:

  • отправку в ветку выпуска он отклоняет;

  • пропускает ее, только если в команде есть явное подтверждение, что отправка намеренная;

  • подтверждение действует на одну команду.

Хук можно обойти командой git push --no-verify, а подтверждение отправки ставит сам отправитель. Эти ограничения записаны в самом хуке. Если агент случайно повторит прошлую команду и забудет поменять ветку, он получит отказ. Если решит обойти защиту, хук ему не помешает. Подробнее об этом в разделе “Чего обвязка не делает”.

Читать код хука не нужно. Его проверяет сторож: пытается отправить изменения в ветку выпуска без подтверждения или с неверным подтверждением и падает, если отправка прошла.

Теперь я прошу показать отказ:

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

В ответе вы должны увидеть само сообщение об отказе, а не рассказ агента о том, что защита есть.

5. Агент должен говорить с вами языком поведения

До 26 июля AGENTS.md не говорил, как агент должен общаться со мной в чате. Потом в нем появился раздел Verification model — “Модель проверки”: кто и как проверяет работу агента. Вот выдержка в переводе:

Вопросы пользователю нужно формулировать через поведение системы: что происходит, когда и что видно пользователю. В такой формулировке технический вопрос вполне уместен, и на него можно ответить; вопрос, для ответа на который нужно читать diff, — нет. Неясности, касающиеся исключительно внутренней реализации, нужно разрешать по документации в docs/specs/ и исходному коду либо предлагать варианты, описывая их через последствия, видимые пользователю.

Если изменение затрагивает наблюдаемое поведение, завершайте работу кратким описанием ручной проверки: что открыть, что сделать, как должен выглядеть правильный результат и какой именно фильтр логов ([ModuleName], [<transition-id>]) использовать для сбора логов, если результат отличается от ожидаемого.

Создание коммита — действие пользователя. Оставляйте готовые изменения в рабочем дереве, сообщайте, что изменилось и как это было проверено, и создавайте коммит только по явной просьбе.

Оригинал на английском
Questions to the user must be framed in the language of system behavior - what happens,when, and what is visible. Framed that way a technical question is fair game and gets ananswer; a question that can only be answered by reading the diff is not. A purely internalunknown is resolved from docs/specs/ and the source, or offered as options described bytheir user-visible consequences.When a change alters observable behavior, end with a short manual check: what to open,what to do, what the correct result looks like, and the exact log filter([ModuleName], [<transition-id>]) to capture if it does not.Committing is the user's action. Leave finished work in the working tree, report whatchanged and how it was validated, and commit only when explicitly asked.

Разница на условном примере. Вопрос без этого правила: “Перенести обработку отправки в фоновый скрипт?” Вопрос по правилу: “Сейчас, если закрыть вкладку во время отправки, отправка обрывается. Сделать, чтобы она доходила до конца?” На первый я ответить не могу. На второй отвечаю сразу, потому что это вопрос о продукте.

6. Агент должен видеть то, что сделал

Раньше агент мог прочитать код и убедительно объяснить, что все работает. Мне этого стало мало. С 22 августа у него есть свой Chrome: пусть откроет расширение, нажмет кнопки и посмотрит. Если до браузера не дошел, в отчете должно быть прямо написано: вывод сделан по коду.

Он подключается к браузеру через Chrome DevTools MCP — инструмент, который дает агенту управлять браузером: открывать страницы, нажимать кнопки, читать консоль.

Со сквозным прогоном я тянул дольше. Сквозной прогон, или end-to-end-тест, — это проход по продукту от начала до конца, как у пользователя. До 7 сентября от него были только строка в списке отложенных дел и команда-заглушка, которая намеренно падала. Теперь это протокол — пошаговое описание проверки:

  • минимум 70 сценариев;

  • 40 страниц-образцов, которые изображают внешние сервисы и лежат в самом проекте;

  • агент проходит все сам в настоящем Chrome.

Перед выпуском 1.0.1 прогон занял около двух часов: 69 сценариев в норме, 1 отклонение. Сценарии агент прошел без меня. Моя часть — прочитать отчет и решить, что делать с отклонением. Отклонение настоящее: при почти полном хранилище, 17 674 шаблона, измененный шаблон появлялся в другом окне расширения через 1,8 секунды, а норма — одна. 12 октября я решил выпускать: замер шел на очень слабом ноутбуке, и его числа не переносятся на машины пользователей. Прогон нашел нарушение нормы, а решение принял я, и в отчете о выпуске оно записано как отклонение со ссылкой на это решение. Сам протокол (end-to-end-тест) тоже проверяется: если у сценария нет страницы-образца или правила, которое он проверяет, тест падает.

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

[Dispatch] [reminder.intent-recorded] fixture-reminder[Dispatch] [reminder.send-started] fixture-reminder[Dispatch] [reminder.send-accepted] fixture-reminder[Dispatch] [reminder.delivery-unproven] fixture-reminder

[Dispatch] — часть программы, которая пишет в лог, здесь отправка. Во вторых скобках — шаг процесса. По шагам выходит такая история:

  1. Записано, что напоминание собираются отправить.

  2. Отправка началась.

  3. Провайдер принял запрос.

  4. Доказать, что напоминание дошло, не удалось.

Итоговый статус здесь “не подтверждено”. Продукт не говорит, что отправил, потому что доказать этого не может.

7. Самый дорогой процесс описывается схемой

Почти у каждого продукта есть действие, которое нельзя отменить: списание денег, письмо, удаление. У меня это отправка промпта в чужой чат. Такой процесс нельзя оставлять только в коде: его нужно проверять, а код я проверить не могу.

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

С июня отправка описана строгой схемой процесса: 15 состояний и 69 переходов. Разработчики называют такую схему конечным автоматом.

  • Состояние — где процесс находится сейчас, например “записано, что напоминание отправляется”.

  • Переход — шаг из одного состояния в другое.

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

  • каждая строка лога начинается с имени перехода, точь-в-точь как в схеме;

  • сборка сверяет имена: каждый переход схемы есть в коде, а в коде нет имен, которых нет в схеме;

  • описание процесса для людей сборка сама создает из схемы.

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

Так выглядит кусок тестовой схемы из шаблона:

{ "id": "intent-recorded",  "meaning": "Намерение записано надежно, после перезапуска оно будет видно",  "durable": true },{ "id": "reminder.send-started", "from": "intent-recorded", "to": "sending",  "when": "Запрос отправляется", "irreversible": true }

Первая запись описывает состояние: durable: true значит, что запись должна пережить перезапуск программы. Вторая описывает переход из состояния from в состояние to; irreversible: true значит, что шаг нельзя отменить.

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

Схему сборка шаблона проверяет по одному правилу: шаг, который нельзя отменить, начинается только из состояния, которое переживет перезапуск. Иначе программа перезапустится, не узнает, что уже отправила, и отправит второй раз. Поставьте в схеме такой шаг раньше записи, и сборка упадет. Поставьте его раньше записи в коде, и упадет сторож “напоминание не уходит, пока не записано, что его собираются отправить” (урок 3, третья поломка).

У отправки семь конечных статусов. Среди них “Sent, not confirmed — check the chat”, то есть “Отправлено, не подтверждено, проверьте чат”. От агента я тоже требую указывать в отчете, что ему не удалось проверить.

Что попросить у агента в своем проекте:

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

В таблице можно заметить пропущенный случай, даже если вы не читаете код.

8. Повторяемое действие становится скиллом

Скилл (skill) — это файл с указаниями для агента на одно повторяемое действие: какие шаги сделать, что проверить, когда остановиться. Claude Code и Codex понимают один формат: папка с файлом SKILL.md. Обычно агент сам решает, когда подгрузить скилл, по его описанию. Скилл можно и вызвать по имени: в Claude Code вы пишете в чат /commit, в Codex — $commit, а все шаги агент делает сам.

В феврале я завел первые два скилла: commit записывает работу в историю, push отправляет ее на GitHub. Тогда это было для удобства.

31 августа в скиллах, которые делают коммит, нашлась ошибка: скилл сам задавал автора коммита, а коммитер (тот, кто записал коммит) брался из настроек компьютера. Они могли не совпасть. Ошибку исправили во всех четырех скиллах с коммитом. Имя и почта автора теперь объявлены в проекте в одном месте, а сторож падает, если скилл снова назовет автора сам.

9. Автор не проверяет сам себя

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

Готовый шаблон я отдал на ревью Codex, который его не писал. Он прочитал файлы и нашел 16 проблем, все подтвердились.

Сборка и 61 тест при этом были зелеными. После исправлений сторож по очереди ломает в копии проекта каждый шаг сборки и требует, чтобы сборка упала.

Машина не может проверить, что ревью действительно состоялось. В описании шаблона обвязка разбита на 15 слоев, у каждого указано, как проверяется соблюдение его правил. Для трех слоев автоматических проверок нет, только текст правил:

  • агент задает вопросы языком поведения;

  • каждый дифф смотрит независимый ревьюер;

  • агент ничего не отправляет за пределы компьютера без моего слова.

Кроме того, машина проверяет только результат. Прочитал ли агент спецификацию перед работой, она не знает.

Чего обвязка не делает

  • Чужие сайты. Страницы-образцы лежат у меня в проекте. Сами по себе они не меняются. А настоящий чат за это время может переделать поле ввода или кнопку отправки. Проверки на образцах проходят, расширение у пользователя уже не работает. Именно так случилось 1 ноября. Тогда я поправил файл настройки, который расширение скачивает само, и обошелся без новой версии в сторе.

  • Качество кода. Я вижу поведение и то, что ловят проверки. Хорош ли код внутри, я оценить не могу.

  • Безопасность и устройство продукта. Секреты в коммите шаблон ищет. Уязвимости — дыры, через которые продукт можно взломать, — он не ищет. Устройство продукта в целом он тоже не оценивает.

  • Намеренное отключение проверок. Агент может ослабить проверку, если решит это сделать. AGENTS.md это запрещает, и намеренные поломки ловят многие такие правки, но не все. Обвязка защищает от ошибок агента, а не от его решения обойти защиту.

  • Агент может переписать сторожа. Сторож защищает правило, например “напоминание уходит не чаще раза в час”. Если агент заменит в стороже “час” на “сутки”, сторож снова будет зеленым: теперь он защищает другое правило. Ни одна проверка этого не заметит, заметить можете только вы. Поэтому в отчете по наряду есть раздел “Измененные сторожа” (в шаблоне — Changed guards). Список файлов в нем берется из git, а не со слов агента. Для каждого файла агент пишет, какое правило теперь проверяется иначе и почему. Если агент ни одного сторожа не менял, в разделе одно слово: “нет” (none). Читайте отчет с этого раздела, еще до того как сами проверите результат в продукте.

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

  • Решения. Что строить и зачем, по-прежнему решаю я.

Как попробовать шаблон

Пошаговое руководство лежит в шаблоне, вот оно: https://github.com/yesgenius/agent-harness/blob/main/docs/GETTING-STARTED.md

По нему можно подготовить компьютер, запустить демонстрацию поломок и перенести обвязку в свой проект. Понадобятся компьютер с Windows или macOS и подписка на Claude или на ChatGPT. Компьютер подготавливает сам агент по одному промпту.

Руководство на английском. Чтобы читать его по-русски, щелкните правой кнопкой мыши по странице GitHub и выберите “Перевести на русский”. Если такого пункта нет, откройте в Chrome адрес chrome://settings/languages и в разделе “Google Переводчик” включите “Использовать Google Переводчик”. Промпты в руководстве переводчик не трогает: они остаются на английском, так их и вставляйте. Агент все равно будет говорить с вами по-русски: промпты велят ему отвечать на языке ваших собственных сообщений, а пока вы ничего не написали — на языке интерфейса компьютера.

Часть 2. Как я работаю с обвязкой сейчас

Обвязка держит то, что записано проверкой. То, что записано только текстом, агент все равно нарушает. Особенно в длинной сессии: правила из AGENTS.md на 179 строк теряются, когда контекст забит работой.

Постепенно я пришел к шести шагам работы с агентом. На каждом вставляю свой промпт, в том числе повторяю правила из AGENTS.md. Ниже промпты, которыми я пользуюсь.

Обходится это примерно в $60 в месяц: две подписки Claude Pro для Claude Code и одна ChatGPT Plus для Codex. Токены — единицы, которыми считается объем работы модели, — я не считаю: мне этих подписок хватает.

Шаг 1. Обсуждаю следующий слайс

Работу я режу на слайсы. Первый слайс — ходячий скелет: самая простая версия, в которой все части уже соединены и работают вместе, пусть и умеют минимум или ничего :). Следующие слайсы наращивают его.

Каждый слайс я сначала обсуждаю с агентом на уровне усилий xhigh. Это настройка, при которой модель дольше думает перед ответом. Обсуждаю, пока предложенное решение меня не устроит.

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

Шаг 2. Агент пишет наряд

Когда решение меня устраивает, в той же сессии прошу составить наряд. Промпт длинный и написан под мой проект, поэтому целиком он под спойлером. Главное в нем: наряд — полный и единственный источник задания для исполнителя; факты о текущем коде проверяются по коду; решение покрыто тестами; спецификации правятся в той же работе; проверяет агент сам в браузере, а я руками — только то, что он проверить не смог; коммит делаю я.

Промпт наряда целиком
Профессионально составь наряд агенту-кодеру в docs/work-orders/ с полным контекстом, как единственный источник правды на русском.Обязательно учти при формировании наряда:1 - проверяй все факты, которые касаются ASIS;2 - решение должно быть покрыто ред-тестами, если они уместны;3 - решение должно быть покрыто юнит-тестами;4 - при реализации соблюдать принципы: KISS, YAGNI, DRY, Fail Fast, Stdlib-only, Forward-Only, SRP, No Magic Values;5 - соблюдать гигиену:- Долгоживущие имена формулировать по поведению, инварианту, failure mode, vendor-like state или модулю;- Не переносить имена вендоров, временных документов, планов, отчетов, нарядов, номеров этапов, issue labels, debug labels и их сокращения в production-код, тесты, комментарии, JSDoc, identifiers, string literals, log prefixes, describe/it names, test IDs, storage keys, event names, config keys, fixture/mock names, `docs/specs/**` и постоянные `testserver/` files.6 - устранить дрейф спецификаций, включая docs/specs/E2E-REGRESSION.md;7 - наряд должен максимально использовать функционал инструмента Chrome DevTools MCP;8 - если требуется, то используй тестовые фикстуры сервера и страниц вендоров с заданным поведением по аналогии с /testserver;9 - продакт выполняет тест руками только для тех случаев, когда Chrome DevTools MCP + /testserver не смог проверить;10 - коммит выполняет продакт;

Промпт содержит специфику проекта, адаптируйте его под ваш случай.

docs/work-orders/ — папка нарядов. Наряд хранится в git, чтобы было видно, что поручали. Для исполнителя на время работы он единственный источник задания, отсюда слова в промпте. Как продукт работает сейчас, по-прежнему говорят спецификации, а наряд описывает, что в них станет иначе: все, что он меняет в продукте, он вносит и в них (пункт 6). Бывает, что наряд в каком-то месте противоречит спецификации, но не говорит, что это место меняется. Тогда агент не решает сам, чему верить: он останавливается и спрашивает меня. Отчет агента по наряду ложится в docs/temp/, в git его нет.

Что означают пункты промпта
  1. AS-IS (в промпте — ASIS) — как устроено сейчас. Все, что наряд говорит о текущем коде, агент проверяет по коду, а не пишет по памяти — это требование не дает агенту срезать угол и выдумать то, чего нет на самом деле.

  2. Красный тест (в промпте — ред-тест) пишут до исправления. Сначала он падает на ошибке, после исправления проходит. Так видно, что тест ловит именно эту ошибку.

  3. Юнит-тест проверяет одну маленькую часть кода отдельно от остальных.

  4. Принципы:

    • KISS — делай проще;

    • YAGNI — не делай впрок;

    • DRY — не повторяй одно и то же дважды;

    • Fail Fast — при ошибке остановись сразу и громко, а не продолжай молча;

    • Stdlib-only — без сторонних библиотек, где хватает встроенных средств;

    • Forward-Only — без обратной совместимости: старое не тащим, если оно мешает;

    • SRP — один модуль, одна задача;

    • No Magic Values — у каждого числа и строки в коде есть имя.

  5. Гигиена имен. Долгоживущее имя называет поведение или правило. В код, в тесты и спецификации не попадают названия чатов, временных документов, нарядов, номера этапов и служебные метки. Через месяц такое слово ничего не значит.

  6. Дрейф спецификаций — когда код делает одно, а спецификация описывает другое. Все, что я знаю о новой функции, рождается на шаге 1, в обсуждении с агентом. Поэтому спецификации правит тот же наряд, что и код, в том числе протокол сквозного прогона. После наряда я обязательно читаю дифф спецификаций (шаг 6). Изменения спецификаций — это зона особого контроля, измененные строки всегда удобно подсвечиваются в среде разработки.

  7. Chrome DevTools MCP — агент проверяет сам, в браузере.

  8. Страницы-образцы. Если нужно, агент делает новые страницы на тестовом сервере проекта, которые ведут себя как чаты в нужной ситуации.

  9. Руками я проверяю только то, что агент не смог проверить в браузере.

  10. Коммит — мое решение.

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

Шаг 3. Вытаскиваю вопросы о продукте

Наряд получается на 1000-3000 строк. К этому моменту контекст сессии забит, и агент начинает путаться. Поэтому сначала я вытаскиваю из него все, что требует моего решения:

Если есть продуктовые вопросы, то задай вопросы продакту и предложи варианты языком поведения системы, первым поставь рекомендованный и объясни почему: 1. Задавай по одному понятному вопросу за раз, предварительно опиши контекст простым языком. 2. Дожидайся моего ответа, прежде чем двигаться дальше. 3. Если вопрос закрыт - профессионально зафиксируй результат. 4. Продолжай, пока не будут сняты все вопросы.

Потом наряд читаю сам. В начале разбираюсь, что изменится для пользователя, дальше просматриваю. Задаю агенту свои вопросы по наряду.

Целиком наряд я не читаю, и никто другой его тоже не читает. Поэтому ошибка в технической части вполне может пройти мимо меня. Рассчитываю, что она вылезет на проверках, при приемке продукта или в диффе спецификаций. Если не вылезет — пропущу. Этот риск я принимаю.

Шаг 4. Ревью наряда

Два раза прошу:

Действуй как независимый ревьюер:- сделай финальное профессиональное ревью наряда перед его выполнением;

Я пробовал отдавать наряд двум агентам в новых сессиях. Потом сравнивал их замечания с тем, что находил агент в текущей. Для наряда текущая сессия оказалась полезнее. Цифр у меня нет, это впечатление от работы. Возможно, помогает то, что в ней еще остались решения из обсуждения. А новому агенту весь этот контекст приходится собирать по файлу.

Дифф кода — другое дело: его смотрит ревьюер, который код не писал (часть 1, раздел 9). Слова “независимый ревьюер” в промпте — роль, а не факт: наряд перечитывает та же сессия, что его написала, то есть это повторная проверка автором.

Шаг 5. Выполнение в новой сессии

Напиши промпт агенту-кодеру для выполнения наряда с командой /goal.Верни в формате для копирования.

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

Промпт я копирую в новую сессию. Она начинает с чистым контекстом, и все, что ей нужно знать, есть в наряде.

Агент возвращает готовую команду /goal: ссылку на наряд и условие, при котором работа считается законченной. Без такого условия агент может остановиться на полпути и написать “готово”. С ним он работает, пока не случится одно из трех:

  • он вывел отчет, где каждый пункт наряда подтвержден проверкой;

  • он задал мне вопрос, без ответа на который дальше нельзя, и остановился;

  • закончился отведенный запас ходов.

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

Кто решает, что работа закончена, зависит от агента. Это уже решение их разработчиков. В Claude Code это отдельная модель: после каждого хода она читает чат, а файлы не открывает. В Codex условие проверяет сам агент, который работал, то есть стороннего судьи там нет, и эту роль у меня играет приемка (шаг 6). Команда одна для обоих агентов, поэтому условие рассчитано на более строгий случай: отчет нужно вывести прямо в чат, а не сообщить, что он лежит в файле. Текст цели не может быть длиннее 4000 символов, поэтому все подробное лежит в наряде, а команда только ссылается на него. Образец такой команды есть в шаблоне, в файле docs/WORK-ORDER-TEMPLATE.md. Там условие требует вывести и второй раздел отчета — “Измененные сторожа”, Changed guards (о нем — в разделе “Чего обвязка не делает”).

Шаг 6. Приемка

Агент написал, что все готово. Я уже знаю, что спросить дальше: что осталось недоделанным и что он не проверял. Сам он почему-то не всегда начинает с этого. Поэтому даже после хорошего отчета отправляю ему эти промпты:

Продакт понимает язык поведения системы.Для принятия решения перечисли продакту:- все незакрытые ДЕФЕКТЫ;- все попутно найденные РЕАЛЬНЫЕ ДЕФЕКТЫ;- все НЕВЫПОЛНЕННЫЕ ПРОВЕРКИ, которые могли быть выполнены Chrome DevTool MCP;
Если есть продуктовые вопросы, то задай вопросы продакту и предложи варианты языком поведения системы, первым поставь рекомендованный и объясни почему: 1. Задавай по одному понятному вопросу за раз, предварительно опиши контекст простым языком. 2. Дожидайся моего ответа, прежде чем двигаться дальше. 3. Если вопрос закрыт - профессионально зафиксируй результат. 4. Продолжай, пока не будут сняты все вопросы.

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

Потом выполняю ручную проверку из отчета: что открыть, что сделать, как выглядит правильно. Если результат не тот, то отправляю логи агенту вместе со скриншотом. Когда все принято, пишу агенту “сделай коммит”.

Часть 3. Что я изменю в следующем проекте

В следующем проекте я хочу внести пять изменений. Первые два касаются порядка отправки и проверки самих тестов (уроки 7 и 3). Еще три должны сократить ручную работу с промптами. Пока ни одно из них я на практике не проверил.

  1. Схема начнет сама останавливать неверный шаг. Сейчас схема отправки — это описание. Сборка сверяет, что имена шагов в коде и в схеме совпадают, но не проверяет, в каком порядке код эти шаги проходит (урок 7). В следующем проекте код не сможет перейти к следующему шагу сам: он будет спрашивать одну функцию, а та сверится со схемой. Если из текущего состояния такого перехода в схеме нет, шаг не выполнится, например: отправка не начнется, пока не записано, что она начинается. Строку в лог тоже пишет эта функция, поэтому лог и схема не разойдутся. В схеме останутся только крупные шаги. Мелкие действия внутри шага — открыть вкладку, вставить текст, нажать кнопку — останутся обычным кодом: если расписать в схеме и их, схему уже не прочитать как процесс. Против: так ловится только неверный порядок шагов. Ошибку внутри шага схема не поймает: например, когда два действия идут одновременно и мешают друг другу или чужая страница ведет себя не так, как ждали. А в истории продукта есть исправления отправки именно такого рода. В продукте такой функции нет, поэтому ее пользу я пока не видел.

  2. Каждый сторож получит намеренную поломку, и тесты дорогого процесса — тоже. В продукте остальным тестам я верю на слово (урок 3). В шаблоне реестр поломок уже есть, но сторож без поломки в нем допустим: в его строке просто стоит прочерк. В следующем проекте проверка упадет на стороже без поломки. Исключение одно, и оно записано: сторож, который сам проверяет поломки. Поломки получат и тесты дорогого процесса: переход не из того состояния, необратимый шаг до записи. Я хочу проверить попыткой нарушения каждое правило, на которое опираюсь при приемке. Против: каждая поломка — это копия проекта и лишний прогон сторожа, поэтому тесты идут дольше; а прочие тесты продукта так и останутся на слово.

  3. Приемка станет частью отчета, а не отдельным промптом. Незакрытые дефекты, найденные попутно дефекты и невыполненные проверки станут обязательными разделами отчета, пустой раздел — словом “нет”. Раздел “Измененные сторожа” (Changed guards) в шаблоне наряда уже так устроен. Против: условие /goal проверит, что раздел есть, но не то, что в нем правда; а отдельный вопрос после “готово” заставляет агента оглянуться на сделанное.

  4. Промпты станут скиллами, а правила из них — сторожами. Шесть промптов я вставляю руками в каждый слайс, а половина промпта наряда повторяет AGENTS.md. Правило, записанное дважды, со временем разойдется с копией. Против: сторож ловит нарушение после работы, а правило в промпте предупреждает заранее, и это дешевле.

  5. AGENTS.md станет короче, а правила — ближе к шагу, где они нужны. В проекте этот файл занимает 179 строк, у шаблона — 130. Правило, нужное на одном шаге, переедет в скилл этого шага, а в AGENTS.md останется то, что нужно в любой задаче. Против: причину правила объясняет только AGENTS.md, а скилл дает само правило. Не зная причины, агент нарушит правило в случае, о котором скилл не говорит.

Часть 4. Вопросы

  1. Что в приведенной схеме работы вы считаете самообманом или избыточным?

  2. Если вы не читаете код, который пишет агент, что именно вы принимаете, когда говорите “принято”?

  3. Модели становятся умнее, и некоторые элементы обвязки можно убрать. Как вы определяете, что можно убрать?

Шаблон: https://github.com/yesgenius/agent-harness

Расширение, на котором все это выросло: https://chromewebstore.google.com/detail/pcpciffaliaooacnhiefjiegigjidehp

ссылка на оригинал статьи https://habr.com/ru/articles/1103924/