Files
orchestrator/docs/work-items/ORCH-109/02-trz.md
claude-bot 082629fa28
All checks were successful
CI / test (push) Successful in 3m53s
analyst(ET): auto-commit from analyst run_id=661
2026-06-14 00:14:40 +03:00

14 KiB
Raw Blame History

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

02 — ТЗ (TRZ): ORCH-109 — timeout budgets + launch-time model telemetry для developer/reviewer

Work Item: ORCH-109 · Repo: orchestrator · Стадия: analysis

ТЗ описывает конкретные изменения к реализации, выведенные из BRD и фактического кода. Архитектурное обоснование/решения (выбор «выделенные config-ключи vs agent_timeout_overrides_json», точные числовые бюджеты, синхронная правка reaper_max_running_s) — задача архитектора (06-adr).

1. Сводка изменения

Две независимые, но связанные правки в подсистеме запуска агентов:

  1. Launch-time стамп модели. В launcher._spawn резолвенная resolve_agent_model(...) (уже вычисляется на launch, строка ~559) записывается в agent_runs.model в той же DB-сессии, что и стамп эффорта (ORCH-087, строки ~566571). Постфактум-парс (usage.record_usage, model=COALESCE(?, model)) сохраняется как обогащение и уже не затирает launch-значение NULL'ом. Следствие: модель присутствует на строке прогона с момента запуска, переживает timeout-kill и видна in-flight в GET /metrics/GET /queue.

  2. Конфигурируемый поднятый wall-clock бюджет для developer/reviewer. _resolve_timeout(agent) должен возвращать поднятый бюджет для developer и reviewer, конфигурируемый и не затрагивающий прочие роли; механизм покрыт тестом. Сохраняется never-break (малформный конфиг → глобальный дефолт) и кросс-инвариант reaper (reaper_max_running_s > max(timeout)+grace).

Плюс верификационные требования: телеметрия timeout-killed прогона (модель+эффорт не null) и guard анти-salvage (timeout-killed прогон не продвигает стадию).

2. Задействованные модули / пути

Путь Действие
src/agents/launcher.py изменить — стамп model в _spawn рядом с effort (≈ стр. 559573); проверка _resolve_timeout обслуживает override developer/reviewer (≈ стр. 661679)
src/config.py изменить — config для поднятого тайм-аута developer/reviewer (выделенные ключи и/или дефолт agent_timeout_overrides_json); обновить комментарии-паспорт (≈ стр. 115126); проверить/при необходимости поднять reaper_max_running_s (≈ стр. 494499)
src/usage.py проверить/зафиксировать тестом — record_usage (model=COALESCE(?, model)) НЕ затирает launch-стамп при model=None (≈ стр. 207230); _extract_model (≈ стр. 95118)
src/notifications.py проверить (правка, вероятно, не нужна) — _stage_line рендерит · {model} · {effort} из agent_runs для строки с exit_code=-9 (≈ стр. 360373, 498542)
src/db.py НЕ менять схему — agent_runs.model TEXT уже есть; проверить, что get_running_agents (≈ стр. 13701405) отдаёт launch-стампнутую модель для running-job
src/stage_engine.py проверить — путь продвижения стадии не advance'ит прогон с exit_code != 0 (guard FR-5); правка только если найден разрыв
.env.example обновить — задокументировать ключи тайм-аута developer/reviewer (BR-6)
tests/test_orch109_timeout_model.py (новый) создать — покрытие FR-1…FR-5
CHANGELOG.md, CLAUDE.md (паспорт), docs/architecture/README.md (модель/эффорт-секция) обновить в том же PR (правило агентов №2)

3. Функциональные требования

FR-1 — Launch-time стамп модели (BR-1)

В launcher._spawn, после model = resolve_agent_model(agent, project_id), резолвенное значение записывается в agent_runs.model для текущего run_id в момент launch, по образцу стампа эффорта (ORCH-087):

  • Запись в той же открытой conn, что и стамп эффорта (допустимо объединить в один UPDATE agent_runs SET model=?, effort=? WHERE id=? — решение реализации).
  • Пустой резолв (model == "", CLI-дефолт без --model) → пишется NULL (как эффорт: effort or None), чтобы суффикс модели в трекере корректно опускался.
  • Инвариант: значение agent_runs.model присутствует с момента launch и не зависит от исхода прогона.
  • never-raise (NFR-2): сбой записи изолирован try/except + WARNING; launch продолжается.

FR-2 — Постфактум-enrich сохраняет launch-стамп (BR-2)

usage.record_usage остаётся источником обогащения (токены/стоимость/модель из usage-JSON), но:

  • При usage is None или usage.get("model") is None (оборванный/малформный JSON) launch-стамп модели не затирается (текущая семантика model=COALESCE(?, model) это уже обеспечивает — требование зафиксировать тестом, не регрессировать).
  • При наличии непустой модели в JSON enrich уточняет значение (например, полный provider-prefixed id или фактический fallback-model) — допустимая перезапись непустым на непустое.
  • Семантика парсинга _extract_model (приоритет modelUsage → top-level model) — без изменений.

