Files
orchestrator/docs/architecture/adr/adr-0030-metrics-endpoint.md

7.6 KiB
Raw Permalink Blame History

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.
  • stagesdb.get_active_tasks_for_reconcile() + фильтр stage NOT IN ('done','cancelled') на слое metrics (helper намеренно отдаёт cancelled для ORCH-086 — не трогаем его инвариант); поля work_item/stage/age_in_stage_s/repo.
  • queuedb.job_status_counts() (+cancelled), глубина, сырьё ретраев (attempts/max_attempts/transient_attempts/в-backoff), worker.breaker.snapshot(), max_concurrency. Недоступный worker → breaker: null, не 500.
  • agents (liveness) — новый dedicated read-only helper db.get_running_agents() (НЕ расширение hot-path get_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, не ошибка.
  • costrunning (по running-job, часто null до завершения — честное сырьё, null ≠ ноль)
    • aggregate (новый helper db.agent_cost_totals(), COALESCE(SUM(...),0) по cost_usd/input_tokens/output_tokens/cache_read_tokens/cache_creation_tokens).
  • Kill-switch metrics_endpoint_enabled (env ORCH_METRICS_ENABLED, дефолт True): при False200 с {"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/, отдельная задача).