7.6 KiB
work_item, stage, author_agent, status, created_at, model_used
| work_item | stage | author_agent | status | created_at | model_used |
|---|---|---|---|---|---|
| ORCH-099 | architecture | architect | proposed | 2026-06-10 | claude-opus-4-8 |
adr-0030: Лёгкий read-only /metrics — сырьё о самом орке для sidecar (F1b)
- Статус: proposed
- Дата: 2026-06-10
- Задача: ORCH-099 (FND/F1a)
- Детальный ADR:
docs/work-items/ORCH-099/06-adr/ADR-001-metrics-endpoint.md
Контекст
Эпик автономного саморазвития, домен 0 «Фундамент». Рамка наблюдаемости (заказчик): наблюдатель
отделён от наблюдаемого — мозг мониторинга (пороги/алерты/история/Telegram) живёт в отдельном
sidecar-контейнере F1b (watchdog/), а орк отдаёт только сырьё, которое знает лишь он сам.
Сегодня такого источника нет: /health = {"status":"ok"}, /status = активные задачи, /queue —
«человеческий» снимок, перемешанный с конфигом демонов. Нет стабильного машинного контракта для
детекта застрявшей стадии / зависшего агента / деградации очереди / всплеска стоимости. F1b
заблокирована этой задачей. Self-hosting: прод общий с enduro-trails ⇒ эндпоинт обязан быть строго
read-only и never-raise.
Решение
Новый leaf-модуль src/metrics.py (build_metrics() -> dict, чистый, never-raise по разделам —
паттерн serial_gate.snapshot()) + тонкий эндпоинт @app.get("/metrics") в src/main.py (стиль
GET /queue). Только чтение существующих таблиц (tasks/jobs/agent_runs) и in-memory-снапшотов
- два read-only helper'а в
src/db.py.STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict- ключи/схема БД — не трогаются.
- Конверт + контракт версии:
schema_version(старт1),generated_at(UTC ISO-8601 — момент снимка, домен часов орка),clk_tck(os.sysconf("SC_CLK_TCK")), разделыstages/queue/agents/cost. Политика версии: аддитивные изменения НЕ бампят (sidecar обязан игнорировать незнакомые ключи и толерировать отсутствие опциональных); бамп — только при ломающем (rename/remove/retype). Forward-compatible контракт для F1b. stages—db.get_active_tasks_for_reconcile()+ фильтрstage NOT IN ('done','cancelled')на слое metrics (helper намеренно отдаётcancelledдля ORCH-086 — не трогаем его инвариант); поляwork_item/stage/age_in_stage_s/repo.queue—db.job_status_counts()(+cancelled), глубина, сырьё ретраев (attempts/max_attempts/transient_attempts/в-backoff),worker.breaker.snapshot(),max_concurrency. Недоступный worker →breaker: null, не 500.agents(liveness) — новый dedicated read-only helperdb.get_running_agents()(НЕ расширение hot-pathget_running_jobs()reaper'а, ORCH-065):agent/run_id/job_id/pid/runtime_s(=running_age_sотjobs.started_at)/model/effort. CPU-сырьё — вариант A: орк читает/proc/<pid>/stat(поля 14+15, utime+stime) →cpu_ticks; дельту не считает — арбитр «жив/завис» это sidecar (stateless-эмиссия).pid is None/мёртвый/нет/proc/не-Linux →cpu_ticks: null, не ошибка.cost—running(по running-job, частоnullдо завершения — честное сырьё,null≠ ноль)aggregate(новый helperdb.agent_cost_totals(),COALESCE(SUM(...),0)поcost_usd/input_tokens/output_tokens/cache_read_tokens/cache_creation_tokens).
- Kill-switch
metrics_endpoint_enabled(envORCH_METRICS_ENABLED, дефолтTrue): приFalse→200с{"schema_version":1,"enabled":false}(контракт остаётся парсимым). Операторский off-switch на общем инстансе. - Never-raise: каждый раздел — свой
try/except+logger.warning+ дефолт (null/[]/{});build_metrics()никогда не пробрасывает. Read-only: ни одногоINSERT/UPDATE/DELETE/CREATE/ALTER.
Альтернативы
- Расширить
/queue— отклонено: ломает байт-в-байт контракт (BR-6) + смешивает сырьё с человеческим снимком. - Prometheus/OpenMetrics — отклонено: заказчик задал тонкий кастомный sidecar (не Prometheus), контракт — JSON.
- Орк считает CPU-дельту сам — отклонено: требует состояния; stateful-арбитр это sidecar (C-1).
- Расширить SELECT
get_running_jobs()— отклонено: перенос инварианта hot-path reaper'а; изолируем dedicated helper. - Push в sidecar — отклонено: нарушает разделение C-1; зависший орк ⇒ pull падает = сам сигнал.
Последствия
- F1b разблокирована стабильным машинным контрактом; домен наблюдаемости стартует.
- Строго read-only + never-raise ⇒ near-zero риск для общего прод-конвейера (enduro-trails);
/health//status//queueбайт-в-байт; гейты/схема/machine-verdict-ключи не тронуты (NFR-5). schema_version+ аддитивно-толерантная политика ⇒ расширения не ломают F1b.- Плата: новая поверхность совместимости
/metrics↔F1b (митигейшн — единый репо контракта + версия); CPU-liveness Linux-специфичен (/proc; не-Linux →null). Топология/схема не меняются (sidecar и его сетевая достижимость — объём F1b). - Новый компонент + публичный контракт →
arch:major-change(хоть и аддитивно/read-only/обратимо); прод-деплой строго через staging-гейт (8501), без рестарта прод-контейнера. - Откат:
metrics_endpoint_enabled=False(мгновенный) или удаление модуля/эндпоинта/helper'ов — без следов в БД/схеме.
Связи
adr-0002 (job-queue/circuit-breaker — источник queue-сырья), adr-0011 (job-reaper —
get_running_jobs/pid/liveness-семантика, изоляция hot-path), adr-0026 (терминал {done,cancelled}
— фильтр stages), adr-0017 (serial_gate — паттерн leaf snapshot()/never-raise), adr-0020
(frontmatter-контракт — стиль версионируемого контракта). Прямой потребитель — F1b (sidecar
watchdog/, отдельная задача).