The router previously recorded only completions (energy_observations),
not the routing decisions behind them, so 'how routing is performing' was
not answerable from data. This adds:
- route_decisions table + idempotent ensure_route_decisions (guarded
CREATE TABLE IF NOT EXISTS, never regenerates a live DB) gated by
logging.log_route_decisions; every decision kind (route/dispatch/chat/
passthrough/local-vision) is persisted best-effort via
persist_route_decision (never fails a request; only session_key, never
session_dir). The table is ensured on the write path (mirroring
proficiency_store._write -> ensure_columns) so a live DB that predates
the feature migrates safely.
- metrics.py aggregator moved quota_burn/scoring_coverage in from the
dispatcher (breaking a would-be circular import) and adds
recent_decisions/per_model/verdict_mix/top_proficiency; /health now
imports them and GET /metrics exposes the 7-key JSON (window-bounded,
loopback-only, no auth).
- observed_at indexes on energy_observations/verifications.
A terminal dashboard that polls the router's GET /metrics and renders
quota burn vs plan, per-model cost/energy/carbon, verdict mix, recent
routing decisions, and health warnings. Auto-refresh with last-good-data
error resilience and keyboard controls (q/Q/Ctrl+C quit, r refresh,
1-5 focus panels). Pins textual (deliberate UI-only dependency, imported
only by tui.py, never by the service).
Post a task to POST /route and print the full decision tree
(classification, candidates, selected model, est cost, rejections)
without dispatching a provider call. Supports --category/--tier/--context
overrides and --json. Route-only and no-spend.