Staging check suite passed 10/10 (exit 0), run canonically inside orchestrator-staging via the Docker Engine API (docker exec equivalent). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Multi-Agent Orchestrator
См. CLAUDE.md (паспорт проекта) и docs/architecture/README.md (архитектура).
FastAPI-сервис для оркестрации мульти-агентного пайплайна разработки. Принимает webhooks от Plane и Gitea, управляет жизненным циклом задач через Quality Gates, запускает Claude CLI агентов на каждой стадии.
Архитектура
Plane (task mgmt) ──webhook──┐
├──► Orchestrator (FastAPI) ──► Quality Gates ──► Agent Launcher
Gitea (git events) ─webhook──┘ │ │
▼ ▼
SQLite DB Claude CLI
(events, tasks, (analyst, architect,
agent_runs) developer, reviewer, tester)
Стадии пайплайна
created → analysis → architecture → development → review → testing → deploy-staging → deploy → done
↑ │
└───── REQUEST_CHANGES ─────┘ (max 3 retries)
| Стадия | Агент | Quality Gate (выход) | Триггер перехода |
|---|---|---|---|
| created | — | — | Plane webhook (work_item.created) |
| analysis | analyst | Файлы BRD/TRZ/AC/TestPlan | Push docs/ |
| architecture | architect | ADR или infra-requirements | Push docs/ |
| development | developer | check_tests_local (орк сам гоняет make test) |
Auto-advance после developer |
| review | reviewer | check_reviewer_verdict (verdict: во frontmatter 12-review.md) |
Auto-advance после reviewer |
| testing | tester | check_tests_passed (test-report.md) | Auto-advance после tester |
| deploy-staging | deployer | check_staging_status (15-staging-log.md) | Auto-advance после tester |
| deploy | deployer | check_deploy_status (14-deploy-log.md) | Auto-advance после staging |
| done | — | — | — |
API Endpoints
| Method | Path | Описание |
|---|---|---|
| GET | /health |
Health check |
| GET | /status |
Активные задачи (stage != done) |
| GET | /queue |
Очередь задач (ORCH-1): counts по статусам + max_concurrency + последние 10 jobs |
| POST | /webhook/plane |
Plane webhook receiver |
| POST | /webhook/gitea |
Gitea webhook receiver |
Структура проекта
src/
├── main.py # FastAPI app, lifespan (orphan recovery)
├── config.py # Pydantic settings (env vars)
├── db.py # SQLite: init, get_db, update_task_stage
├── stages.py # State machine (transitions, agents, QG)
├── notifications.py # Уведомления (логирование)
├── plane_sync.py # Синхронизация статусов с Plane API
├── queue_worker.py # ORCH-1: фоновый воркер очереди (claim → launch_job)
├── agents/
│ └── launcher.py # AgentLauncher: launch/launch_job, monitor, watchdog, auto-advance
├── webhooks/
│ ├── plane.py # Plane webhook handler
│ └── gitea.py # Gitea webhook handler (push, PR, CI status)
└── qg/
└── checks.py # Quality Gate checks (filesystem + Gitea API)
data/
├── orchestrator.db # SQLite database
└── runs/ # Agent output logs ({run_id}.log)
docs/
├── PRODUCT_VISION.md # Видение продукта
├── architecture/
│ ├── README.md # Обзор архитектуры, компоненты, API
│ ├── internals.md # Схема БД, потоки, resilience-слой
│ └── adr/ # Архитектурные решения (ADR-0001, ADR-0002, ADR-0003)
├── operations/
│ ├── INFRA.md # Топология, порты, env, self-hosting риски
│ ├── DEPLOY_HOOK.md # Деплой-хук
│ ├── STAGING.md # Staging-окружение
│ ├── STAGING_CHECK.md # Проверки staging
│ └── SETUP_WEBHOOKS.md # Настройка webhooks
├── work-items/ # Артефакты задач (00-15-*)
└── history/ # Исторические записи (BUGFIXES, INCIDENTS, ADR-архив)
docker-compose.yml # Deployment config
Dockerfile # Python 3.12 + Docker CLI + tini
Запуск
Docker (production)
docker compose up -d --build
Dev
pip install -r requirements.txt
uvicorn src.main:app --reload --port 8500
Конфигурация
Все переменные с префиксом ORCH_:
| Переменная | Описание | Default |
|---|---|---|
ORCH_PLANE_API_URL |
Plane API URL | http://localhost:8091 |
ORCH_PLANE_API_TOKEN |
Plane API token | — |
ORCH_PLANE_WEBHOOK_SECRET |
Webhook secret | — |
ORCH_PLANE_WORKSPACE_SLUG |
Workspace slug | — |
ORCH_PLANE_PROJECT_ID |
Project UUID | — |
ORCH_GITEA_URL |
Gitea URL | http://localhost:3000 |
ORCH_GITEA_TOKEN |
Gitea API token | — |
ORCH_GITEA_WEBHOOK_SECRET |
Gitea webhook secret | — |
ORCH_GITEA_OWNER |
Gitea repo owner | admin |
ORCH_DEFAULT_REPO |
Default repository (fallback) | enduro-trails |
ORCH_PROJECTS_JSON |
Multi-repo реестр (JSON-массив, ORCH-6) | "" → дефолт в src/projects.py |
ORCH_CLAUDE_BIN |
Путь к Claude CLI | /opt/claude-code/bin/claude.exe |
ORCH_REPOS_DIR |
Repos dir (container) | /repos |
ORCH_HOST_REPOS_DIR |
Repos dir (host) | /home/slin/repos |
ORCH_DB_PATH |
SQLite path | /app/data/orchestrator.db |
ORCH_MAX_CONCURRENCY |
Сколько jobs воркер запускает параллельно (ORCH-1) | 1 |
ORCH_QUEUE_POLL_INTERVAL |
Период опроса очереди воркером, сек (ORCH-1) | 2.0 |
ORCH_PREFLIGHT_CACHE_TTL |
Кэш preflight (CLI/net), сек (ORCH-1 resilience) | 45 |
ORCH_BACKOFF_BASE_SECONDS |
База exp-backoff для transient (429) | 10 |
ORCH_BACKOFF_MAX_SECONDS |
Потолок backoff | 600 |
ORCH_TRANSIENT_MAX_ATTEMPTS |
Ретраи для 429/недоступности | 5 |
ORCH_BREAKER_THRESHOLD |
transient подряд до открытия breaker | 3 |
ORCH_BREAKER_PAUSE_SECONDS |
Пауза при открытом breaker | 300 |
Очередь задач (ORCH-1 / F-2b)
Webhook-хэндлеры больше не спавнят claude-агентов синхронно в процессе uvicorn.
Вместо этого они кладут job в персистентную SQLite-таблицу jobs
(enqueue_job, мгновенный ответ), а фоновый воркер (src/queue_worker.py)
забирает jobs с учётом ORCH_MAX_CONCURRENCY и запускает агента (launch_job,
та же Popen-логика, что и раньше).
Преимущества:
- Рестарт-safe. При старте jobs со статусом
runningвозвращаются вqueued(queue-recovery в lifespan) — работа не теряется. - Лимит параллелизма. Воркер не превышает
ORCH_MAX_CONCURRENCY. - Ретраи. Упавший job (exit≠0) ретраится пока
attempts < max_attempts, потомfailed+ Telegram-нотификация.
Статусы job: queued → running → done | failed. Наблюдаемость — через GET /queue.
Resilience-слой: дешёвый preflight (CLI/net, кэш, без токенов) гейтит claim;
429/overload детектится по логу (transient vs permanent), transient ретраится с
exp-backoff (available_at, Retry-After); circuit breaker паузит воркер после N
transient подряд. Подробности: docs/history/ORCH-1_JOB_QUEUE.md.
Multi-repo: реестр проектов (ORCH-6)
Оркестратор обслуживает несколько репозиториев через реестр проектов
(src/projects.py), ключ = Plane project id. Plane-webhook фильтрует события
по проекту (неизвестный проект → ignored) и резолвит repo / work_item_prefix /
Plane-проект из маппинга.
По умолчанию (если ORCH_PROJECTS_JSON пуст) зарегистрированы два проекта:
| Проект | Plane project id | repo | prefix |
|---|---|---|---|
| enduro-trails | 7a79f0a9-5278-49cd-9007-9a338f238f9c |
enduro-trails |
ET |
| orchestrator | 8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a |
orchestrator |
ORCH |
Как добавить новый проект
-
Убедись, что gitea-репо уже клонировано в
/repos/<repo>(авто-clone — отдельно). -
Узнай Plane project uuid (из URL проекта в Plane или через Plane API).
-
Добавь запись в
ORCH_PROJECTS_JSONв.env(JSON-массив). Важно: если задаёшьORCH_PROJECTS_JSON, он полностью заменяет дефолт — перечисли все нужные проекты (включая enduro-trails и orchestrator):ORCH_PROJECTS_JSON='[ {"plane_project_id":"7a79f0a9-5278-49cd-9007-9a338f238f9c","repo":"enduro-trails","work_item_prefix":"ET","name":"enduro-trails"}, {"plane_project_id":"8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a","repo":"orchestrator","work_item_prefix":"ORCH","name":"orchestrator"}, {"plane_project_id":"<новый-uuid>","repo":"<новый-repo>","work_item_prefix":"<PREFIX>","name":"<имя>"} ]' -
Пересобери:
docker compose up -d --build. -
Проверь резолв:
docker exec orchestrator python3 -c "from src.projects import get_project_by_plane_id as g; print(g('<новый-uuid>'))"
Поля name опционально (по умолчанию = repo). Подробности — docs/architecture/internals.md.
Ключевые механизмы
Auto-advance
После успешного завершения агента (exit_code=0), _try_advance_stage() проверяет QG и автоматически продвигает задачу + запускает следующего агента.
Review bounce
При REQUEST_CHANGES от reviewer задача откатывается в development, developer перезапускается (до 3 попыток). При исчерпании — эскалация.
Orphan recovery (M-1)
При старте контейнера каждый run с finished_at IS NULL старше 35 минут помечается exit_code=-1, логируется per-run warning и отправляется Telegram-уведомление «нужна ручная проверка/перезапуск» (не молча).
Запись task-файлов (B-1)
Task-файлы .task-*.md пишутся прямой записью в смонтированный volume /repos/<repo>/ (без docker). При ошибке записи — RuntimeError (не молчит). В .gitignore проекта.
Логи агентов (B-2)
stdout/stderr агента перенаправляются СРАЗУ в /app/data/runs/{id}.log на уровне ОС (без PIPE). monitor-поток делает proc.wait() → реальный exit_code, нет зомби.
Watchdog
Каждый агент имеет timeout 30 минут. При превышении — SIGKILL + запись exit_code=-9.
Event routing
Gitea events роутятся по типу:
push→ проверка файлов, advance architecture/developmentpull_request*(wildcard) → review approved/rejected, PR mergestatus→ (legacy) Gitea CI; С-1: больше не authoritative,failureлогируется на debug и не блокирует/не алертит (QG развития = локальныйcheck_tests_local)
Тесты
pytest tests/ -v
Известные ограничения
- Single-task / shared
/reposcheckout — одновременно безопасно обрабатывается одна задача: все агенты иcheck_tests_localделаютgit checkoutв одном/repos/<repo>→ гонки при параллельных задачах. Исправление — git worktree per task (S-4, отдельно). - Plane sync — маппинг issue ID может быть некорректным (P3, в работе)
- In-process daemon-потоки — агенты живут в потоках uvicorn; при рестарте ловит orphan-recovery. Целевое — очередь задач (F-2b)
- Gitea CI не настроен — тесты гоняет сам оркестратор локально
- Tester timeout — e2e тесты с Playwright могут занимать >25 мин на тяжёлых фичах
- No retry on API errors — httpx вызовы к Gitea/Plane без retry logic