diff --git a/MEMORY.md b/MEMORY.md index a35613f..31ada12 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -148,6 +148,16 @@ docker exec fr24-tracks-fr24 grep "flight-summary" /app/fr24_worker.py ## Проекты +## Orchestrator — состояние 2026-06-02 (журнал: `tasks/orchestrator/PROGRESS_2026-06-02.md`) +- **Инцидент дня:** создала ORCH-1..7 в Plane → webhook авто-запустил конвейер БЕЗ фильтра проекта → мусор ET-010..016 в `enduro-trails`. Подход стоп→чистка→защита→root-fix. +- ✅ **ORCH-6 multi-repo (root fix)** замержен **PR #2** (`b021ff7`): реестр `src/projects.py` (Plane id→repo+prefix), фильтр webhook по проекту (unknown→ignored), resolve repo. Проверен на проде: HMAC + project-filter режут чужие проекты, задача не создаётся. **Plane-webhook включён обратно** (`is_active=t`) — теперь безопасно. +- ✅ **ORCH-1 очередь задач** база готова **PR #3** (76 passed): таблица `jobs`, `queue_worker.py`, atomic claim, `max_concurrency`, ретраи, restart-safe (requeue running на старте), `/queue` эндпоинт. Заменил in-process daemon-потоки. +- 🔄 **ORCH-1b resilience** (поверх PR #3): идея Славы про надёжность claude CLI. **A.** дешёвый preflight (`claude --version`, кэш 45с — токены НЕ жжёт, FAIL→job ждёт); 🚫 НИКАКОГО prompt-ping (трата лимита). **B.** 429 ловить на ВЫХОДЕ (паттерны в логе), классификатор transient/permanent. **C.** exp backoff + `available_at` + Retry-After. **D.** circuit breaker (3 fail→пауза 5мин, CLI не дёргаем, алерт→half-open). +- ⚠️ **Урок:** при дослыле уточнения в активную сессию Dev — проверять что оно вошло в КОД, а не только в отчёт «done» (resilience в базовый PR #3 НЕ попал). +- ⚠️ **Урок:** тест-алерты Dev (синтетич. `repo r-retry`/job N) дёргают Славу в Telegram. Проверка реальности — `/queue` + прод-БД. TODO ORCH-1b: подавить нотификации в pytest. +- ⚠️ **Грабли postgres-пароля Plane:** `\x27`-экранирование через ssh+docker ломается → SQL в файл + `psql -f`, пароль из env контейнера `plane-app-plane-db-1`. +- **Висят:** PR #19 (enduro-trails), PR #1 (orchestrator worktree) — мержить? Бэклог: ORCH-3 (rollback), ORCH-4 (stage-engine), ORCH-5 (idempotency). + ## Multi-Agent Orchestrator (enduro-trails) - **ЕДИНОЕ ИМЯ ВЕЗДЕ = `orchestrator`** (решено 02.06.2026): - Gitea-репо: `admin/orchestrator` (переименован из `agent-dev`) diff --git a/memory/2026-06-02.md b/memory/2026-06-02.md index a8e2b6d..f87d7f3 100644 --- a/memory/2026-06-02.md +++ b/memory/2026-06-02.md @@ -437,3 +437,76 @@ ssh slin@82.22.50.71 " ### Следующий бэклог ORCH (после ORCH-6 merge+webhook on) - ORCH-1 (F-2b очередь задач вместо in-process daemon) — следующий приоритет - ORCH-3 (S-2/S-3 rollback), ORCH-4 (M-3 stage-engine), ORCH-5 (M-7 идемпотентность webhook dedup) + +--- + +## ORCH-6 ЗАМЕРЖЕН + Webhook ВКЛЮЧЁН (2026-06-02 ~21:00) — проверено вживую + +### Сделано (с ОК Славы «Делай») +- ✅ **PR #2 смержен** в main orchestrator (HTTP 200, merge commit `b021ff7`) +- ✅ Контейнер пересобран из main, health ok +- ✅ В main также прилетел `test_git_worktree.py` (ORCH-2 хвост) — норма +- ✅ **Plane-webhook ВКЛЮЧЁН обратно**: `UPDATE webhooks SET is_active=true` → `is_active=t`, UPDATE 1 + +### Боевой тест предохранителя (двойная защита, подтверждено на проде) +1. 🛡️ HMAC-подпись: webhook без валидной подписи → `401 Invalid signature` (секрет 40 симв настроен) +2. 🛡️ Фильтр проекта: webhook с ВАЛИДНОЙ подписью от неизвестного проекта → `200 {"status":"ignored","reason":"unknown project"}`, лог `ignoring event from unknown project (known: 2)` +3. ✅ Задача НЕ создалась (11→11) — инцидент технически невозможен + +### Грабли (повтор из инцидента — записать чтоб не наступать) +- Postgres-пароль Plane: `\x27`-экранирование кавычек через ssh+docker ЛОМАЕТСЯ. Рабочий способ — писать SQL в файл и `psql -f`, пароль брать из env Plane-контейнера (`len=5`) +- Plane postgres: контейнер `plane-app-plane-db-1`, db/user `plane` + +--- + +## ORCH-1 (F-2b) — Персистентная очередь задач (2026-06-02 ~21:00) + +### Контекст проблемы (in-process архитектура) +- `launcher.launch()` синхронно: `subprocess.Popen(claude)` + 2 daemon-thread (`_watchdog` таймаут-киллер 1800с + `_monitor_agent` ждёт/коммитит/advance stage) +- 8 точек вызова: `plane.py:189,234,308,389` + `gitea.py:126,203,275,300` +- Беды: рестарт орка = агенты-сироты + потеря работы; нет лимита параллелизма; нет ретраев; webhook блокируется на спавне + +### ТЗ +- `tasks/orchestrator/DEV_TASK_ORCH1_QUEUE.md` (снайперское, все file:line собраны заранее) +- Решение: таблица `jobs` (queued/running/done/failed) + atomic `claim_next_job` + `queue_worker.py` петля + `max_concurrency` + ретраи + requeue running на старте (restart-safe) + `/queue` эндпоинт + +### ✅ Базовая очередь готова (PR #3) — проверено мной вживую +- **76 passed** (19 новых тестов очереди, вкл. атомарность claim на 8 потоках/20 jobs), 9 fail — те же pre-existing 401 +- `/queue` живой: counts, max_concurrency, recent +- PR #3 open, mergeable:True +- Фиксы B-1/B-2/M-1/ORCH-2/ORCH-6 не тронуты +- Живой прогон: queued→running→done, ретрай, restart-safe + +### ⚠️ Resilience-слой Dev в базовый PR #3 НЕ встроил +- Моё уточнение (preflight/429/backoff/breaker) пришло когда Dev уже ушёл в финал T8-T12 → в коде НЕТ ни `preflight.py`, ни классификатора 429, ни breaker, ни колонок backoff +- Урок: при дослыле уточнения в активную сессию Dev — проверять, что оно реально вошло в код, а не только в отчёт «done» + +--- + +## ORCH-1b (Resilience-слой) — дослан отдельной задачей (2026-06-02 ~21:00) + +### Идея Славы (отличный вопрос про надёжность claude CLI) +- Два РАЗНЫХ зверя, лечить раздельно: + - **CLI недоступен** (бинарь/сеть) → дешёвый preflight `claude --version` + TCP, кэш 45с, токены НЕ жжёт. Мёртв → job ждёт в очереди, не падает + - **Rate limit 429** → предсказать НЕЛЬЗЯ; prompt-ping перед запуском = трата лимита, ТАК НЕ ДЕЛАТЬ. Ловить 429 на ВЫХОДЕ (паттерны в логе: `429`/`rate limit`/`overloaded`/`Retry-After`), классифицировать transient vs permanent +- **Backoff** для transient: exp + `available_at`/`next_attempt_at` колонка в очереди + уважать `Retry-After`. Очередь = идеальное место для backoff +- **Circuit breaker**: 3 фейла подряд → пауза 5 мин (CLI не дёргаем, лимит не выжигаем) + алерт → half-open проба → закрыли +- Ретраи раздельные: transient (429) ≠ code-fault (attempts=2) + +### Статус +- Задача `orch1b_resilience` запущена на Dev (Opus 4.8), поверх той же ветки/PR #3 (фокусные коммиты) +- Требует: безопасная миграция БД через PRAGMA-проверку (prod-база `jobs` уже живая) + +### ⚠️ Урок: тест-алерты дёргают Славу +- Dev гонял retry-тест с синтетическим `repo r-retry` / job 3 → fail-алерт долетел в Telegram, Слава испугался («Ау») +- Проверка: `/queue` всё по нулям, в прод-БД никакого `r-retry`/job 3 нет — жил только в эфемерной pytest-БД +- **TODO для ORCH-1b**: подавить Telegram-нотификации в тестовых прогонах (нотификации только прод, не pytest) + +### Следующие шаги +- Дождаться `orch1b_resilience`, проверить вживую (preflight без трат токенов, 429-классификатор, backoff, breaker, миграция) +- Слить PR #3 (base queue + resilience) ОДНИМ куском после полной проверки +- Дальше бэклог: ORCH-3 (S-2/S-3 rollback), ORCH-4 (M-3 stage-engine), ORCH-5 (M-7 idempotency/webhook dedup) + +### Висящие вопросы Славе +- PR #19 (enduro-trails), PR #1 (orchestrator worktree) — мержить? +- PR #2 уже смержен (ORCH-6) diff --git a/memory/heartbeat-state.json b/memory/heartbeat-state.json new file mode 100644 index 0000000..9f30d6a --- /dev/null +++ b/memory/heartbeat-state.json @@ -0,0 +1,11 @@ +{ + "lastChecks": { + "email": null, + "calendar": null, + "weather": null, + "orchestrator_health": "2026-06-02T21:04:00Z", + "open_prs": ["PR #19 enduro-trails", "PR #1 orchestrator worktree", "PR #2 orchestrator multi-repo"] + }, + "lastHeartbeat": "2026-06-02T21:04:00Z", + "note": "First heartbeat-state.json created during 2026-06-02 21:04 UTC cron poll" +} \ No newline at end of file diff --git a/tasks/orchestrator/PROGRESS_2026-06-02.md b/tasks/orchestrator/PROGRESS_2026-06-02.md new file mode 100644 index 0000000..dbbb6b3 --- /dev/null +++ b/tasks/orchestrator/PROGRESS_2026-06-02.md @@ -0,0 +1,137 @@ +# 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.py` — `ProjectConfig` + резолверы `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) + +### Идея Славы (отличный вопрос про надёжность 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 | + +## 6. Следующие шаги +- Дождаться `orch1b_resilience`, проверить вживую (preflight без трат токенов, 429-классификатор, backoff, breaker, безопасная миграция) +- Слить PR #3 (base + resilience) ОДНИМ куском после полной проверки +- Бэклог: 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) — ✅ уже смержен diff --git a/tasks/orchestrator/STATUS.md b/tasks/orchestrator/STATUS.md index 30f6b84..50d5199 100644 --- a/tasks/orchestrator/STATUS.md +++ b/tasks/orchestrator/STATUS.md @@ -1,10 +1,25 @@ # Статус проекта: Мультиагентная разработка ПО -**Дата обновления:** 2026-05-31 16:45 UTC +**Дата обновления:** 2026-06-02 21:10 UTC **Ревьюер:** Стрим --- +## 📌 Свежее (2026-06-02) — подробности в `PROGRESS_2026-06-02.md` + +| Работа | Статус | +|--------|--------| +| Инцидент webhook автозапуск (мусор в enduro-trails) | 🟢 локализован, вычищен | +| **ORCH-6 multi-repo** (root fix: фильтр проекта + repo/prefix per project) | 🟢 замержен **PR #2**, проверен на проде | +| Plane-webhook | 🟢 включён обратно (`is_active=t`), защита боевая (HMAC + project filter) | +| **ORCH-1 очередь задач** (персистентная, restart-safe, retry, max_concurrency) | 🟢 база готова **PR #3** (76 passed), проверена | +| **ORCH-1b resilience** (preflight + 429-классификатор + backoff + circuit breaker) | 🔄 в работе поверх PR #3 | + +**Дальше:** дождаться resilience → проверить вживую → слить PR #3 одним куском. Бэклог: ORCH-3 (rollback), ORCH-4 (stage-engine), ORCH-5 (idempotency). +**Висят:** PR #19 (enduro-trails), PR #1 (orchestrator worktree) — мержить? + +--- + ## Общая зрелость | Фаза (из BRD) | Статус | Комментарий | diff --git a/tasks/orchestrator/reports/dev-2026-06-02-orch1b-resilience.md b/tasks/orchestrator/reports/dev-2026-06-02-orch1b-resilience.md new file mode 100644 index 0000000..89ace52 --- /dev/null +++ b/tasks/orchestrator/reports/dev-2026-06-02-orch1b-resilience.md @@ -0,0 +1,31 @@ +# Dev Report: ORCH-1b RESILIENCE (preflight + 429 + backoff + circuit breaker) +Дата: 2026-06-02 +Статус: IN PROGRESS + +## Задача +Надстройка над базовой очередью ORCH-1 (PR #3, ветка feature/ORCH-1-job-queue): +A. Дешёвый preflight (CLI/сеть доступны, кэш ~45с, токены НЕ тратит) +B. 429-классификатор (transient/permanent) +C. Backoff + available_at + transient_attempts (миграция БД безопасная) +D. Circuit breaker в воркере (open/half-open/closed) ++ config, тесты, деплой, доки. + +## Сделано +- [x] T1: прочитал ТЗ (раздел ДОПОЛНЕНИЕ), db.py, config.py, queue_worker.py, launcher.py, main.py, test_queue.py +- [ ] A: preflight.py +- [ ] B: error_classifier.py +- [ ] C: backoff + миграция БД +- [ ] D: circuit breaker +- [ ] config +- [ ] tests +- [ ] деплой +- [ ] доки + +## Изменённые файлы +(заполняется по ходу) + +## Результат +(в конце) + +## Проблемы и решения +(по ходу)