Routing Health Check
SkillCommunicationOn-demand routing health check for /chat. Verifies every model (auto / Sonnet / Opus / Gemma-local / Claude Code) on BOTH text and voice, across every persona, and that messaging a persona in /chat is identical to messaging its Telegram bot.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Routing Health Check skill
What this skill tells your AI
The instructions your AI receives, as published by nbramia/lifeos in .claude/skills/routing-health/SKILL.md and read by ahel’s review.
Arguments: $ARGUMENTS
Modes:
- (default) — deterministic dispatch matrix + cheap liveness probes. No cloud cost beyond a single Haiku voice turn; no Claude Code workers are spawned. ~30–60s.
- --live — additionally runs a real generation per model on text AND voice (verified against the recorded routing model), plus one
claude_code-via-voice handoff for a no-handoff persona — which spawns a Claude Code worker and then kills it (process included). Real cloud cost (≈ one Opus + one Sonnet + local + Haiku turns, twice). A few minutes.
Why this exists
The /chat model picker (auto / Sonnet / Opus / Gemma (local) / Claude Code) and the voice surface route a turn through several independent layers: the web/voice client, model_override on /api/ask/stream, the orchestrator's model resolution + escalation, the whisper-relay voice gateway, and the Claude Code engine handoff. A regression in any one layer — a new picker value with no backend support, a persona that stops resolving, the voice gateway dropping model_override, the claude_code short-circuit breaking, the handoff gate suppressing a non-handoff persona — is silent until someone hits that exact cell. This check exercises the whole grid (2 surfaces × 5 models × every persona) on demand.
It also asserts the design invariant the project depends on: messaging a persona in /chat (text or voice) behaves identically to messaging that persona's Telegram bot — both resolve the same bot.persona preamble from the registry and call the same run_agent_loop via /api/ask/stream.
What it checks
- Inventory — every model-picker option value in
web/index.htmlis one the backend actually handles; enumerates personas fromsettings.list_http_personas(). - Persona ↔ Telegram equivalence —
/api/personasmatches the Telegram bot registry;resolve_persona(id)returns each bot's exactpersonapreamble; and empirically,run_agent_loopreceives the identical persona whether the turn arrives the/chatway (persona_id) or the Telegram way (personapreamble). - Text routing matrix — every persona × {
auto,opus,sonnet,gemma,claude_code}: the correct model id is resolved (mocked dispatch — no LLM calls),force_localis set for gemma, the persona preamble is passed through, andclaude_codeshort-circuits to aclaude_intenthandoff (no agent loop, no inline answer). - Voice forwarding + gate — whisper-relay forwards
model_overrideand bypasses the persona handoff gate forclaude_code(runs itstests/test_model_override.py). - Liveness — the deployed service actually emits
claude_intentand serves a real voice turn end-to-end. - --live only — a real generation per model on text and voice (asserted against the conversation's recorded routing model), and a live
claude_code-via-voice handoff for a no-handoff persona (spawned worker + killed, OS process included).
Instructions
Step 0: Pre-flight
!curl -s -m4 http://localhost:8000/health >/dev/null 2>&1 && echo "lifeos-api: up" || echo "lifeos-api: DOWN"; [ -x "$HOME/.venvs/lifeos/bin/python" ] && echo "venv: ok" || echo "venv: MISSING"; pgrep -f "voice_gateway.main" >/dev/null 2>&1 && echo "voice-gateway: up" || echo "voice-gateway: down (voice live-probes will fail)"
If lifeos-api is DOWN, stop and tell the user to start it (sudo systemctl start lifeos-api) — every live probe needs it. If the voice gateway is down, the voice liveness probe (and --live voice cells) will FAIL; note it but the rest still runs.
Step 1: Run the checker
Run from the repo root with the project venv. Include --live only if $ARGUMENTS contains --live:
~/.venvs/lifeos/bin/python .claude/skills/routing-health/scripts/routing_check.py [--live]
The script prints a [PASS]/[FAIL]/[WARN] line per check, a final ROUTING-HEALTH-VERDICT: line, and a ROUTING-HEALTH-JSON: summary blob. It cleans up its own probe conversations (across every persona scope) and, under --live, kills the worker session it spawns (and its OS subprocess). Suppress noisy warnings with 2>/dev/null.
Step 2: Report
Interpret the script's output (do not re-run the underlying tests yourself). Produce a concise report:
- Overall — 🟢 HEALTHY / 🟡 DEGRADED (any WARN) / 🔴 FAILING (any FAIL), taken from the verdict line.
- Coverage — state what actually ran: N personas × 5 models on text (always), voice forwarding + gate, liveness, and — if
--live— the live generation matrix and theclaude_codevoice handoff. - Equivalence — explicitly confirm or deny that
/chat(text + voice) behaves identically to the Telegram bots for personas (from theequivalence/*checks). - Failures — for each FAIL, name the cell and the detail, and point at where to look (table below).
- Cleanup — confirm probe conversations were deleted; under
--live, confirm no stray worker remains (the script'slive-voice/claude_code-cleanupline, and you may verify withpgrep -af "claude -p").
If the script itself crashed (a deterministic-harness FAIL line or a Python traceback), surface that and stop — the matrix didn't run.
Failure → where to look
| Failing check | Look at |
|---|---|
inventory/model-picker-options | web/index.html #modelPicker options vs the handled set in api/routes/chat.py (model_override resolution, ~L1243) |
equivalence/* | config/settings.py (resolve_persona, list_http_personas, telegram bot registry) and api/services/telegram.py (chat_via_api) |
text-matrix/<persona>/<model> | api/routes/chat.py ask_stream — model_override resolution + persona handling + the claude_code short-circuit |
voice/whisper-relay-* | ~/Code/whisper-relay adapter (src/voice_gateway/adapters/lifeos.py) + web/chat/voice.js (model_override on the voice FormData) |
live/*, live-text/*, live-voice/* | deployed service (restart lifeos-api); for voice cells, the whisper-relay gateway on :9788 |
Notes
- Read-mostly. The only writes are throwaway probe conversations (auto-deleted) and, under
--live, one Claude Code worker session — killed, OS process included (operator-kill alone leaves the process; see issue #379). - Requirements:
lifeos-apirunning; for voice checks, the whisper-relay gateway on :9788; for the local/gemmacells,lifeos-llmactive. - whisper-relay location defaults to
~/Code/whisper-relay— override withWHISPER_RELAY_DIR. If it's absent, the voice-forwarding checks WARN (skipped) rather than fail, and the verdict degrades to 🟡 rather than 🔴. - The deterministic matrix loads the app in-process (TestClient) and mocks
run_agent_loop, so it makes no real LLM calls and spawns nothing — it's safe to run frequently. The cost and side effects live entirely in--live.
Signals
- GitHub stars
- 30
- Forks
- 7
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
routing-health- Source
- github.com/nbramia/lifeos