218 lines
11 KiB
Markdown
218 lines
11 KiB
Markdown
# Архитектура Orchestrator
|
||
|
||
## Обзор
|
||
|
||
Orchestrator — event-driven FastAPI сервис, который управляет жизненным циклом задач разработки через мульти-агентный пайплайн. Каждая задача проходит через фиксированные стадии, на каждой из которых работает специализированный Claude CLI агент.
|
||
|
||
## Компоненты
|
||
|
||
### 1. Webhook Receivers
|
||
|
||
#### Plane Webhook (`src/webhooks/plane.py`)
|
||
- Принимает `work_item.created` — создаёт задачу в DB, запускает analyst
|
||
- Принимает `work_item.updated` — синхронизация статусов
|
||
|
||
#### Gitea Webhook (`src/webhooks/gitea.py`)
|
||
- **push** — проверяет наличие артефактов (docs/, src/), продвигает стадию
|
||
- **pull_request\*** (wildcard) — обрабатывает review approved/rejected, PR merge
|
||
- **status** — CI green/failure, продвигает development → review
|
||
|
||
### 2. State Machine (`src/stages.py`)
|
||
|
||
Линейный пайплайн с одним возможным откатом (review → development):
|
||
|
||
```
|
||
STAGE_TRANSITIONS = {
|
||
created: → analysis (agent: None)
|
||
analysis: → architecture (agent: architect, QG: check_analysis_approved)
|
||
architecture: → development (agent: developer, QG: check_architecture_done)
|
||
development: → review (agent: reviewer, QG: check_tests_local)
|
||
review: → testing (agent: tester, QG: check_reviewer_verdict)
|
||
testing: → deploy (agent: deployer, QG: check_tests_passed)
|
||
deploy: → done (agent: None, QG: None)
|
||
}
|
||
```
|
||
|
||
### 3. Quality Gates (`src/qg/checks.py`)
|
||
|
||
| Check | Метод проверки |
|
||
|-------|---------------|
|
||
| check_analysis_approved | Filesystem: 4 файла + :approved: comment в Plane |
|
||
| check_architecture_done | Filesystem: ADR dir или infra-requirements.md |
|
||
| check_tests_local | Оркестратор сам гоняет `make test` в `/repos/<repo>` (judge по exit-code). Заменил check_ci_green: Gitea CI не сконфигурирован. |
|
||
| check_reviewer_verdict | Filesystem: читает `verdict: APPROVED\|REQUEST_CHANGES` из YAML-frontmatter `12-review.md` (только машиночитаемое поле, не подстроки в тексте) |
|
||
| check_tests_passed | Filesystem: test-report.md содержит "PASS" |
|
||
| check_ci_green | (legacy) Gitea API: GET /commits/{branch}/status — больше не используется как QG развития |
|
||
| check_review_approved | (legacy) Gitea API: GET /pulls/{n}/reviews — не используется в STAGE_TRANSITIONS |
|
||
|
||
### 4. Agent Launcher (`src/agents/launcher.py`)
|
||
|
||
Запускает Claude CLI как subprocess:
|
||
|
||
```bash
|
||
claude.exe --print --system-prompt --allowedTools Read,Write,Edit,Bash
|
||
```
|
||
|
||
Каждый запуск:
|
||
1. Записывает run в DB (agent_runs)
|
||
2. Запускает subprocess. **stdout/stderr перенаправляются СРАЗУ в файл `/app/data/runs/{id}.log` на уровне ОС** (Popen `stdout=log_fh`). Никакого PIPE в памяти оркестратора → нет PIPE-deadlock, нет потока-читателя, нет зомби (B-2).
|
||
3. Стартует **watchdog thread** (timeout 30 мин → SIGKILL по pid)
|
||
4. Стартует **monitor thread**: `proc.wait()` (гарантированный reap → реальный exit_code в БД) → закрывает log_fh → git commit/push → auto-advance
|
||
|
||
### 5. Auto-advance (`launcher._try_advance_stage`)
|
||
|
||
После успешного завершения агента:
|
||
1. Определяет текущую стадию задачи
|
||
2. Проверяет QG для выхода из стадии
|
||
3. Если QG пройден — продвигает стадию
|
||
4. Запускает следующего агента (если определён)
|
||
|
||
Примечание: переход `review → testing` использует `check_reviewer_verdict` (читается из frontmatter `12-review.md`); `development → review` — `check_tests_local` (оркестратор сам прогоняет тесты, не зависит от Gitea CI).
|
||
|
||
### 6. Review Bounce
|
||
|
||
При REQUEST_CHANGES:
|
||
1. Считает количество developer runs для задачи
|
||
2. Если < MAX_DEV_RETRIES (3) — откатывает в development, перезапускает developer
|
||
3. Если >= MAX_DEV_RETRIES — эскалация (логирование + уведомление)
|
||
|
||
## Database Schema
|
||
|
||
```sql
|
||
-- Задачи
|
||
CREATE TABLE tasks (
|
||
id INTEGER PRIMARY KEY,
|
||
work_item_id TEXT, -- Plane issue identifier (e.g. "ET-006")
|
||
plane_issue_id TEXT, -- Plane UUID
|
||
repo TEXT,
|
||
branch TEXT,
|
||
stage TEXT DEFAULT 'created',
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Запуски агентов
|
||
CREATE TABLE agent_runs (
|
||
id INTEGER PRIMARY KEY,
|
||
task_id INTEGER REFERENCES tasks(id),
|
||
agent TEXT, -- analyst/architect/developer/reviewer/tester
|
||
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
finished_at TIMESTAMP,
|
||
exit_code INTEGER,
|
||
output_path TEXT -- /app/data/runs/{id}.log
|
||
);
|
||
|
||
-- Сырые события
|
||
CREATE TABLE events (
|
||
id INTEGER PRIMARY KEY,
|
||
source TEXT, -- plane/gitea
|
||
event_type TEXT,
|
||
payload TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
## Deployment
|
||
|
||
### Docker Compose
|
||
|
||
```yaml
|
||
services:
|
||
orchestrator:
|
||
build: .
|
||
container_name: orchestrator
|
||
restart: unless-stopped
|
||
network_mode: host
|
||
volumes:
|
||
- ./data:/app/data # SQLite + logs
|
||
- /home/slin/repos:/repos # Git repositories
|
||
- /var/run/docker.sock:/var/run/docker.sock # Docker CLI
|
||
- claude-code:/opt/claude-code:ro # Claude CLI binary
|
||
- /home/slin/.claude:/home/slin/.claude # Claude config
|
||
env_file: .env
|
||
group_add: ["999"] # docker group
|
||
```
|
||
|
||
### Dockerfile
|
||
|
||
- Base: python:3.12-slim
|
||
- Docker CLI (sibling containers)
|
||
- **tini** как PID 1 (proper zombie reaping)
|
||
- `git config --global safe.directory '*'`
|
||
- ENTRYPOINT: tini → uvicorn
|
||
|
||
## Потоки данных
|
||
|
||
### Happy path (ET-006 пример)
|
||
|
||
```
|
||
1. Plane webhook: work_item.created → task created, analyst launched
|
||
2. Analyst: пишет BRD/TRZ/AC/TestPlan → git push docs/
|
||
3. Plane comment :approved: → QG check_analysis_approved → PASS
|
||
4. Auto-advance: analysis → architecture, architect launched
|
||
5. Architect: пишет ADR, infra-requirements → git push docs/
|
||
6. Gitea push webhook: ADR detected → QG check_architecture_done → PASS
|
||
7. Auto-advance: architecture → development, developer launched
|
||
8. Developer: пишет код src/ + tests/ → git push, creates PR
|
||
9. Gitea status webhook: CI green → QG check_ci_green → PASS
|
||
10. Auto-advance: development → review, reviewer launched
|
||
11. Reviewer: оставляет review (APPROVED или REQUEST_CHANGES)
|
||
12. Gitea PR webhook: review event → QG check_review_approved → PASS
|
||
13. Advance: review → testing, tester launched
|
||
14. Tester: прогоняет тесты, пишет test-report.md → git push
|
||
15. Auto-advance: testing → deploy (QG check_tests_passed → PASS)
|
||
16. PR merge → Gitea PR webhook: action=closed, merged=true → done
|
||
```
|
||
|
||
### Review bounce path
|
||
|
||
```
|
||
11. Reviewer: REQUEST_CHANGES
|
||
12. Gitea PR webhook: review_state=REQUEST_CHANGES, stage=review
|
||
13. Rollback: review → development, developer relaunched (attempt N/3)
|
||
14. Developer: фиксит замечания → git push
|
||
15. CI green → development → review, reviewer relaunched
|
||
16. Reviewer: APPROVED → continue happy path
|
||
```
|
||
|
||
## Resilience
|
||
|
||
| Механизм | Описание |
|
||
|----------|----------|
|
||
| Watchdog | Каждый агент: timeout 30 мин → SIGKILL + exit_code=-9 |
|
||
| safe.directory | git операции работают в любой директории |
|
||
| Max retries | Developer: max 3 попытки, затем эскалация |
|
||
| Zombie-free | stdout идёт сразу в файл + monitor `proc.wait()` → процесс всегда reap'нут (B-2) |
|
||
| Orphan recovery | При старте: orphan-run'ы (finished_at IS NULL, старше 35 мин) помечаются exit=-1 с per-run warning + Telegram-уведомлением «нужна ручная проверка» (M-1) |
|
||
|
||
## Агенты
|
||
|
||
Каждый агент — Claude CLI с:
|
||
- **System prompt**: `.openclaw/agents/{role}.md` (в репозитории)
|
||
- **Task file**: `.task-{suffix}.md` — генерируется orchestrator **прямой записью в смонтированный volume `/repos/<repo>/`** (B-1, без docker). В `.gitignore` репозитория проекта (рантайм-артефакт, не коммитится).
|
||
- **Tools**: Read, Write, Edit, Bash
|
||
- **Output**: `--print` mode (весь вывод в stdout после завершения)
|
||
|
||
| Агент | Артефакты | Время (типичное) |
|
||
|-------|-----------|-------------------|
|
||
| analyst | BRD, TRZ, AC, TestPlan | 5-10 мин |
|
||
| architect | ADR, infra-requirements, tech-risks | 5-10 мин |
|
||
| developer | src/, tests/, PR | 15-30 мин |
|
||
| reviewer | review report, PR review | 3-5 мин |
|
||
| tester | test-report.md, e2e results | 10-25 мин |
|
||
| deployer | merge PR + SSH deploy-hook + smoke | 5-10 мин |
|
||
|
||
## Известные ограничения
|
||
|
||
- **Shared `/repos` checkout (гонки при параллельных задачах).** Все агенты и
|
||
`check_tests_local` делают `git checkout` в одном `/repos/<repo>`. При двух
|
||
одновременно активных задачах checkout одной перетрёт рабочую копию другой.
|
||
Пока приемлемо (задачи идут последовательно). **Исправление — git worktree per task/branch
|
||
(запланировано отдельной задачей S-4).**
|
||
- **In-process daemon-потоки.** Агенты запускаются в daemon-потоках uvicorn. При
|
||
рестарте uvicorn запущенные агенты осиротевают → ловит orphan-recovery (M-1).
|
||
Целевая архитектура — очередь задач (F-2b, отдельно).
|
||
- **Gitea CI не настроен.** QG развития теперь локальный (`check_tests_local`);
|
||
Gitea CI-статусы не являются authoritative и не блокируют pipeline.
|
||
- **Docker внутри контейнера orchestrator НЕДОСТУПЕН.** Деплой идёт только через
|
||
SSH-хук `enduro-deploy-hook.sh` на хосте.
|