Routing-explain — why a worker was picked or skipped (kc0nz)
Routing used to be a black box once the decision was made: reasons[] explained the winning
fleet, but nothing recorded which OTHER candidates existed, or why a worker never even reached
the router (a quota cooldown, a retired worker, a missing credential, a fleet.json restriction).
buildRoutingSnapshot() (src/core/routingExplain.ts) turns a RouteDecision plus those
candidate-pool exclusion notes into one structured "routing block" — every candidate's
status (picked / considered / skipped), its rank, cost/tier metadata, resolved model, and
the reason lines specific to it (split out from the generic classification/mode reasons).
The SAME block is surfaced in three places:
cyberine route "<task>"(dry run) — the human-readable output now adds aconsidered (in pool, not picked):section and askipped (never reached the router):section after the worker-profiles block;cyberine route --json "<task>"adds aroutingkey with the full snapshot.- The TUI run detail view (
npm run tui, open any run) — aroutingline under the run header showskind/tier,confidence,source, the picked worker ids, and aN considered, M skipped before dispatchcount. Shown whenever the run carries a routing block; nothing to toggle. - The MCP
inspect_runtool — its response carries aroutingkey (same shape) whenever the run has one, via the sharedbuildInspectionView()(src/store/runInspection.ts).
# Dry run: see picked/considered/skipped candidates and why
cyberine route "refactor the entire auth module across every file"
cyberine route --json "refactor the entire auth module across every file" | jq .routing
# Background dispatch: the routing block lands on run.json at dispatch time
cyberine run --bg "fix the off-by-one bug in pagination"
cyberine agents # find the runId, then:
cat .local/runs/<runId>/run.json | jq .routingSchema (RoutingSnapshot, src/core/routingExplain.ts):
{
"version": 2,
"capturedAt": "2026-09-03T12:00:00.000Z",
"kind": "code-large",
"tier": "complex",
"confidence": 0.9,
"source": "deterministic",
"recommendedMode": "compete",
"picked": ["claude", "codex"],
"failover": { "primary": ["claude", "codex"], "chain": [], "reasons": [], "billing": {} },
"candidates": [
{
"id": "claude",
"status": "picked",
"rank": 1,
"tier": "strong",
"costModel": "subscription",
"resolvedModel": "claude-sonnet-4-6",
"reasons": ["claude available: preferred pool (subscription)", "claude preferred: tier=strong suits code-large task"]
},
{ "id": "opencode-deepseek", "status": "considered", "rank": null, "reasons": ["opencode-deepseek excluded: metered native API (preferred-pool workers available)"] },
{ "id": "loop-kimi-build", "status": "skipped", "rank": null, "reasons": ["loop-kimi-build in quota cooldown (rate-limit) until 3:00:00 PM (~12m) - skipped"] }
],
"generalReasons": ["classified as code-large: edit verbs + size signals (repo-wide / multi-file / long spec)", "recommendedMode=compete"]
}Only the heuristic auto-router path populates run.json's routing field. It is built in
src/launchBg.ts right after routeTaskSmart() decides the fleet, and handed to the child
process via CYBERINE_ROUTING_SNAPSHOT_JSON so WorkflowEngine.run() can attach it to the run
at creation time — so it appears on cyberine run --bg, chat /run, and the TUI's "new task"
key. routing is ABSENT (not null — the key is missing) when:
- the foreground
cyberine run "<task>"(no--bg) never writes a run.json at all — there is nothing to attach it to; - an explicit
--fleet <ids>orCYBERINE_BUILD_FLEETenv pinned the fleet — noRouteDecisionwas made to describe; - a
fleet.jsonrouting map matched the task's kind and short-circuited the heuristic router entirely (resolveRoutingForKind); - the run.json predates this field (every reader —
describeInvalidRun,buildInspectionView, the TUI,inspect_run— treats it as optional and tolerates its absence).
Known limitations and deferred work:
- A worker excluded purely for a missing credential (no CLI login, no API key) is silently absent
from
candidatesrather than appearing asstatus: "skipped"—availableBuildWorkerIds()only records an exclusion NOTE for cooldown, retirement, and fleet.json restrictions today, not for a bare "no key configured" case. Pass anotesOutarray to see exactly what it does record. - Internal
routeTask()call sites that select workers for something other than the top-level dispatch (src/engine/execStrategy.ts'sGroupOrchestratormember selection,WorkflowScriptRuntime's per-script routing,beamSearch.ts's candidate exploration) do not attach a routing block to any run — this feature covers the ONE dispatch-time decision that picks a run's fleet, not every internal routing call in the codebase. - The snapshot is captured once, at dispatch time — it is not re-verified against what actually
got built into adapters (
buildDefaultAdapters/buildFleetAdapterscould still drop a picked worker for a last-minute reason, e.g. a race with a credential expiring mid-dispatch); it answers "what did the router decide and why", not "what definitely ran". src/eval/routerEval.tsstill recomputesrouteTask()fresh from each historical run's prompt rather than reading a storedroutingblock — the schema here is shaped to make that direct read straightforward (matchingkind/tier/confidence/sourcenaming) but the harness itself has not been switched over.
Regression guard: npm run selftest:routing-explain — asserts snapshot categorisation against
both a synthetic RouteDecision and the real router, asserts availableBuildWorkerIds()'s
notesOut parameter is additive, and (the core guard) asserts a fresh WorkflowEngine.run()
call actually persists the routing block to run.json ON DISK, survives a RunStore.get()
round-trip, and flows through buildInspectionView() — plus a backward-compatibility check that
a routing-less Run still round-trips cleanly.
Machine-readable route recommendation — cyberine route --json (T62)
cyberine route --json "<task>" emits a stable JSON object designed for non-Cyberine callers (e.g. a Claude Code subagent dispatcher) that need to ask Cyberine "what tier should this task run at"...
OpenTelemetry GenAI export — CYBERINE_OTEL_ENDPOINT (kyv0t)
run.json/events.jsonl is a proprietary, Cyberine-only format with no interop. Setting CYBERINEOTELENDPOINT turns on an exporter that maps RunStore run/step/worker events onto spans and ships them...