FR-3 — Конфигурируемый поднятый тайм-аут developer/reviewer (BR-3)

  • _resolve_timeout(agent) возвращает поднятый бюджет для agent in {"developer","reviewer"}, конфигурируемый, детерминированный, и не затрагивающий прочие роли (они продолжают получать глобальный agent_timeout_seconds, если для них нет override).
  • Механизм: либо документированный дефолт agent_timeout_overrides_json, либо выделенные ключи (например agent_timeout_developer_s/agent_timeout_reviewer_s) — выбор архитектора; контракт FR-3 — резолв per-agent поднятого бюджета.
  • never-break (NFR-2): малформный/невалидный конфиг → откат на глобальный дефолт + WARNING (поведение _resolve_timeout сохраняется).
  • Кросс-инвариант (NFR-4): итоговый max(резолвенный тайм-аут) + agent_kill_grace_seconds обязан оставаться < reaper_max_running_s; при нарушении — синхронно поднять reaper_max_running_s.

FR-4 — Телеметрия timeout-killed прогона (BR-4)

Для прогона с exit_code != 0 без финального usage-JSON (timeout-kill, _record_kill стампит -9):

  • Строка стадии трекера (notifications._stage_line) рендерит · {short_model} · {effort} с реальными значениями (модель не null), т.к. оба стампнуты на launch (FR-1 + ORCH-087).
  • db.get_running_agents (источник GET /metrics/GET /queue) отдаёт launch-стампнутую модель и для running-job (in-flight видимость, NFR-6).
  • Изменения notifications.py, вероятно, не требуются (рендер уже читает model); требование — верифицировать тестом, что при стампе на launch значение долетает.

FR-5 — Guard анти-salvage timeout-killed прогона (BR-5)

  • Timeout-killed прогон (exit_code != 0, в т.ч. -9/-15/143) developer/reviewer не продвигает стадию (development → review, review → testing) автоматически.
  • Существующий контракт (advance только при чистом exit-коде + зелёный exit-гейт; иначе attempts<max → queued, иначе failed + Telegram — launcher._monitor_agent/queue_worker/ job_reaper) реализует это структурно.
  • Требование: анализ подтверждает достаточность существующей гарантии; поведение фиксируется регресс-тестом. Отдельный guard в коде добавляется только если тест выявит разрыв.
  • salvage-режим НЕ вводится (вне объёма) — задача гарантирует не-продвижение, не возобновление.

FR-6 — Документация конфига (BR-6)

  • Комментарий-паспорт в config.py (блок ORCH-7, строки ~115126) расширяется описанием поднятых бюджетов developer/reviewer и ссылкой на reaper-инвариант (NFR-4).
  • .env.example несёт соответствующие ключи с дефолтами = боевым значениям (канон ORCH-101).
  • Сквозная документация (CLAUDE.md, docs/architecture/README.md — таблица «модель/эффорт по ролям») обновляется в том же PR.

4. Изменения API

Нет. Ни одного нового/изменённого endpoint'а. GET /metrics и GET /queue отдают тот же контракт (schema_version: 1) — поле agents[].model лишь начинает заполняться для running-job (аддитивное улучшение данных, не контракта; sidecar обязан толерировать, ORCH-099 NFR-6).

5. Изменения схемы БД

Нет. Колонка agent_runs.model (TEXT, NULLABLE) уже существует (db._ensure_column, инициализация init_db). Никаких CREATE/ALTER/новых таблиц. Меняется только момент и частота записи в существующую колонку (launch + опциональный постфактум-enrich).

6. Требования к новым/изменённым QG checks

Нет. QG_CHECKS / check_* / _parse_* / machine-verdict ключи — не трогаются. Задача целиком вне слоя Quality Gate (подсистема launch/телеметрия/конфиг). FR-5 опирается на существующий exit-code-контракт продвижения, не на новый гейт.

7. Совместимость / регресс

  • Обратная совместимость: стамп модели аддитивен; при пустом timeout-override поведение байт-в-байт прежнее (NFR-1). Никаких kill-switch не требуется — изменение не вводит новых ветвей риска (стамп модели всегда безопасен; тайм-аут конфигурируем и fail-safe на глобальный дефолт).
  • Область раската: стамп модели — все репо/роли (безопасно). Поднятый тайм-аут — только developer/reviewer (все репо, т.к. тайм-аут глобален per-agent); прочие роли неизменны.
  • Обратимость: вернуть тайм-аут — снять override-конфиг (откат на 1800s). Launch-стамп модели отката не требует (чистое улучшение телеметрии).
  • Кросс-каттинг (NFR-4): при поднятии бюджета выше reaper_max_running_s grace синхронно поднять reaper_max_running_s (ORCH-065). Sanity-тест конфига стережёт инвариант.
  • never-raise (NFR-2): обе правки изолированы; сбой не роняет launch и не падает на старте при плохом env.
  • Self-hosting (NFR-5): ни рестарта прода, ни изменения deploy-пути; общий инстанс безопасен.
  • Полный регресс tests/ остаётся зелёным; coverage-гейт (ORCH-027) удовлетворён новым тест-файлом (изменения в src/ минимальны и покрыты).