168 lines
16 KiB
Markdown
168 lines
16 KiB
Markdown
---
|
||
work_item: ORCH-109
|
||
stage: analysis
|
||
author_agent: analyst
|
||
status: ready-for-review
|
||
created_at: 2026-06-14
|
||
model_used: claude-opus-4-8
|
||
---
|
||
|
||
# 01 — BRD (бизнес-требования): ORCH-109 — timeout budgets + launch-time model telemetry для developer/reviewer
|
||
|
||
Work Item: **ORCH-109** · Repo: **orchestrator** · Стадия: analysis
|
||
|
||
## 1. Бизнес-контекст и проблема
|
||
|
||
Инцидент **ORCH-104** (runs 658/659/660, прод-watchdog 1800s) вскрыл **два независимых дефекта**
|
||
в подсистеме запуска агентов и телеметрии:
|
||
|
||
**Дефект A — недостаточный wall-clock бюджет для тяжёлых ролей.**
|
||
Агенты `developer` и `reviewer` на сложных задачах **честно** упираются в общий тайм-аут
|
||
`agent_timeout_seconds = 1800` и убиваются watchdog'ом (`launcher._watchdog → stop_process`,
|
||
exit 143 / -9). Этот тайм-аут — единый для ВСЕХ ролей, хотя `developer` (effort `xhigh`,
|
||
кодирующая роль) и `reviewer` объективно требуют больше времени, чем механические роли
|
||
(`tester`/`deployer`, effort `medium`). Существует механизм per-agent override
|
||
(`_resolve_timeout` + `agent_timeout_overrides_json`), но в проде он пуст → все роли получают 1800s.
|
||
|
||
**Дефект B — потеря модели в телеметрии при оборванном прогоне.**
|
||
Модель агента (`agent_runs.model`) пишется **только постфактум** — из финального usage-JSON
|
||
прогона в `launcher._monitor_agent → usage.record_usage` (`_extract_model`). Убитый по тайм-ауту
|
||
прогон **не успевает эмитить финальный JSON** → `_extract_model` возвращает `None` →
|
||
`record_usage` пишет `model=COALESCE(None, model)` = остаётся **NULL**. В результате карточка
|
||
Telegram-трекера (`notifications._stage_line`) и снимок `GET /metrics`/`GET /queue`
|
||
(`db.get_running_agents`) показывают `model=null` именно тогда, когда что-то пошло не так — в
|
||
момент, когда модель/эффорт критичны для разбора инцидента.
|
||
|
||
Существующий прецедент уже решает половину задачи: **эффорт стампится в момент launch**
|
||
(`launcher._spawn`, ORCH-087, `UPDATE agent_runs SET effort=?`), потому что CLI его в result-JSON
|
||
не возвращает. Модель резолвится в той же точке (`resolve_agent_model`, строка 559), но **в БД на
|
||
launch не пишется** — стампится только эффорт. ORCH-109 распространяет ту же гарантию на модель.
|
||
|
||
**Сопутствующие проверки (производные от A и B):**
|
||
- Поведение оборванного (timeout-killed) прогона в трекере и status-комментариях: модель и эффорт
|
||
должны быть видны даже если финальный JSON не записан.
|
||
- Нужен ли отдельный guard: не пускать timeout-killed `developer`/`reviewer` автоматически дальше
|
||
по конвейеру (`development → review`, `review → testing`) без явного salvage-режима.
|
||
|
||
**Установленные факты (по коду, не изобретать):**
|
||
- `agent_runs.model` — колонка `TEXT` (NULLABLE), уже существует (`db._ensure_column`); **миграция
|
||
не нужна**.
|
||
- `record_usage` уже использует `model=COALESCE(?, model)` — то есть постфактум-парс уже
|
||
**сохраняет** ранее проставленное значение и не затирает его `NULL`'ом. Не хватает только
|
||
записи на launch.
|
||
- `_resolve_timeout(agent)` уже умеет per-agent override через `agent_timeout_overrides_json`;
|
||
малформный JSON → откат на глобальный дефолт + лог (never-break).
|
||
- Кросс-инвариант reaper: `reaper_max_running_s = 3600` с зафиксированным в `config.py` правилом
|
||
«MUST be > max agent_timeout + grace» (Tier-3 backstop job-reaper'а, ORCH-065).
|
||
|
||
## 2. Объём (scope)
|
||
|
||
### В объёме
|
||
- **Launch-time стамп модели:** записывать резолвенную `resolve_agent_model(...)` в
|
||
`agent_runs.model` в момент launch (`launcher._spawn`), рядом со стампом эффорта (ORCH-087).
|
||
- **Конфигурируемый поднятый wall-clock бюджет для `developer` и `reviewer`** через config-override,
|
||
**без изменения** бюджета остальных ролей (`analyst`/`architect`/`tester`/`deployer`).
|
||
- **Сохранение постфактум-enrich:** `usage.record_usage` остаётся источником обогащения
|
||
модели/токенов/стоимости из usage-JSON, но **перестаёт быть единственным источником истины** о
|
||
модели (launch-стамп — первичный, JSON — уточняющий).
|
||
- **Видимость при timeout/kill:** строка стадии трекера и status-комментарии показывают реальные
|
||
модель + эффорт для оборванного прогона (model не `null`).
|
||
- **Guard анти-salvage:** гарантия (и регресс-тест), что timeout-killed прогон
|
||
(`exit_code != 0`, в т.ч. -9/-15/143) **не продвигает** стадию автоматически в следующую без
|
||
явного решения.
|
||
- **Обновление документации/комментариев** по конфигу тайм-аутов (`config.py`, `.env.example`).
|
||
- **Тесты**, покрывающие все перечисленные FR.
|
||
|
||
### Вне объёма
|
||
- Изменение model-routing: все 6 агентов остаются на `claude-opus-4-8` (ORCH-41 G3 не включается).
|
||
- Любые изменения `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключей / схемы БД
|
||
(колонка `agent_runs.model` уже есть — миграции нет).
|
||
- Изменение тайм-аута для ролей кроме `developer`/`reviewer`.
|
||
- **Salvage / возобновление** недоделанной работы убитого прогона (поднять «как было», дописать,
|
||
переиспользовать частичный результат) — в объёме ТОЛЬКО гарантия не-продвижения, не salvage.
|
||
- Изменения транспорта Telegram/Plane (`send_telegram`/комментарии) — только использование уже
|
||
доступных полей.
|
||
- Перезапуск/деплой прод-контейнера в рамках задачи (self-hosting безопасность).
|
||
|
||
## 3. Заинтересованные стороны
|
||
|
||
- **Заказчик/Owner (Слава)** — инициатор; нуждается в надёжной телеметрии для разбора инцидентов и
|
||
в адекватных бюджетах тяжёлых ролей при пакетном автономном прогоне (эпик ORCH-088).
|
||
- **Оператор self-hosting** — потребитель карточки трекера и `GET /metrics`/`GET /queue`; без модели
|
||
в карточке теряет ключевой контекст инцидента.
|
||
- **Сам конвейер (self-hosting)** — затрагивается поведение запуска агентов; общий прод-инстанс
|
||
обслуживает и enduro-trails (тайм-аут — глобальная per-agent настройка, не repo-scoped).
|
||
|
||
## 4. Бизнес-требования (BR)
|
||
|
||
- **BR-1** — Резолвенная модель агента сохраняется в `agent_runs.model` **в момент launch**, рядом
|
||
с эффортом, а не только постфактум из usage-JSON. Значение присутствует на строке прогона с
|
||
момента запуска и переживает любой исход прогона.
|
||
- **BR-2** — Постфактум-парс usage/model (`usage.record_usage`) сохраняется как **обогащение**, но
|
||
**не как единственный источник истины**: при отсутствии/обрыве финального JSON launch-стамп модели
|
||
не теряется.
|
||
- **BR-3** — Wall-clock тайм-аут для `developer` и `reviewer` поднимается и **настраивается через
|
||
config-override**, **без изменения** тайм-аута остальных ролей; механизм покрыт тестом/проверкой.
|
||
- **BR-4** — При timeout/kill (оборванный прогон без финального JSON) строка стадии в трекере и
|
||
status-комментарии показывают **реальную модель (не `null`) и эффорт**.
|
||
- **BR-5** — Timeout-killed прогон `developer`/`reviewer` **не продвигается** автоматически на
|
||
следующую стадию без явного salvage-режима; поведение зафиксировано регресс-тестом. (Анализ
|
||
определяет, нужен ли отдельный guard поверх существующей гарантии «advance только при чистом
|
||
exit + зелёный QG».)
|
||
- **BR-6** — Документация и комментарии по конфигу тайм-аутов обновлены (паспорт изменения внутри
|
||
`config.py` + `.env.example`).
|
||
|
||
## 5. Нефункциональные требования (NFR)
|
||
|
||
- **NFR-1 — Обратная совместимость / нулевая регрессия.** Стамп модели аддитивен (колонка уже
|
||
существует, миграции нет). Дефолтный тайм-аут ролей, кроме `developer`/`reviewer`, не меняется;
|
||
при пустом override-конфиге поведение байт-в-байт прежнее.
|
||
- **NFR-2 — never-raise / never-break.** Сбой стампа модели (ошибка БД) **не блокирует** launch
|
||
(та же `try/except`-изоляция, что у стампа эффорта). Малформный/невалидный timeout-конфиг →
|
||
откат на глобальный дефолт + WARNING, прогон не падает.
|
||
- **NFR-3 — Неприкосновенность контрактов.** `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`,
|
||
machine-verdict ключи (`verdict:`/`result:`/`deploy_status:`/`staging_status:`/`security_status:`/
|
||
`coverage_status:`), схема БД — **не трогаются**.
|
||
- **NFR-4 — Сохранение reaper-инварианта.** Любой поднятый бюджет `developer`/`reviewer` обязан
|
||
сохранять `reaper_max_running_s > max(резолвенный тайм-аут любого агента) + agent_kill_grace_seconds`
|
||
(Tier-3 backstop ORCH-065); иначе job-reaper может реапнуть **здоровый** долгоиграющий прогон до
|
||
срабатывания его собственного watchdog'а. Если новый бюджет нарушает неравенство —
|
||
`reaper_max_running_s` поднимается синхронно (решение архитектора).
|
||
- **NFR-5 — Self-hosting безопасность.** Изменение не рестартит/не роняет прод-контейнер, не
|
||
трогает deploy-путь, безопасно для общего инстанса (enduro-trails не затронут негативно).
|
||
- **NFR-6 — Наблюдаемость in-flight.** Модель становится видна в `GET /metrics`/`GET /queue`
|
||
(`db.get_running_agents`) **во время** прогона, а не только после завершения (побочное улучшение
|
||
launch-стампа).
|
||
|
||
## 6. Допущения и ограничения
|
||
|
||
- Тайм-аут — **глобальная per-agent** настройка (не repo-scoped): поднятие бюджета
|
||
`developer`/`reviewer` действует на все репо. Для enduro это благоприятно/нейтрально.
|
||
- Колонка `agent_runs.model` уже существует и NULLABLE — повторная запись/COALESCE безопасны.
|
||
- CLI не возвращает effort в result-JSON (причина launch-стампа эффорта ORCH-087); модель в JSON
|
||
возвращается, но только при успешном финале — отсюда необходимость launch-стампа модели.
|
||
- Точные числовые значения новых бюджетов (`developer`/`reviewer`) и способ их конфигурации
|
||
(выделенные ключи vs `agent_timeout_overrides_json`) — решение архитектора/Owner в рамках FR-3;
|
||
BRD фиксирует только **способность + инвариант NFR-4 + тест**.
|
||
- Salvage недоделанной работы — отдельная возможность, вне этой задачи.
|
||
|
||
## 7. Критерии успеха
|
||
|
||
Модель агента видна (не `null`) в трекере, status-комментариях и `/metrics` для ЛЮБОГО исхода
|
||
прогона, включая timeout-kill; бюджеты `developer`/`reviewer` подняты и конфигурируемы без влияния
|
||
на прочие роли и без нарушения reaper-инварианта; timeout-killed прогон не «протекает» в следующую
|
||
стадию; всё покрыто тестами; конфиг задокументирован. Детальные PASS/FAIL — `03-acceptance-criteria.md`.
|
||
|
||
## 8. Риски
|
||
|
||
- **R-1** — Поднятие бюджета выше `reaper_max_running_s − grace` → ложный reap здорового прогона
|
||
(NFR-4). Митигируется sanity-тестом конфига и/или синхронным поднятием `reaper_max_running_s`.
|
||
- **R-2** — Постфактум-enrich затирает корректный launch-стамп при странном JSON. Митигируется
|
||
семантикой COALESCE (NULL не затирает) + тестом enrich-кейсов.
|
||
- **R-3** — Гонка двух писателей `exit_code` (`_record_kill` = -9 и `_monitor_agent` = `proc.wait()`)
|
||
не должна влиять на телеметрию модели (модель — отдельная колонка). Подтверждается тестом FR-4.
|
||
- **R-4** — Глобальность тайм-аута: поднятие для enduro-developer могло бы маскировать зависший
|
||
прогон. Митигируется тем, что Tier-3 backstop reaper'а сохраняется (NFR-4).
|
||
|
||
Детали рисков и архитектурные трейд-оффы — `10-tech-risks.md` (заполняет архитектор).
|