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              – здесь оркестратор фиксирует прогресс
# 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 удалён)

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

  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

Вывод

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

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

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

Prompt Injection в ИИ-агентах: классификация атак и 10 практических способов защиты
/9 мин

Prompt Injection в ИИ-агентах: классификация атак и 10 практических способов защиты

Разбираем главную угрозу для LLM-приложений на примере уязвимости CLI-агента Gemini. Подробный разбор явных и неявных инъекций, рекомендации OpenAI и пошаговое руководство по построению эшелонированной защиты.