146 lines
14 KiB
Markdown
146 lines
14 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
|
||
---
|
||
|
||
# 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, строки ~566–571). Постфактум-парс (`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` (≈ стр. 559–573); проверка `_resolve_timeout` обслуживает override `developer`/`reviewer` (≈ стр. 661–679) |
|
||
| `src/config.py` | изменить — config для поднятого тайм-аута `developer`/`reviewer` (выделенные ключи и/или дефолт `agent_timeout_overrides_json`); обновить комментарии-паспорт (≈ стр. 115–126); проверить/при необходимости поднять `reaper_max_running_s` (≈ стр. 494–499) |
|
||
| `src/usage.py` | проверить/зафиксировать тестом — `record_usage` (`model=COALESCE(?, model)`) НЕ затирает launch-стамп при `model=None` (≈ стр. 207–230); `_extract_model` (≈ стр. 95–118) |
|
||
| `src/notifications.py` | проверить (правка, вероятно, не нужна) — `_stage_line` рендерит `· {model} · {effort}` из `agent_runs` для строки с `exit_code=-9` (≈ стр. 360–373, 498–542) |
|
||
| `src/db.py` | НЕ менять схему — `agent_runs.model` TEXT уже есть; проверить, что `get_running_agents` (≈ стр. 1370–1405) отдаёт 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, строки ~115–126) расширяется описанием поднятых
|
||
бюджетов `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/` минимальны и покрыты).
|