197 lines
7.2 KiB
Markdown
197 lines
7.2 KiB
Markdown
# DEV_TASK_ORCHESTRATOR_FIXES.md — Фикс критических багов Orchestrator
|
||
|
||
---
|
||
type: dev-task
|
||
priority: high
|
||
project: multi-agent
|
||
created_at: 2026-05-21
|
||
author: stream
|
||
---
|
||
|
||
## Контекст
|
||
|
||
Первый полный автоматический прогон ET-002 выявил 5 критических багов, которые мешают автономной работе пайплайна. Без этих фиксов каждый переход между стадиями требует ручного вмешательства.
|
||
|
||
## Задачи
|
||
|
||
### 1. Установить git в контейнер orchestrator
|
||
|
||
**Файл:** `/home/slin/repos/orchestrator/Dockerfile`
|
||
|
||
**Что сделать:** Добавить `git` в `apt-get install` (или `apk add git` если alpine).
|
||
|
||
**Зачем:** Агенты (Claude CLI) не могут коммитить — используют dulwich (workaround), который коммитит от root и ломает permissions.
|
||
|
||
**Проверка:**
|
||
```bash
|
||
docker exec orchestrator git --version
|
||
```
|
||
|
||
### 2. Агенты коммитят от правильного пользователя
|
||
|
||
**Файл:** `/home/slin/repos/orchestrator/src/agents/launcher.py`
|
||
|
||
**Что сделать:** В env для subprocess.Popen добавить:
|
||
```python
|
||
env={
|
||
**os.environ,
|
||
"HOME": "/home/slin",
|
||
"GIT_AUTHOR_NAME": "claude-bot",
|
||
"GIT_AUTHOR_EMAIL": "claude-bot@mva154.local",
|
||
"GIT_COMMITTER_NAME": "claude-bot",
|
||
"GIT_COMMITTER_EMAIL": "claude-bot@mva154.local",
|
||
}
|
||
```
|
||
|
||
**Зачем:** Коммиты от root ломают `.git/objects` permissions. С git env vars коммиты будут от claude-bot.
|
||
|
||
**Проверка:**
|
||
```bash
|
||
docker exec orchestrator bash -c 'HOME=/home/slin GIT_AUTHOR_NAME=claude-bot git -C /repos/enduro-trails log --format="%an" -1'
|
||
```
|
||
|
||
### 3. Привязать task_id к agent_runs
|
||
|
||
**Файл:** `/home/slin/repos/orchestrator/src/agents/launcher.py`
|
||
|
||
**Что сделать:** Метод `launch()` должен принимать `task_id: int | None = None` и передавать его в INSERT:
|
||
```python
|
||
def launch(self, agent: str, repo: str, task_content: str = None, task_id: int = None) -> int:
|
||
...
|
||
cursor = conn.execute(
|
||
"INSERT INTO agent_runs (task_id, agent) VALUES (?, ?)",
|
||
(task_id, agent),
|
||
)
|
||
```
|
||
|
||
**Файл:** `/home/slin/repos/orchestrator/src/webhooks/plane.py`
|
||
|
||
**Что сделать:** В месте вызова `launcher.launch(agent, repo, task_desc)` добавить `task_id=task_id`:
|
||
```python
|
||
run_id = launcher.launch(agent, repo, task_desc, task_id=task_id)
|
||
```
|
||
|
||
**Проверка:**
|
||
```bash
|
||
docker exec orchestrator python -c "
|
||
import sqlite3
|
||
conn = sqlite3.connect('/app/data/orchestrator.db')
|
||
conn.row_factory = sqlite3.Row
|
||
for r in conn.execute('SELECT id, task_id, agent FROM agent_runs ORDER BY id DESC LIMIT 3').fetchall():
|
||
print(dict(r))
|
||
"
|
||
```
|
||
Ожидание: `task_id` не NULL для новых runs.
|
||
|
||
### 4. Автоматическое продвижение stage после CI green (без `:approved:`)
|
||
|
||
**Файл:** `/home/slin/repos/orchestrator/src/webhooks/gitea.py`
|
||
|
||
**Что сделать:** При получении `status` event с `state=success`:
|
||
1. Найти task по branch
|
||
2. Если task в stage `development` и QG `check_ci_green` проходит → автоматически продвинуть в `review` и запустить Reviewer
|
||
3. Аналогично: если task в stage `review` и push содержит review file → проверить QG и продвинуть
|
||
|
||
**Логика:**
|
||
```python
|
||
# В handle_status или handle_push:
|
||
if task.stage == "development" and ci_state == "success":
|
||
# Check QG
|
||
passed, reason = check_ci_green(repo, branch)
|
||
if passed:
|
||
advance_stage(task_id, "development", "review")
|
||
launch_reviewer(task_id, repo, branch)
|
||
```
|
||
|
||
**Зачем:** Сейчас после CI green Orchestrator просто логирует "waiting for CI" и ничего не делает. Нужен `:approved:` webhook для продвижения. По BRD это должно быть автоматически.
|
||
|
||
**Проверка:** Push в feature-ветку → CI green → stage автоматически меняется на `review` без ручного webhook.
|
||
|
||
### 5. Timeout для агентов (30 минут)
|
||
|
||
**Файл:** `/home/slin/repos/orchestrator/src/agents/launcher.py`
|
||
|
||
**Что сделать:** Добавить background thread/asyncio task который:
|
||
1. Через 30 минут после запуска проверяет — жив ли процесс
|
||
2. Если жив — kill -9
|
||
3. Записывает в agent_runs: `exit_code=-9, finished_at=now()`
|
||
4. Логирует warning
|
||
|
||
**Вариант реализации:**
|
||
```python
|
||
import threading
|
||
|
||
def _watchdog(self, pid: int, run_id: int, timeout: int = 1800):
|
||
"""Kill agent if it exceeds timeout."""
|
||
import time, signal
|
||
time.sleep(timeout)
|
||
try:
|
||
os.kill(pid, signal.SIGKILL)
|
||
logger.warning(f"Agent run_id={run_id} killed after {timeout}s timeout")
|
||
conn = get_db()
|
||
conn.execute(
|
||
"UPDATE agent_runs SET finished_at=datetime('now'), exit_code=-9 WHERE id=?",
|
||
(run_id,)
|
||
)
|
||
conn.commit()
|
||
conn.close()
|
||
except ProcessLookupError:
|
||
pass # Already finished
|
||
|
||
# В launch(), после Popen:
|
||
t = threading.Thread(target=self._watchdog, args=(proc.pid, run_id), daemon=True)
|
||
t.start()
|
||
```
|
||
|
||
**Проверка:** Запустить агента с заведомо долгой задачей → через 30 мин процесс убит, в БД exit_code=-9.
|
||
|
||
---
|
||
|
||
## Порядок выполнения
|
||
|
||
1. Dockerfile (git) → rebuild
|
||
2. launcher.py (git env + task_id + timeout)
|
||
3. plane.py (task_id в вызове launch)
|
||
4. gitea.py (auto-advance после CI green)
|
||
5. Rebuild + restart + smoke test
|
||
|
||
## Файлы для изменения
|
||
|
||
- `/home/slin/repos/orchestrator/Dockerfile`
|
||
- `/home/slin/repos/orchestrator/src/agents/launcher.py`
|
||
- `/home/slin/repos/orchestrator/src/webhooks/plane.py`
|
||
- `/home/slin/repos/orchestrator/src/webhooks/gitea.py`
|
||
|
||
## Ограничения
|
||
|
||
- НЕ менять порт 8500
|
||
- НЕ менять формат .env
|
||
- НЕ менять структуру БД (только использовать существующие колонки)
|
||
- НЕ добавлять новые зависимости (threading уже в stdlib)
|
||
- Тесты: `cd /home/slin/repos/orchestrator && python -m pytest tests/ -v`
|
||
|
||
## Команды проверки после деплоя
|
||
|
||
```bash
|
||
# 1. Git в контейнере
|
||
docker exec orchestrator git --version
|
||
|
||
# 2. Health
|
||
curl -s http://localhost:8500/health
|
||
|
||
# 3. Тесты
|
||
cd /home/slin/repos/orchestrator && python -m pytest tests/ -v
|
||
|
||
# 4. Smoke: запустить агента и проверить task_id
|
||
docker exec orchestrator python -c "
|
||
from src.agents.launcher import launcher
|
||
run_id = launcher.launch('architect', 'enduro-trails', 'test', task_id=99)
|
||
print(f'run_id={run_id}')
|
||
import sqlite3
|
||
conn = sqlite3.connect('/app/data/orchestrator.db')
|
||
conn.row_factory = sqlite3.Row
|
||
r = conn.execute('SELECT * FROM agent_runs WHERE id=?', (run_id,)).fetchone()
|
||
print(dict(r))
|
||
"
|
||
```
|