Loop Engineering на практике: автономный цикл «оркестратор, мейкер, чекер»

Разбираем рабочий демо-стенд loop engineering: внешний цикл находит задачу в GitHub Issues, изолирует работу в Git worktree, делегирует реализацию мейкеру и независимую проверку чекеру и доводит задачу до pull request – с валидацией по правилам и памятью в state/loop-state.md.

Loop Engineering на практике: автономный цикл «оркестратор, мейкер, чекер»

В отдельной статье мы разбирали loop engineering как подход: внутренний и внешний циклы, паттерн maker-checker, Git worktrees, память и критерии успеха. Теперь перейдём от теории к практике и соберём небольшой, но рабочий стенд, где внешний цикл сам управляет агентами.

Это демо метода loop engineering, которую популяризировал Addy Osmani, – с моими собственными изменениями и дополнениями. Система находит задачи в GitHub Issues, изолирует их в worktree, делегирует реализацию и независимую проверку разным агентам и действует через внешние инструменты. Мейкер и чекер – это субагенты; их разделение и обеспечивает независимую проверку.

Процесс намеренно простой, но, как показала практика, эффективный. Задачи для агентов хранятся в GitHub Issues – это встроенный в GitHub таск-трекер. Агент-оркестратор загружает задачу через MCP, создаёт изолированный Git worktree и делегирует работу субагентам. Дальше всё происходит без ручного промптинга.

Как устроен процесс

Логика внешнего цикла укладывается в одну схему. Оркестратор берёт задачу, изолирует работу, передаёт её мейкеру, отправляет результат на проверку чекеру и по итогу проверки либо создаёт pull request, либо фиксирует неудачу в файле памяти. Третьего не дано: проверка либо пройдена, либо нет.

загрузка задачи через MCP

реализует задачу
и проверяет себя

запускает скрипт-валидатор

да (PASS)

нет (FAIL)

Задача в GitHub Issues

Агент-оркестратор

Создать Git worktree

Субагент-мейкер

Возврат оркестратору

Субагент-чекер

Проверка пройдена?

Создать pull request
Закрыть задачу
Обновить state/loop-state.md
Удалить worktree

Записать причину в state/loop-state.md
Удалить ветку и worktree
PR не создавать
Статус задачи не менять

Разберём этот процесс по шагам.

Требования

Для запуска понадобятся:

  • Oh My Pi (omp) – обвязка для запуска агента. Установка: npm install -g @ohmy-pi/cli. Подойдёт и другой агент (OpenCode, Pi, Claude Code) – принципиальной разницы для этого подхода нет.
  • Node.js >= 18 и npm.
  • Git с поддержкой worktree.
  • Персональный токен доступа GitHub (personal access token) со скоупами repo и issues – только для боевого режима.

Режимы запуска

Стенд работает в двух режимах.

Демо-режим (без учётных данных GitHub)

DEMO_MODE=true ./scripts/loop-demo.sh

Задачи берутся из fixtures/demo-issues.json. Всё состояние и имитация действий с PR и задачами пишутся в state/loop-state.md. Сетевых вызовов нет.

Боевой режим (нужен токен GitHub)

GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx ./scripts/loop-demo.sh

Система находит открытые задачи с меткой loop-demo, обрабатывает по одной за запуск, создаёт pull request и закрывает задачу.

Что происходит за один цикл

Один проход цикла состоит из пяти шагов.

  1. Discover (найти). Находит первую открытую задачу с меткой loop-demo – в fixtures/demo-issues.json (демо-режим) или через GitHub API (боевой режим).
  2. Isolate (изолировать). Создаёт Git worktree с веткой fix/<issue-key>.
  3. Delegate (делегировать мейкеру). Запускает субагента-мейкера: тот читает задачу, реализует исправление в worktree и прогоняет npm test и npm run build.
  4. Verify (проверить чекером). Запускает субагента-чекера: тот выполняет scripts/validate-fix.sh – скрипт автоматической проверки по правилам, который сверяет тесты, сборку, корректность реализации и критерий приёмки задачи.
  5. Act (действовать). Если проверка пройдена – создаётся PR и закрывается задача (боевой режим) либо записывается имитация действия (демо-режим). Если проверка не пройдена – фиксируется причина отказа и выполняется очистка.

