Loop Engineering на практике: автономный цикл «оркестратор, мейкер, чекер»
Разбираем рабочий демо-стенд loop engineering: внешний цикл находит задачу в GitHub Issues, изолирует работу в Git worktree, делегирует реализацию мейкеру и независимую проверку чекеру и доводит задачу до pull request – с валидацией по правилам и памятью в state/loop-state.md.
В отдельной статье мы разбирали loop engineering как подход: внутренний и внешний циклы, паттерн maker-checker, Git worktrees, память и критерии успеха. Теперь перейдём от теории к практике и соберём небольшой, но рабочий стенд, где внешний цикл сам управляет агентами.
Это демо метода loop engineering, которую популяризировал Addy Osmani, – с моими собственными изменениями и дополнениями. Система находит задачи в GitHub Issues, изолирует их в worktree, делегирует реализацию и независимую проверку разным агентам и действует через внешние инструменты. Мейкер и чекер – это субагенты; их разделение и обеспечивает независимую проверку.
Процесс намеренно простой, но, как показала практика, эффективный. Задачи для агентов хранятся в GitHub Issues – это встроенный в GitHub таск-трекер. Агент-оркестратор загружает задачу через MCP, создаёт изолированный Git worktree и делегирует работу субагентам. Дальше всё происходит без ручного промптинга.
Как устроен процесс
Логика внешнего цикла укладывается в одну схему. Оркестратор берёт задачу, изолирует работу, передаёт её мейкеру, отправляет результат на проверку чекеру и по итогу проверки либо создаёт pull request, либо фиксирует неудачу в файле памяти. Третьего не дано: проверка либо пройдена, либо нет.
Разберём этот процесс по шагам.
Требования
Для запуска понадобятся:
- 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 и закрывает задачу.
Что происходит за один цикл
Один проход цикла состоит из пяти шагов.
- Discover (найти). Находит первую открытую задачу с меткой
loop-demo– вfixtures/demo-issues.json(демо-режим) или через GitHub API (боевой режим). - Isolate (изолировать). Создаёт Git worktree с веткой
fix/<issue-key>. - Delegate (делегировать мейкеру). Запускает субагента-мейкера: тот читает задачу, реализует исправление в worktree и прогоняет
npm testиnpm run build. - Verify (проверить чекером). Запускает субагента-чекера: тот выполняет
scripts/validate-fix.sh– скрипт автоматической проверки по правилам, который сверяет тесты, сборку, корректность реализации и критерий приёмки задачи. - 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 – здесь оркестратор фиксирует прогресс
# Loop Engineering Demo — Project Conventions
These conventions apply to every agent (maker, checker, orchestrator) in the loop.
## Project root
`demo-app/` (relative to the repo root).
## Package manager
`npm`. All package operations use `npm install`, `npm test`, `npm run build`.
## Test command
```bash
cd demo-app && npm test
```
Uses Vitest. Always run with `npm test -- --run` for a single run.
## Lint/typecheck command
```bash
cd demo-app && npm run build
```
Runs `tsc --noEmit`.
## Branch naming
`fix/<issue-number>` — e.g. `fix/1` for GitHub issue #1.
## PR title format
`[<issue-number>] <issue title>` — e.g. `[1] Fix add() returning subtraction result`.
## State file location
`state/loop-state.md` — the orchestrator reads and writes the loop's progress.
## Validation script
`scripts/validate-fix.sh` — the checker runs this to determine PASS/FAIL. It tests:
1. `npm test` passes
2. `npm run build` passes
3. The acceptance criterion from the issue is satisfied
## Maker requirements
Before reporting completion, the maker MUST run both:
- `npm test` (with `--run`)
- `npm run build`
Report the results of both commands verbatim.
## Checker rules
The checker MUST NOT invent rules beyond those in `scripts/validate-fix.sh`. Run the script and report its output verbatim.
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
}
}
}
}
Субагенты: мейкер и чекер
Роли строго разделены. Мейкер (.omp/agents/maker.md) получает задачу от оркестратора, реализует её в worktree и проверяет себя, запуская npm test и npm run build, после чего возвращает результат оркестратору. Он не создаёт pull request, не обновляет задачу в GitHub, не запускает скрипт валидации и не трогает файлы, не связанные с задачей.
---
name: loop-maker
description: Implements a GitHub issue in the demo-app project
# model: sonnet-4.6
---
You are the maker in a loop-engineering system. You implement issues.
Read `.omp/AGENTS.md` for project conventions.
You receive:
- Issue key (e.g. `DEMO-1`)
- Issue summary
- Issue description (including the bug)
- Acceptance criterion
- Worktree path: `../le-demo-2-wt-<issue-key>/demo-app`
Your job:
1. Change to the worktree's demo-app directory.
2. Read `src/calculator.ts` and understand the issue description.
3. Implement the issue.
4. Run `npm test -- --run` and `npm run build`.
5. Report what you changed, the test results, and which validation rules from `scripts/validate-fix.sh` your fix satisfies.
Do NOT:
- Create a PR
- Update the GitHub issue
- Run the validation script
- Modify unrelated files
Report the following in your output:
```
CHANGED: <what file and what change>
TEST_RESULT: <stdout of npm test>
BUILD_RESULT: <stdout of npm run build>
RULES_SATISFIED: <list of rules your fix satisfies>
```
Чекер (.omp/agents/checker.md) решает не менее простую задачу: получает входные данные от оркестратора, запускает скрипт-валидатор с нужными параметрами и возвращает результат. Вариантов ровно два – PASS или FAIL, то есть проверка пройдена или нет. Если пройдена – оркестратор создаёт pull request и меняет статус задачи. Если FAIL – pull request не создаётся, статус задачи не меняется, а в state/loop-state.md появляется пометка, что задача не прошла валидацию.
---
name: loop-checker
description: Runs the validation script and checks work against defined rules
# model: gpt-5.5
---
You are the checker in a loop-engineering system. You did NOT write the fix. Your job is to run the deterministic validation script and report the result.
You receive:
- Issue key
- Worktree path
- Acceptance criterion
Steps:
1. Change directory to the worktree root (not demo-app, the root of the worktree containing both `demo-app/` and `scripts/`).
2. Run:
```
scripts/validate-fix.sh "$(pwd)" <issue-key> "<acceptance-criterion>"
```
3. Capture the script's stdout and exit code.
4. Report the output verbatim.
Do NOT:
- Invent additional rules beyond those in `scripts/validate-fix.sh`
- Modify any files
- Run any tests on your own
Output format:
- If exit code is 0 and output contains `VALIDATION_RESULT: PASS`, return:
```
VERDICT: PASS
<full validator output>
```
- Otherwise, return:
```
VERDICT: FAIL
<full validator output>
```
Почему в центре оркестратор
Цикл управляется оркестратором: один агент прогоняет весь цикл, порождая субагентов для мейкера и чекера. Мейкер и чекер никогда не видят работу друг друга – они запускаются как независимые субагенты, и между ними только оркестратор. Именно поэтому проверка получается по-настоящему независимой.
Оркестратор → найти задачу → создать worktree
├─ запустить Мейкера (реализует исправление, гоняет тесты)
└─ ждать
├─ запустить Чекера (независимо запускает скрипт валидации)
└─ ждать
├─ PASS → создать PR, закрыть задачу, обновить state
└─ FAIL → записать причину, удалить ветку, обновить state
Приложение для демонстрации
Приложение максимально простое – калькулятор. Цель – файл demo-app/src/calculator.ts, где функция сложения сразу реализована с ошибкой: вместо сложения чисел выполняется вычитание (a - b вместо a + b).
export function add(a: number, b: number): number {
return a - b; // deliberate bug: should be a + b
}
К приложению есть один готовый тест demo-app/tests/calculator.test.ts, который проверяет операцию сложения: add(2, 3) === 5. Из-за бага он падает – багованная функция возвращает -1 вместо 5.
import { describe, it, expect } from "vitest";
import { add } from "../src/calculator.js";
describe("calculator", () => {
it("add(2, 3) should equal 5", () => {
expect(add(2, 3)).toBe(5);
});
});
Отдельно лежит файл fixtures/demo-issues.json – на случай, если цикл запускается в демо-режиме, без GitHub Issues и MCP. Тогда агент берёт задачи прямо из этого файла.
[
{
"key": "DEMO-1",
"summary": "Fix add() returning subtraction result",
"description": "The add() function in demo-app/src/calculator.ts returns a - b instead of a + b. Acceptance criterion: npm test passes and add(2,3) === 5.",
"acceptance_criterion": "a + b",
"status": "To Do",
"labels": ["loop-demo"]
}
]
Скрипт валидации
Самое интересное – скрипт валидации scripts/validate-fix.sh, который запускает чекер. Он выполняет несколько шагов по порядку.
Сначала запускаются тесты. Затем – сборка проекта. И, наконец, самый неочевидный шаг: проверяется, что критерий приёмки действительно реализован в коде. Причём это не только тот критерий, который явно указан в задаче.
Допустим, в задаче указано, что критерий приёмки – это пройденные тесты и определённый результат конкретной функции. Но в самом скрипте критерий – это результат интерпретации всей задачи с помощью LLM. Например, для операции сложения скрипт ищет в нужном файле именно операцию сложения, то есть a + b. Если такая операция найдена – критерий приёмки считается выполненным.
#!/usr/bin/env bash
set -euo pipefail
# Usage: scripts/validate-fix.sh <worktree-dir> <issue-key> <acceptance-criterion>
WORKTREE="$(cd "$1" && pwd)" # resolve to absolute path before cd
ISSUE_KEY="$2"
CRITERION="$3"
cd "$WORKTREE/demo-app"
FAILURES=()
# Rule 1: npm test passes
if ! npm test -- --run 2>/dev/null; then
FAILURES+=("TEST_FAILURE: npm test exited non-zero")
fi
# Rule 2: npm run build passes
if ! npm run build 2>/dev/null; then
FAILURES+=("BUILD_FAILURE: npm run build exited non-zero")
fi
# Rule 3: acceptance criterion is met (sourced from the issue)
if [ -n "$CRITERION" ] && [ "$CRITERION" != "none" ]; then
CRITERION_FOUND=false
# Try git diff against main in the worktree (handles linked worktrees)
if git -C "$WORKTREE" diff main -- demo-app/src/calculator.ts 2>/dev/null | grep -q "$CRITERION"; then
CRITERION_FOUND=true
fi
# If that fails, try diff against ORIG_HEAD
if [ "$CRITERION_FOUND" = false ]; then
if git -C "$WORKTREE" diff ORIG_HEAD 2>/dev/null | grep -q "$CRITERION"; then
CRITERION_FOUND=true
fi
fi
# Fallback: check file content directly for the criterion
if [ "$CRITERION_FOUND" = false ]; then
if grep -q "$CRITERION" "$WORKTREE/demo-app/src/calculator.ts" 2>/dev/null; then
CRITERION_FOUND=true
fi
fi
if [ "$CRITERION_FOUND" = false ]; then
FAILURES+=("CRITERION_MISSING: acceptance criterion not satisfied")
fi
fi
# Output
if [ ${#FAILURES[@]} -eq 0 ]; then
echo "VALIDATION_RESULT: PASS"
echo "RULES_PASSED: All rules passed"
exit 0
else
echo "VALIDATION_RESULT: FAIL"
echo "FAILURES:"
for f in "${FAILURES[@]}"; do echo " - $f"; done
exit 1
fi
Если все шаги завершились успешно, валидатор возвращает PASS. Если проверка не пройдена на любом из шагов – FAIL. Этот скрипт – единственный источник правды о правилах валидации: чекер не выдумывает правила, а запускает скрипт и передаёт его вывод дословно.
⚠️ Проверка критерия приёмки через LLM удобна для демо, но это не гарантия корректности. Автоматическая проверка по правилам и явные тесты остаются обязательными – финальную ответственность за результат всё равно несёт разработчик.
Запуск внешнего цикла
Запуск оформлен bash-скриптом scripts/loop-demo.sh. Строк в нём много, но все они нужны, чтобы перед стартом основного агента пройти предварительные проверки: доступ к MCP, наличие нужных файлов и так далее.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Load .env if present (auto-export with set -a)
if [ -f "$REPO_ROOT/.env" ]; then
set -a
source "$REPO_ROOT/.env"
set +a
fi
# Derive REPO_OWNER and REPO_NAME from git remote
REMOTE_URL="$(git remote get-url origin 2>/dev/null || echo "")"
if [[ "$REMOTE_URL" =~ github\.com[:/]([^/]+)/([^/.]+)(\.git)?$ ]]; then
REPO_OWNER="${BASH_REMATCH[1]}"
REPO_NAME="${BASH_REMATCH[2]}"
fi
DEFAULT_BRANCH="${DEFAULT_BRANCH:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")}"
export REPO_OWNER="${REPO_OWNER:-epro}"
export REPO_NAME="${REPO_NAME:-le-demo-2}"
export DEFAULT_BRANCH="${DEFAULT_BRANCH:-main}"
export DEMO_MODE
# Defaults
DEMO_MODE="${DEMO_MODE:-false}"
MCP_TEMPLATE="$REPO_ROOT/.omp/mcp.json.template"
MCP_CONFIG="$REPO_ROOT/.omp/mcp.json"
MCP_BACKUP="$REPO_ROOT/.omp/mcp.json.bak"
# Ensure required directories exist
mkdir -p "$REPO_ROOT/state" "$REPO_ROOT/fixtures"
# Initialize state file if missing
if [ ! -f "$REPO_ROOT/state/loop-state.md" ]; then
cat > "$REPO_ROOT/state/loop-state.md" << 'STATEEOF'
# Loop State
## Completed
## Failures
## In Progress
STATEEOF
fi
cleanup() {
# Restore original MCP config if we backed it up
if [ -f "$MCP_BACKUP" ]; then
mv "$MCP_BACKUP" "$MCP_CONFIG"
fi
}
trap cleanup EXIT
if [ "$DEMO_MODE" = "true" ]; then
echo "============================================"
echo " DEMO MODE"
echo " Using local fixtures, no GitHub network calls."
echo "============================================"
# Backup and replace MCP config with empty one so GitHub server doesn't load
if [ -f "$MCP_CONFIG" ]; then
cp "$MCP_CONFIG" "$MCP_BACKUP"
fi
echo '{"mcpServers":{}}' > "$MCP_CONFIG"
export GITHUB_PERSONAL_ACCESS_TOKEN="demo-stub-token"
else
# Resolve GITHUB_PERSONAL_ACCESS_TOKEN from env (sourced from .env or shell)
TOKEN="${GITHUB_PERSONAL_ACCESS_TOKEN:-}"
if [ -z "$TOKEN" ]; then
echo "ERROR: GITHUB_PERSONAL_ACCESS_TOKEN is not set."
echo " Add it to .env (see .env.example) or export it."
echo " Or run with DEMO_MODE=true for a dry run."
exit 1
fi
# Backup current mcp.json if it exists
if [ -f "$MCP_CONFIG" ]; then
cp "$MCP_CONFIG" "$MCP_BACKUP"
fi
# Generate mcp.json from template, injecting the actual token
# This ensures omp gets the resolved value regardless of ${...} support
if [ -f "$MCP_TEMPLATE" ]; then
sed "s/\${GITHUB_PERSONAL_ACCESS_TOKEN}/$TOKEN/g" "$MCP_TEMPLATE" > "$MCP_CONFIG"
else
echo "ERROR: $MCP_TEMPLATE not found."
exit 1
fi
fi
cd "$REPO_ROOT"
echo ""
echo "Starting loop orchestrator..."
echo "============================================"
exec omp -p --auto-approve \
--cwd "$REPO_ROOT" \
"Use /skill:loop-demo to run the full loop – discover work, delegate to maker and checker, and act on the result."
Сам агент запускается в неинтерактивном режиме, без подтверждения действий от пользователя. Пользовательский промпт короткий: использовать скилл loop-demo для запуска полного цикла, получить задачу, делегировать работу мейкеру и чекеру и действовать по результатам так, как описано в скилле. Сам скилл .omp/skills/loop-demo/SKILL.md содержит логику оркестратора, ссылку на конвенцию проекта, информацию о репозитории и все те шаги, что описаны выше.
---
name: loop-demo
description: Orchestrator of a loop-engineering system. Runs the full loop – discover work, delegate to maker and checker, and act on the result.
---
# Loop Orchestrator
You are the orchestrator in a loop-engineering system. You run the full loop: discover work, delegate to maker and checker, and act on the result.
Read `.omp/AGENTS.md` for project conventions.
## Repository info
The repo owner and name are set via environment variables:
- `REPO_OWNER` — GitHub owner (e.g. `eugeneproai`)
- `REPO_NAME` — GitHub repo name (e.g. `le-demo`)
- `DEFAULT_BRANCH` — the base branch (e.g. `main`)
Use these values in all GitHub MCP tool calls.
If these vars are not set, derive them from `git remote get-url origin` and `git rev-parse --abbrev-ref HEAD`.
## GitHub MCP tools
The GitHub MCP server exposes these tools (find the right one for each step):
- `list_issues` — list issues in a repo (params: `owner`, `repo`, `state`, `labels`, `sort`, `direction`)
- `get_issue` — get a single issue (params: `owner`, `repo`, `issue_number`)
- `create_issue` — create a new issue
- `update_issue` — update an issue (params: `owner`, `repo`, `issue_number`, `state`, `body`)
- `add_issue_comment` — add a comment to an issue
- `create_pull_request` — create a PR (params: `owner`, `repo`, `title`, `head`, `base`, `body`, `draft`)
- `search_issues` — search across repos
## State management
Read `state/loop-state.md`. It has sections:
- `## Completed` — issues already processed
- `## Failures` — issues that failed validation
- `## In Progress` — currently being worked on
## Step 1: Discover work
If `DEMO_MODE` is `true`:
Read `fixtures/demo-issues.json`. Parse the JSON array.
Use the first issue with key not already in `## Completed` or `## In Progress` or `## Failures`. Extract its `key`, `summary`, `description`, and `acceptance_criterion` fields.
If none found, append "- No new work found." to state at `## Completed` and stop.
If `DEMO_MODE` is not set (live mode):
Call `list_issues` with the repo owner/name, `state: "open"`, `labels: ["loop-demo"]`, `sort: "created"`, `direction: "asc"`.
Pick the first issue whose issue number (as string) is not in `## Completed` or `## In Progress` or `## Failures`.
If none found, append "- No new work found." to state at `## Completed` and stop.
## Step 2: Create worktree
Run:
```
git worktree add ../le-demo-2-wt-<issue-number> -b fix/<issue-number>
```
Use the issue's number (e.g. `1`, `2`) as the `<issue-key>` throughout.
## Step 3: Delegate to maker
Spawn a `task` agent (role: "loop-maker") with assignment containing:
- The issue number, title, body, and acceptance criterion
- Reference to read `.omp/AGENTS.md`
- Work in `../le-demo-2-wt-<issue-number>/demo-app`
Wait for the maker to return its report.
## Step 4: Delegate to checker
Spawn a `reviewer` agent (role: "loop-checker") with assignment containing:
- The issue number
- Acceptance criterion
- Change to the worktree root (../le-demo-2-wt-<issue-number>)
- Run: `scripts/validate-fix.sh "$(pwd)" <issue-number> "<acceptance-criterion>"`
- Parse the script output. Return VERDICT: PASS only if VALIDATION_RESULT: PASS.
Wait for the checker to return its verdict.
## Step 5: Act on result
### If checker verdict is PASS:
In live mode:
1. Call `create_pull_request` with:
- owner, repo from env
- title: `[<issue-number>] <issue title>`
- head: `fix/<issue-number>`
- base: the default branch
- body: `Fixes #<issue-number>`
2. Call `update_issue` to close it (`state: "closed"`) with a comment linking the PR number.
Record in state:
```
- <issue-number> | PR: <pr-url> | Issue closed | Maker report: <changed files> | Checker: PASS
```
In demo mode:
Write to `state/loop-state.md`:
```
- <issue-key> [PR: fix/<issue-key> → main] [Issue closed] | Maker: PASS | Checker: PASS
```
Keep the worktree branch (it's in the repo). Remove the worktree:
```
git worktree remove ../le-demo-2-wt-<issue-number>
```
### If checker verdict is FAIL:
1. Write to `state/loop-state.md` under `## Failures`:
```
- <issue-number> | Failure: <first failure message from validator output>
```
2. Do NOT create a PR or close the issue.
3. Remove the worktree including the branch:
```
git branch -D fix/<issue-number>
git worktree remove ../le-demo-2-wt-<issue-number>
```
Что получилось после запуска
После старта поднимается оркестратор, и мы ждём выполнения. По итогам первого прогона создаётся 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 удалён)
Сквозная проверка (боевой режим)
- Создайте в репозитории задачу с заголовком «Fix add() returning subtraction result» и меткой
loop-demo. - Запустите баш-скрипт
GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx ./scripts/loop-demo.sh. - Проверьте: задача закрыта, есть 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
Вывод
С помощью такого цикла можно реализовывать любые задачи. В этом демо они намеренно простые, но ничто не мешает адаптировать метод под задачи сложнее и масштабнее. На ежедневных задачах агент во внешнем цикле при правильной настройке показывает себя хорошо.
Главное – держать процесс в понятных границах: атомарные задачи, проверяемые критерии, независимая проверка результата и файл памяти, который хранит состояние между итерациями. Тогда внешний цикл не превращается в чёрный ящик, а остаётся управляемым инструментом.

