docs: update QG table, task-file write, orphan recovery; add BUGFIXES_2026-06-02
This commit is contained in:
22
README.md
22
README.md
@@ -27,10 +27,10 @@ created → analysis → architecture → development → review → testing →
|
||||
| created | — | — | Plane webhook (work_item.created) |
|
||||
| analysis | analyst | Файлы BRD/TRZ/AC/TestPlan | Push docs/ |
|
||||
| architecture | architect | ADR или infra-requirements | Push docs/ |
|
||||
| development | developer | CI green | Gitea status event |
|
||||
| review | reviewer | PR approved (no stale) | Gitea review event |
|
||||
| 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 | Test report с PASS | Auto-advance после tester |
|
||||
| deploy | — | — | PR merge |
|
||||
| deploy | deployer | — | SSH deploy-hook |
|
||||
| done | — | — | — |
|
||||
|
||||
## API Endpoints
|
||||
@@ -115,8 +115,14 @@ uvicorn src.main:app --reload --port 8500
|
||||
### Review bounce
|
||||
При REQUEST_CHANGES от reviewer задача откатывается в development, developer перезапускается (до 3 попыток). При исчерпании — эскалация.
|
||||
|
||||
### Orphan recovery
|
||||
При старте контейнера все runs с `finished_at IS NULL` старше 35 минут помечаются как failed (exit_code=-1).
|
||||
### 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.
|
||||
@@ -125,7 +131,7 @@ uvicorn src.main:app --reload --port 8500
|
||||
Gitea events роутятся по типу:
|
||||
- `push` → проверка файлов, advance architecture/development
|
||||
- `pull_request*` (wildcard) → review approved/rejected, PR merge
|
||||
- `status` → CI green/failure
|
||||
- `status` → (legacy) Gitea CI; С-1: больше не authoritative, `failure` логируется на debug и не блокирует/не алертит (QG развития = локальный `check_tests_local`)
|
||||
|
||||
## Тесты
|
||||
|
||||
@@ -135,7 +141,9 @@ pytest tests/ -v
|
||||
|
||||
## Известные ограничения
|
||||
|
||||
1. **Single-task** — одновременно обрабатывается одна задача на репозиторий (нет параллелизма)
|
||||
1. **Single-task / shared `/repos` checkout** — одновременно безопасно обрабатывается одна задача: все агенты и `check_tests_local` делают `git checkout` в одном `/repos/<repo>` → гонки при параллельных задачах. Исправление — git worktree per task (S-4, отдельно).
|
||||
2. **Plane sync** — маппинг issue ID может быть некорректным (P3, в работе)
|
||||
3. **In-process daemon-потоки** — агенты живут в потоках uvicorn; при рестарте ловит orphan-recovery. Целевое — очередь задач (F-2b)
|
||||
4. **Gitea CI не настроен** — тесты гоняет сам оркестратор локально
|
||||
3. **Tester timeout** — e2e тесты с Playwright могут занимать >25 мин на тяжёлых фичах
|
||||
4. **No retry on API errors** — httpx вызовы к Gitea/Plane без retry logic
|
||||
|
||||
@@ -26,9 +26,9 @@ 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_ci_green)
|
||||
review: → testing (agent: tester, QG: check_review_approved)
|
||||
testing: → deploy (agent: None, QG: check_tests_passed)
|
||||
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)
|
||||
}
|
||||
```
|
||||
@@ -39,9 +39,11 @@ STAGE_TRANSITIONS = {
|
||||
|-------|---------------|
|
||||
| check_analysis_approved | Filesystem: 4 файла + :approved: comment в Plane |
|
||||
| check_architecture_done | Filesystem: ADR dir или infra-requirements.md |
|
||||
| check_ci_green | Gitea API: GET /commits/{branch}/status |
|
||||
| check_review_approved | Gitea API: GET /pulls/{n}/reviews (skip stale) |
|
||||
| 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`)
|
||||
|
||||
@@ -53,9 +55,9 @@ claude.exe --print --system-prompt --allowedTools Read,Write,Edit,Bash
|
||||
|
||||
Каждый запуск:
|
||||
1. Записывает run в DB (agent_runs)
|
||||
2. Запускает subprocess с stdout → `/app/data/runs/{id}.log`
|
||||
3. Стартует **watchdog thread** (timeout 30 мин → SIGKILL)
|
||||
4. Стартует **monitor thread** (ждёт завершения → git commit/push → auto-advance)
|
||||
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`)
|
||||
|
||||
@@ -65,7 +67,7 @@ claude.exe --print --system-prompt --allowedTools Read,Write,Edit,Bash
|
||||
3. Если QG пройден — продвигает стадию
|
||||
4. Запускает следующего агента (если определён)
|
||||
|
||||
Исключение: `check_review_approved` — обрабатывается через PR webhook, не через auto-advance.
|
||||
Примечание: переход `review → testing` использует `check_reviewer_verdict` (читается из frontmatter `12-review.md`); `development → review` — `check_tests_local` (оркестратор сам прогоняет тесты, не зависит от Gitea CI).
|
||||
|
||||
### 6. Review Bounce
|
||||
|
||||
@@ -176,18 +178,17 @@ services:
|
||||
|
||||
| Механизм | Описание |
|
||||
|----------|----------|
|
||||
| Orphan recovery | При старте: runs без finished_at старше 35 мин → exit_code=-1 |
|
||||
| Watchdog | Каждый агент: timeout 30 мин → SIGKILL + exit_code=-9 |
|
||||
| tini | PID 1 reaper — zombie processes невозможны |
|
||||
| safe.directory | git операции работают в любой директории |
|
||||
| Stale review skip | check_review_approved игнорирует stale reviews |
|
||||
| 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)
|
||||
- **Task file**: `.task-{suffix}.md` — генерируется orchestrator **прямой записью в смонтированный volume `/repos/<repo>/`** (B-1, без docker). В `.gitignore` репозитория проекта (рантайм-артефакт, не коммитится).
|
||||
- **Tools**: Read, Write, Edit, Bash
|
||||
- **Output**: `--print` mode (весь вывод в stdout после завершения)
|
||||
|
||||
@@ -198,3 +199,19 @@ services:
|
||||
| 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` на хосте.
|
||||
|
||||
66
docs/BUGFIXES_2026-06-02.md
Normal file
66
docs/BUGFIXES_2026-06-02.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Bugfixes 2026-06-02 — устранение багов оркестратора
|
||||
|
||||
**Источник:** `tasks/multi-agent/AUDIT_2026-06-02.md`
|
||||
**Цель:** вернуть автономность мультиагентного pipeline (ET-009: 0/6 этапов были автономны).
|
||||
**Исполнитель:** Dev-агент (Opus 4.8 Tokenator).
|
||||
|
||||
---
|
||||
|
||||
## Что починено
|
||||
|
||||
### B-1 — запись `.task-*.md` без docker
|
||||
**Было:** `launcher._write_task_file()` писал файл через `docker run --rm -i python:3.12-slim bash -c "cat > ..."`. Бинарника `docker` в контейнере НЕТ → запись падала молча → агент читал старый task-файл.
|
||||
**Стало:** прямая запись в смонтированный volume `/repos/<repo>/<task_file>` обычным `open(..., "w")`. При ошибке записи — `RuntimeError` (не молчит).
|
||||
**Файл:** `src/agents/launcher.py` (`_write_task_file`, вызов в `launch`).
|
||||
**Проверка:**
|
||||
```bash
|
||||
docker exec orchestrator python3 -c "
|
||||
import sys; sys.path.insert(0,'/repos/orchestrator')
|
||||
from src.agents.launcher import launcher
|
||||
launcher._write_task_file('enduro-trails', '.task-test-write.md', 'hello-from-fix')
|
||||
print(open('/repos/enduro-trails/.task-test-write.md').read())"
|
||||
# => hello-from-fix (без docker)
|
||||
```
|
||||
✅ Verified: READBACK = `hello-from-fix`.
|
||||
|
||||
### B-2 — Popen stdout → файл, убран PIPE-поток (зомби, потеря exit_code)
|
||||
**Было:** `Popen(stdout=PIPE)` + daemon-поток с `select`/`readline` + startup-timeout 120с. → PIPE-deadlock, зомби при рестарте, `exit_code=None` в БД (все прогоны ET-009).
|
||||
**Стало:** `log_fh = open(output_path, "w")`; `Popen(stdout=log_fh, stderr=STDOUT)`. `_monitor_agent` упрощён до `proc.wait()` + `log_fh.close()`. PIPE-поток и startup-timeout удалены. Watchdog по pid (`AGENT_TIMEOUT`) сохранён.
|
||||
**Файл:** `src/agents/launcher.py` (`launch`, `_monitor_agent`).
|
||||
**Проверка:** после прогона `SELECT exit_code FROM agent_runs ORDER BY id DESC LIMIT 1` != NULL; `ps aux | grep defunct` — пусто.
|
||||
|
||||
### B-3 — `.task-*.md` в `.gitignore`, не коммитятся
|
||||
**Было:** task-файлы трекались в git (`.task-arch.md`, `.task-dev.md`, `.task-review.md`, `.task.md`) и тащились между задачами.
|
||||
**Стало:** в `enduro-trails/.gitignore` добавлено `.task*.md`; трекаемые файлы убраны из индекса (`git rm --cached`).
|
||||
**Файл:** `enduro-trails/.gitignore` (+ untrack). Ветка `main` protected → изменения в **PR #19** (`chore/gitignore-task-files`).
|
||||
**Проверка:** `git check-ignore .task.md .task-arch.md` → matched. `git add docs/ src/ tests/` (scoped) не цепляют task-файлы.
|
||||
|
||||
### S-5 — машиночитаемый verdict ревьюера
|
||||
**Было:** `check_reviewer_verdict` искал подстроки `APPROVED`/`REQUEST_CHANGES` во всём тексте (5000 байт) → ложные срабатывания на таблицах.
|
||||
**Стало:** читается ТОЛЬКО `verdict:` из YAML-frontmatter `12-review.md` (через `yaml.safe_load`). Нет verdict / нет frontmatter → not-approved. `reviewer.md` обновлён: требование frontmatter `verdict: APPROVED|REQUEST_CHANGES`.
|
||||
**Файлы:** `src/qg/checks.py` (`check_reviewer_verdict`), `enduro-trails/.openclaw/agents/reviewer.md` (PR #19; рабочая копия применена сразу).
|
||||
**Проверка:** ET-009 `12-review.md` (frontmatter `verdict: APPROVED`) → `(True, 'Reviewer verdict: APPROVED')`. Unit-тесты покрывают APPROVED/REQUEST_CHANGES/no-verdict/no-frontmatter/таблица-в-теле.
|
||||
|
||||
### S-1 — QG тестов гоняет сам оркестратор (не Gitea CI)
|
||||
**Было:** `development → review` QG = `check_ci_green` (Gitea status). CI не настроен → всегда false → автопереход не происходил + ложные «CI failed» алерты.
|
||||
**Стало:** новый QG `check_tests_local` — оркестратор делает `git fetch/checkout <branch>` + `make test` в `/repos/<repo>`, judge по exit-code. `stages.py`: `development` QG → `check_tests_local`. Dispatch добавлен в `launcher._try_advance_stage` и `webhooks/plane._try_advance_stage` (args `(repo, branch)`). `webhooks/gitea.handle_ci_status`: `failure` → debug-лог, без `notify_error`.
|
||||
**Файлы:** `src/qg/checks.py`, `src/stages.py`, `src/agents/launcher.py`, `src/webhooks/plane.py`, `src/webhooks/gitea.py`.
|
||||
**Грабля (известное ограничение):** `check_tests_local` делает checkout в shared `/repos` — небезопасно при параллельных задачах (S-4 worktree — отдельно).
|
||||
|
||||
### M-1 — нормальный orphan-recovery
|
||||
**Было:** `UPDATE agent_runs SET exit_code=-1 WHERE finished_at IS NULL AND started_at < now-35min` — молча списывал зомби.
|
||||
**Стало:** перечисляем каждый orphan-run, помечаем exit=-1, логируем per-run `warning` («manual check needed»), отправляем Telegram-уведомление. Не автоперезапускаем (риск зацикливания). Killing по pid невозможен — pid не персистится в БД (задокументировано).
|
||||
**Файл:** `src/main.py` (lifespan).
|
||||
|
||||
---
|
||||
|
||||
## Что НЕ входило (отдельные задачи)
|
||||
- S-2/S-3 (rollback деплоера в shared-репо), S-4 (git worktree per task), M-3 (единый stage-engine), F-2b (очередь задач), M-7 (идемпотентность webhook). `_auto_merge_pr` — мёртвый код оставлен (отдельная чистка).
|
||||
|
||||
## Тесты
|
||||
- Новый файл `tests/test_launcher.py`: 10 тестов (`_write_task_file` пишет/raise/без docker; `check_reviewer_verdict` frontmatter cases).
|
||||
- `tests/test_qg.py`: 16 passed. `tests/test_launcher.py`: 10 passed.
|
||||
- ⚠️ Pre-existing: `tests/test_webhooks.py` имеет падения (401/signature + cross-file env pollution) — НЕ связаны с этими фиксами, существовали до правок. Запуск в изоляции part-passes; в общем прогоне больше падений из-за общего env/DB между тест-файлами. Гигиена test_webhooks — отдельная задача.
|
||||
|
||||
## Деплой
|
||||
Оркестратор пересобран: `cd /home/slin/repos/orchestrator && docker compose up -d --build`. Health: `{"status":"ok"}`.
|
||||
Reference in New Issue
Block a user