Структура проекта

В качестве агента я использую Oh My Pi. Конвенции проекта описаны в .omp/AGENTS.md: как собирать проект, какие команды запускать, каких ограничений придерживаться. Вот как выглядит репозиторий целиком.

.omp/
  AGENTS.md                  – конвенции проекта для всех агентов
  mcp.json                   – MCP-сервер: GitHub (issues + PR)
  mcp.json.template          – шаблон для подстановки токена
  agents/maker.md            – определение субагента-мейкера
  agents/checker.md          – определение субагента-чекера
  skills/loop-demo/SKILL.md  – логика оркестратора + общие конвенции
demo-app/
  src/calculator.ts          – цель: багованный add() [a - b вместо a + b]
  tests/calculator.test.ts   – падающий тест: add(2, 3) === 5
  package.json, tsconfig.json, .gitignore, package-lock.json
scripts/
  loop-demo.sh               – точка входа (окружение, проверки, запуск omp)
  validate-fix.sh            – скрипт автоматической проверки по правилам (PASS/FAIL)
fixtures/
  demo-issues.json           – заготовки задач для DEMO_MODE=true
state/
  loop-state.md              – здесь оркестратор фиксирует прогресс
// [ERROR fetch failed] eugeneproai/le-demo/main/.omp/AGENTS.md
// [ERROR fetch failed] eugeneproai/le-demo/main/.omp/mcp.json

Субагенты: мейкер и чекер

Роли строго разделены. Мейкер (.omp/agents/maker.md) получает задачу от оркестратора, реализует её в worktree и проверяет себя, запуская npm test и npm run build, после чего возвращает результат оркестратору. Он не создаёт pull request, не обновляет задачу в GitHub, не запускает скрипт валидации и не трогает файлы, не связанные с задачей.

// [ERROR fetch failed] eugeneproai/le-demo/main/.omp/agents/maker.md

Чекер (.omp/agents/checker.md) решает не менее простую задачу: получает входные данные от оркестратора, запускает скрипт-валидатор с нужными параметрами и возвращает результат. Вариантов ровно два – PASS или FAIL, то есть проверка пройдена или нет. Если пройдена – оркестратор создаёт pull request и меняет статус задачи. Если FAIL – pull request не создаётся, статус задачи не меняется, а в state/loop-state.md появляется пометка, что задача не прошла валидацию.

// [ERROR fetch failed] eugeneproai/le-demo/main/.omp/agents/checker.md

Почему в центре оркестратор

Цикл управляется оркестратором: один агент прогоняет весь цикл, порождая субагентов для мейкера и чекера. Мейкер и чекер никогда не видят работу друг друга – они запускаются как независимые субагенты, и между ними только оркестратор. Именно поэтому проверка получается по-настоящему независимой.

Оркестратор → найти задачу → создать worktree
    ├─ запустить Мейкера (реализует исправление, гоняет тесты)
    └─ ждать
    ├─ запустить Чекера (независимо запускает скрипт валидации)
    └─ ждать
    ├─ PASS → создать PR, закрыть задачу, обновить state
    └─ FAIL → записать причину, удалить ветку, обновить state

Приложение для демонстрации

Приложение максимально простое – калькулятор. Цель – файл demo-app/src/calculator.ts, где функция сложения сразу реализована с ошибкой: вместо сложения чисел выполняется вычитание (a - b вместо a + b).

// [ERROR fetch failed] eugeneproai/le-demo/main/demo-app/src/calculator.ts

К приложению есть один готовый тест demo-app/tests/calculator.test.ts, который проверяет операцию сложения: add(2, 3) === 5. Из-за бага он падает – багованная функция возвращает -1 вместо 5.

// [ERROR fetch failed] eugeneproai/le-demo/main/demo-app/tests/calculator.test.ts

Отдельно лежит файл fixtures/demo-issues.json – на случай, если цикл запускается в демо-режиме, без GitHub Issues и MCP. Тогда агент берёт задачи прямо из этого файла.

// [ERROR fetch failed] eugeneproai/le-demo/main/fixtures/demo-issues.json

Скрипт валидации

Самое интересное – скрипт валидации scripts/validate-fix.sh, который запускает чекер. Он выполняет несколько шагов по порядку.

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

