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

7.2 KiB
Raw Blame History

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:

  1. Найти task по branch
  2. Если task в stage development и QG check_ci_green проходит → автоматически продвинуть в review и запустить Reviewer
  3. Аналогично: если 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 который:

  1. Через 30 минут после запуска проверяет — жив ли процесс
  2. Если жив — kill -9
  3. Записывает в agent_runs: exit_code=-9, finished_at=now()
  4. Логирует 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.


Порядок выполнения

  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

Команды проверки после деплоя

# 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))
"