7.2 KiB
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.
Проверка:
docker exec orchestrator git --version
2. Агенты коммитят от правильного пользователя
Файл: /home/slin/repos/orchestrator/src/agents/launcher.py
Что сделать: В env для subprocess.Popen добавить:
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.
Проверка:
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:
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:
run_id = launcher.launch(agent, repo, task_desc, task_id=task_id)
Проверка:
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:
- Найти task по branch
- Если task в stage
developmentи QGcheck_ci_greenпроходит → автоматически продвинуть вreviewи запустить Reviewer - Аналогично: если task в stage
reviewи push содержит review file → проверить QG и продвинуть
Логика:
# В 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 который:
- Через 30 минут после запуска проверяет — жив ли процесс
- Если жив — kill -9
- Записывает в agent_runs:
exit_code=-9, finished_at=now() - Логирует warning
Вариант реализации:
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.
Порядок выполнения
- Dockerfile (git) → rebuild
- launcher.py (git env + task_id + timeout)
- plane.py (task_id в вызове launch)
- gitea.py (auto-advance после CI green)
- 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
Команды проверки после деплоя
# 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))
"