Files
wiki/tasks/orchestrator/PROGRESS_2026-06-02.md
2026-06-03 00:20:01 +03:00

10 KiB
Raw Permalink Blame History

Orchestrator — Журнал прогресса 2026-06-02

Большой день. Инцидент → root-fix → надёжная очередь. Хронология и итоги.


0. Контекст дня

Создала в Plane задачи ORCH-1..7. Plane-webhook поймал каждую и авто-запустил конвейер — но без фильтра по проекту, всё ушло в неправильный репо enduro-trails, наплодив мусор ET-010..016. Подход: стоп → чистка → защита → root-fix → надёжность.


1. ИНЦИДЕНТ: webhook авто-запуск (19:00)

Что случилось

  • Plane-webhook (id 93f0c342-a614-4248-9d0f-c107276f5620) срабатывал на ЛЮБОЕ issue в workspace, без фильтра по проекту
  • plane.py:91 hardcode repo=settings.default_repo → всё лилось в enduro-trails
  • Наплодило ветки/работу ET-010..016 в чужом репо
  • ⚠️ Позитив: автономность реально работает (analyst exit=0, auto-commit, worktree ORCH-2 сработал) — баг был в маршрутизации, не в движке

Меры (предохранитель)

  • 🛡️ Plane-webhook ДЕАКТИВИРОВАН в Plane postgres: UPDATE webhooks SET is_active=false → проверено is_active=f
  • 🧹 Вычищено: ветки ET-010..016 (git local+remote), worktree _wt/enduro-trails/* (root-owned → sudo rm), тестовые iso-A/iso-B, tasks≥19 + agent_runs в БД орка
  • Plane чист: orchestrator=7, enduro=5 (родные)
  • Заметка: docs/INCIDENT_2026-06-02_webhook_autorun.txt

Грабли (postgres-пароль)

  • \x27-экранирование кавычек через ssh+docker ЛОМАЕТСЯ → писать SQL в файл + psql -f, пароль из env контейнера
  • Plane postgres: контейнер plane-app-plane-db-1, db/user plane

2. ROOT FIX = ORCH-6 (Multi-repo) — ЗАМЕРЖЕН (PR #2)

ТЗ

DEV_TASK_ORCH6_MULTIREPO.md — реестр проектов (Plane id→repo+prefix) + фильтр webhook по проекту + resolve repo + plane_sync в правильный проект + prefix per project.

Реализация (Dev, Opus 4.8)

  • src/projects.pyProjectConfig + резолверы get_project_by_plane_id/get_project_by_repo/known_plane_project_ids
  • Реестр: enduro→enduro-trails/ET, orchestrator→orchestrator/ORCH, unknown→игнор
  • Затронуты: config.py, plane.py, db.py (get_next_work_item_id per project), plane_sync.py, gitea.py
  • ⚠️ Первый прогон упал по таймауту LLM на ~75% — дослала добивку (НЕ начинала заново, рабочее не теряем)

Проверено мной вживую + замержено

  • PR #2 смержен (merge commit b021ff7), main пересобран, health ok
  • 57 passed (20 новых), 9 fail — pre-existing baseline цел
  • Plane-webhook ВКЛЮЧЁН обратно (is_active=t)
  • Боевой тест защиты на проде:
    1. HMAC-подпись: без валидной → 401 Invalid signature
    2. Фильтр проекта: валидная подпись + unknown проект → 200 {"status":"ignored","reason":"unknown project"}, лог known: 2
    3. Задача НЕ создалась (11→11) — инцидент технически невозможен

3. ORCH-1 (F-2b) — Персистентная очередь задач — БАЗА ГОТОВА (PR #3)

Проблема (in-process)

  • launcher.launch() синхронно: Popen(claude) + 2 daemon-thread (watchdog 1800с + monitor: ждёт/коммитит/advance)
  • 8 точек: plane.py:189,234,308,389 + gitea.py:126,203,275,300
  • Беды: рестарт = агенты-сироты + потеря работы; нет лимита параллелизма; нет ретраев; webhook блокируется

Реализация (Dev, Opus 4.8) — ТЗ DEV_TASK_ORCH1_QUEUE.md

  • Таблица jobs (queued/running/done/failed) + atomic claim_next_job + хелперы (enqueue/mark/count/requeue/get/status_counts/recent)
  • src/queue_worker.py — drain-loop + max_concurrency + graceful stop
  • launcher.launch_job() + _finalize_job (done/requeue/failed+Telegram)
  • main.py lifespan: queue-recovery (requeue running на старте) после M-1
  • Webhook-хэндлеры (8 точек) → enqueue_job
  • GET /queue эндпоинт
  • config: max_concurrency=1, queue_poll_interval=2.0
  • Доки на сервере: docs/ARCHITECTURE.md, docs/ORCH-1_JOB_QUEUE.md

Проверено мной вживую

  • 76 passed (19 новых, вкл. атомарность claim на 8 потоках/20 jobs), 9 pre-existing fail
  • /queue живой: counts, max_concurrency, recent
  • PR #3 open, mergeable:True
  • Фиксы B-1/B-2/M-1/ORCH-2/ORCH-6 не тронуты

Метрики прогона

  • Dev orch1_queue: done, 12m26s, 337k токенов (in 294k / out 43k)

⚠️ Урок: resilience в базовый PR Dev НЕ встроил

Моё уточнение (preflight/429/backoff/breaker) пришло когда Dev уже ушёл в финал → в коде его нет. Правило: при дослыле уточнения в активную сессию — проверять, что оно реально вошло в КОД, а не только в отчёт «done».


4. ORCH-1b (Resilience-слой) — ГОТОВ (в PR #3, проверен на проде)

Итог (Dev orch1b_resilience, Opus 4.8) — проверено мной вживую

  • 7 коммитов поверх базы (4ef87a3..c23f000), запушено (local==remote), PR #3 mergeable
  • preflight.py, error_classifier.py, db _ensure_column (PRAGMA-safe), compute_backoff+Retry-After, CircuitBreaker
  • 110 passed (26 новых resilience, всего 34 в test_resilience), 9 fail — pre-existing 401
  • /queue отдаёт resilience.breaker (closed) + preflight_ok:true
  • preflight reason 2.1.142 (Claude Code)токены НЕ потрачены
  • классификатор: 429/overloaded→transient, traceback→permanent
  • ⚠️ Dev «остановился из осторожности» (решил что параллельная сессия пишет те же файлы — mtime менялись). Параллельной сессии НЕ БЫЛО — это его собственная активность. По факту всё довёл/запушил; пересборку сделала я.

Идея Славы (отличный вопрос про надёжность claude CLI)

Два РАЗНЫХ зверя, лечить раздельно:

A. CLI недоступен (бинарь/сеть) → дешёвый preflight:

  • os.path.exists(CLAUDE_BIN) + claude --version (timeout 5с — токены НЕ жжёт) + опц. TCP до endpoint
  • Кэш ~45с, FAIL → воркер не claim'ит job (остаётся queued), не падает
  • 🚫 НИКАКОГО prompt-ping (ping→pong) перед каждым job — это трата лимита

B. Rate limit 429 → предсказать НЕЛЬЗЯ, ловить на ВЫХОДЕ:

  • Парсить log/stderr на 429/rate limit/overloaded/Retry-After/quota
  • Классификатор transient vs permanent → разные ветки ретраев

C. Backoff для transient:

  • Колонки available_at + transient_attempts в jobs
  • claim: WHERE status='queued' AND (available_at IS NULL OR available_at<=now)
  • Exp backoff min(2^n*10, 600)с + уважать Retry-After

D. Circuit breaker:

  • 3 transient подряд → open: пауза 5 мин (CLI не дёргаем, лимит не выжигаем) + Telegram-алерт
  • half-open → 1 проба → ожил=closed, иначе снова open
  • отражать в /queue

Config (добавляется)

preflight_cache_ttl=45, backoff_base_seconds=10, backoff_max_seconds=600, transient_max_attempts=5, breaker_threshold=3, breaker_pause_seconds=300

⚠️ Урок: тест-алерты дёргают Славу

Dev гонял retry-тест с синтетическим repo r-retry/job 3 → fail-алерт долетел в Telegram, Слава испугался («Ау»). Проверка: /queue по нулям, в прод-БД нет r-retry — жил только в эфемерной pytest-БД. TODO ORCH-1b: подавить Telegram-нотификации в тестовых прогонах (нотификации только прод).

Требование к миграции БД

Безопасная: PRAGMA table_info-проверка перед ALTER (prod-база jobs уже живая).


5. Итоги дня

Что Статус
Инцидент webhook 🟢 локализован, вычищен
ORCH-6 multi-repo (root fix) 🟢 замержен PR #2, проверен на проде
Plane-webhook 🟢 включён обратно, защита боевая
ORCH-1 очередь 🟢 база готова PR #3, проверена
ORCH-1b resilience 🟢 готов в PR #3 (110 passed), проверен на проде

6. Следующие шаги

  • PR #3 (base + resilience) готов, проверен — ждёт ОК Славы на мерж
  • TODO: подавить Telegram-нотификации в pytest-прогонах (тест-алерты дёргают Славу)
  • Бэклог: ORCH-3 (S-2/S-3 rollback), ORCH-4 (M-3 stage-engine), ORCH-5 (M-7 idempotency/webhook dedup)

7. Висящие вопросы Славе

  • PR #19 (enduro-trails) — мержить?
  • PR #1 (orchestrator worktree) — мержить?
  • PR #2 (ORCH-6) — уже смержен