12 KiB
work_item, stage, author_agent, status, created_at, model_used
| work_item | stage | author_agent | status | created_at | model_used |
|---|---|---|---|---|---|
| ORCH-099 | analysis | analyst | ready-for-review | 2026-06-10 | claude-opus-4-8 |
01 — BRD (бизнес-требования): ORCH-099 — FND/F1a: лёгкий /metrics в орке (отдать сырьё)
Work Item: ORCH-099 · Repo: orchestrator · Стадия: analysis
1. Бизнес-контекст и проблема
Задача — фундаментный кирпич F1a домена 0 «Фундамент» эпика автономного саморазвития
(docs/epics/self-evolution.md). Архитектурная рамка наблюдаемости зафиксирована заказчиком
(Слава, 09.06) и для аналитика — установленный факт, не предмет переизобретения:
- C-1/C-1б: наблюдатель ОТДЕЛЁН от наблюдаемого. Мониторинг живёт в отдельном sidecar-контейнере
(
watchdog/, рантайм — свой Dockerfile + сервис в compose), а НЕ внутри орка. Если орк упал/завис/съел память — sidecar жив и репортит это. - C-2/C-3: без внешнего плеча, тонкий стек (хост впритык: RAM 171Mi free, диск 92% — НЕ Grafana/Prometheus).
- Разделение ответственности: орк отдаёт только сырьё (лёгкий read-only
/metrics— свои внутренние данные, которые знает только он сам), БЕЗ логики мониторинга/порогов/алертов/хранения. Мозг (пороги, алерты, свой Telegram-канал, история) — это F1b (sidecar), отдельная задача.
Боль, которую закрывает задача. Сегодня у орка нет машинного «сырья» о самом себе в одной
точке. /health отдаёт лишь {"status":"ok"}, /status — список активных задач, /queue —
богатый, но «человеческий» снимок очереди, перемешанный с конфигом демонов. Ни один из них не даёт
sidecar'у структурированный, стабильный КОНТРАКТ для детекта: застрявшая стадия, зависший агент
(liveness по pid/CPU), деградация очереди (breaker open, рост failed), всплеск стоимости токенов.
Без этого источника весь домен наблюдаемости (F1b и далее) слеп и не может стартовать.
Self-hosting контекст. Орк дорабатывает сам себя; прод-контейнер общий для всех проектов.
/metrics обязан быть строго read-only и never-raise — он не должен ни при каких входных
данных уронить или притормозить прод, обслуживающий enduro-trails.
2. Объём (scope)
В объёме
- Новый read-only HTTP-эндпоинт (
GET /metrics), отдающий JSON-снимок сырья о самом орке. - Четыре раздела сырья: активные стадии задач, очередь jobs, agent-liveness,
стоимость/токены (
agent_runs). - Новый leaf-модуль
src/metrics.py— сборка снимка из БД (чистый, never-raise, без побочных эффектов), по образцуsnapshot()-функций (serial_gate/task_deps/cancel). - Документирование формата
/metricsкак контракта для sidecar (F1b) вdocs/architecture/README.md+ запись вCHANGELOG.md. - Pytest-покрытие: структура ответа, never-raise, read-only-инвариант.
Вне объёма
- ❌ Любая логика мониторинга: пороги, алерты, Telegram, оценка «застрял/завис», хранение истории — это F1b (sidecar).
- ❌ Сам sidecar-контейнер (
watchdog/, Dockerfile, compose-сервис) — отдельная задача F1b. - ❌ Хостовые/контейнерные/внешние метрики (диск/RAM/CPU хоста, docker.sock, пинг Plane/Gitea/Anthropic) — их собирает sidecar, не орк.
- ❌ Изменение
STAGE_TRANSITIONS/QG_CHECKS/check_*/ схемы БД / любых machine-verdict ключей. - ❌ Дашборд/UI (упомянут в F1 эпика как отдельный последующий шаг).
- ❌ Прометей-совместимый text-формат — отдаём JSON (контракт под конкретный sidecar; OpenMetrics не требование заказчика).
3. Заинтересованные стороны
- Заказчик: Слава (рамки наблюдаемости F1, эпик саморазвития).
- Прямой потребитель контракта: будущий sidecar F1b (
watchdog/) — читает/metricsпо HTTP. Задача F1b заблокирована этой (ORCH-099 — источник контракта). - Затрагивается: прод-инстанс орка (общий с enduro-trails) — поэтому жёсткое требование read-only/never-raise.
- Принимает результат: reviewer/tester конвейера + Слава как владелец рамок.
4. Бизнес-требования (BR)
- BR-1 — Эндпоинт сырья. Орк предоставляет HTTP
GET /metrics, отдающий JSON с четырьмя разделами: (a) активные стадии задач, (b) очередь jobs, (c) agent-liveness, (d) стоимость/токены. Состав полей каждого раздела — см. TRZ §3 (FR-1…FR-4). - BR-2 — Стадии задач. По каждой незавершённой задаче отдаётся
work_item, текущаяstageи «как давно в стадии» (секунды) — сырьё для детекта застреваний sidecar'ом. - BR-3 — Очередь jobs. Отдаются счётчики по статусам (
queued/running/failed/…), глубина очереди, информация о ретраях и состояние circuit-breaker'а — сырьё для детекта деградации. - BR-4 — Agent-liveness. По каждому running-job отдаётся
agent,run_id,pid,runtime_sи сырьё для alive-детекта (CPU-тики pid либо данные, по которым sidecar посчитает CPU-дельту). sidecar — арбитр «жив/завис»; орк лишь поставляет факты. - BR-5 — Стоимость/токены. Отдаётся текущая (по running-job) и агрегированная стоимость/токены
из
agent_runs(cost_usd,input/output/cache_*токены) — сырьё для cost-наблюдаемости (D3). - BR-6 — Аддитивность. Существующие
/health,/status,/queueостаются байт-в-байт прежними по контракту;/metricsдобавляется рядом, ничего не ломая. - BR-7 — Документированный контракт. Формат
/metricsзафиксирован вdocs/architecture/README.mdкак стабильный контракт для sidecar (F1b) +CHANGELOG.md.
5. Нефункциональные требования (NFR)
- NFR-1 — Read-only. Эндпоинт НИЧЕГО не мутирует: не пишет в БД, не запускает/останавливает процессы, не рестартит, не дёргает внешние API. Только SELECT'ы + чтение in-memory-снимков демонов.
- NFR-2 — Never-raise (по полям). Любая ошибка при сборе отдельного поля/раздела → это поле
получает
null(или раздел — безопасный дефолт), но эндпоинт возвращает 200 и валидный JSON, никогда не 500. Эталон —serial_gate.snapshot()с fallback вexcept. - NFR-3 — Лёгкость. Только быстрые запросы к локальной SQLite + чтение уже посчитанных in-memory снапшотов; без тяжёлых вычислений, без сетевых вызовов, без сканирования файлов/git. Цель — единичные мс на типовом объёме (десятки задач/jobs).
- NFR-4 — Self-hosting-безопасность. Эндпоинт физически не способен повлиять на прод-конвейер (следствие NFR-1) — безопасен на общем инстансе с enduro-trails.
- NFR-5 — Совместимость БД/гейтов.
STAGE_TRANSITIONS/QG_CHECKS/check_*/ machine-verdict ключи / схема БД — НЕ трогаются. Задача читает существующие таблицы (tasks/jobs/agent_runs) и существующие in-memory снапшоты. - NFR-6 — Стабильность контракта. Формат — аддитивный и версионируемый (поле
schema_version), чтобы будущие расширения не ломали уже написанный sidecar.
6. Допущения и ограничения
- Данные уже есть в БД. Все нужные поля присутствуют:
tasks(stage, work_item_id, updated_at, created_at),jobs(status, attempts, max_attempts, transient_attempts, available_at, pid, run_id),agent_runs(agent, started_at, finished_at, model, effort, cost_usd, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens). Новые колонки/таблицы не нужны. - Breaker-состояние — in-memory (
queue_worker.worker.status()/CircuitBreaker.snapshot()); читается без БД. - CPU-тики pid читаются из
/proc/<pid>/stat(Linux прод-контейнер). Допущение: контейнер Linux; при отсутствии/гонке (процесс уже умер) — полеnull(NFR-2), НЕ ошибка. Это согласуется с рамкой C-1: «орк лёг → endpoint недоступен = сам сигнал тревоги» — детект делает sidecar. - Арбитраж liveness — на стороне sidecar. Орк не решает «завис/жив»; он лишь отдаёт
pid,runtime_sи (по возможности) CPU-тики; sidecar считает дельту между опросами. - Формат — JSON, не OpenMetrics/Prometheus (рамка C-3: тонкий кастомный sidecar, не Prometheus).
7. Критерии успеха
GET /metrics отдаёт лёгкий, read-only, never-raise JSON с четырьмя разделами сырья;
/health//status//queue не сломаны; формат задокументирован как контракт sidecar; pytest
зелёный. Детальные PASS/FAIL — 03-acceptance-criteria.md.
8. Риски
- Гонка чтения
/proc/<pid>/stat(процесс умер между выборкой job и чтением proc) → закрывается NFR-2 (null, не ошибка). - Расхождение контракта
/metricsи ожиданий sidecar (F1b) → закрывается BR-7 (контракт в одном репо, документирован) +schema_version(NFR-6). - Соблазн «протащить» в
/metricsлогику алертинга → закрывается scope-границей (вне объёма) и NFR-1.
Детальная оценка технических рисков — 10-tech-risks.md (заполняет архитектор).