16 KiB
work_item, stage, author_agent, status, created_at, model_used
| work_item | stage | author_agent | status | created_at | model_used |
|---|---|---|---|---|---|
| ORCH-109 | analysis | analyst | ready-for-review | 2026-06-14 | 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) и способ их конфигурации (выделенные ключи vsagent_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 (заполняет архитектор).