Допустим, в задаче указано, что критерий приёмки – это пройденные тесты и определённый результат конкретной функции. Но в самом скрипте критерий – это результат интерпретации всей задачи с помощью LLM. Например, для операции сложения скрипт ищет в нужном файле именно операцию сложения, то есть a + b. Если такая операция найдена – критерий приёмки считается выполненным.

// [ERROR fetch failed] eugeneproai/le-demo/main/scripts/validate-fix.sh

Если все шаги завершились успешно, валидатор возвращает PASS. Если проверка не пройдена на любом из шагов – FAIL. Этот скрипт – единственный источник правды о правилах валидации: чекер не выдумывает правила, а запускает скрипт и передаёт его вывод дословно.

⚠️ Проверка критерия приёмки через LLM удобна для демо, но это не гарантия корректности. Автоматическая проверка по правилам и явные тесты остаются обязательными – финальную ответственность за результат всё равно несёт разработчик.

Запуск внешнего цикла

Запуск оформлен bash-скриптом scripts/loop-demo.sh. Строк в нём много, но все они нужны, чтобы перед стартом основного агента пройти предварительные проверки: доступ к MCP, наличие нужных файлов и так далее.

// [ERROR fetch failed] eugeneproai/le-demo/main/scripts/loop-demo.sh

Сам агент запускается в неинтерактивном режиме, без подтверждения действий от пользователя. Пользовательский промпт короткий: использовать скилл loop-demo для запуска полного цикла, получить задачу, делегировать работу мейкеру и чекеру и действовать по результатам так, как описано в скилле. Сам скилл .omp/skills/loop-demo/SKILL.md содержит логику оркестратора, ссылку на конвенцию проекта, информацию о репозитории и все те шаги, что описаны выше.

// [ERROR fetch failed] eugeneproai/le-demo/main/.omp/skills/loop-demo/SKILL.md

Что получилось после запуска

После старта поднимается оркестратор, и мы ждём выполнения. По итогам первого прогона создаётся state/loop-state.md – файл памяти. В нём три раздела: выполненные задачи, задачи с ошибками и задачи, над которыми идёт работа прямо сейчас. Файл обновляется по мере реализации задач. На первой итерации агент исправляет функцию сложения.

Обновляем страницу в GitHub – создался pull request с этой задачей. Смотрим коммит: всё исправлено, вместо операции вычитания теперь сложение, все условия соблюдены. Открытой осталась только вторая задача – реализовать деление чисел.

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

Запускаем цикл ещё раз, для второй задачи. Она тоже выполняется: файл calculator.ts обновлён, файл с тестами обновлён, чекер вернул PASS, проверки пройдены. Критерий приёмки – деление a / b – реализован, все нужные файлы обновлены, статус задачи изменён. Причём агент добавил все возможные тесты – с этой частью он справился даже аккуратнее человека.

Смотрим Issues – открытых задач нет, две закрытые. У каждой есть комментарий, коммит и pull request, задача закрыта.

Проверка результата

Чтобы убедиться, что стенд собран правильно и цикл отработал, есть несколько проверок.

Предварительная проверка

cd demo-app && npm test    # тест не пройдет: add(2, 3) возвращает -1 вместо 5
cat .omp/mcp.json           # должен содержать запись github MCP
ls .omp/AGENTS.md .omp/agents/maker.md .omp/agents/checker.md .omp/skills/loop-demo/SKILL.md scripts/validate-fix.sh

Сквозная проверка (демо-режим)

DEMO_MODE=true ./scripts/loop-demo.sh
less state/loop-state.md     # DEMO-1 должен появиться в разделе ## Completed
ls ../le-demo-2-wt-DEMO-1/   # каталога быть не должно (worktree удалён)

Сквозная проверка (боевой режим)

  1. Создайте в репозитории задачу с заголовком «Fix add() returning subtraction result» и меткой loop-demo.
  2. Запустите баш-скрипт GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx ./scripts/loop-demo.sh.
  3. Проверьте: задача закрыта, есть pull request из ветки fix/<issue-numbering> в main.

Как сбросить состояние

rm -rf ../le-demo-2-wt-*
git branch -D fix/DEMO-1 2>/dev/null || true
git worktree prune
rm -f state/loop-state.md

Вывод

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

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

Поделиться статьей:

Читайте также