Files
wiki/tasks/multi-agent/DEV_TASK_ORCHESTRATOR_FIXES.md
2026-05-21 20:40:04 +03:00

197 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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))
"
```