Heimdall — Vision
Heimdall is the health-aware lane gateway and router for Pantheon. This document is the trajectory: where it is now, what’s next, and where it grows to. Contributors can pick a rung and jump in.
A lane is a provider × account × runtime triple — claude@mathew.dostal,
claude@dostalmathew, codex, gemini-3-pro, openrouter/grok, ollama-local
— each with its own long-lived credentials. Heimdall’s job is to know which lanes
are healthy and to act on that knowledge.
① Current — where it is (v0.36.0)
Heimdall runs as a headless Node/TypeScript service on http://localhost:4870
(override with PORT). Everything below actually runs today.
Sensing — 6/6 north-star providers. Layered signal sources (passive,
public_status, active_probe) resolve every lane to up, down,
out_of_credit, or degraded via resolveStatus(), a pure function that never
throws on malformed input. A corroboration policy guards against provider
false-positives. Adapters exist for Claude (both raw API keys and Claude Code
subscription OAuth tokens — the latter shells out to the real claude CLI, the
only active-probe in the codebase that spends real inference, since there’s no
free way to validate that credential type), Codex, Gemini, Kimi K3,
OpenRouter (nests as a gateway with independently-toggleable routes under one
credential, not a flat lane), and Ollama (liveness-only — no auth, no
degraded/out_of_credit concept for local inference). State persists to a
node:sqlite store (HEIMDALL_DB_PATH, default ~/.local/share/heimdall/heimdall.db
via resolveDefaultDbPath() — a real per-machine persistent file, not
in-memory, so the dashboard server, an MCP process, and the CLI can all open
the same DB at once). SLA-verified:
status correctness within 10 seconds of an actual state change, measured by
test/sla-harness/.
Full error codes, not just a status. Every lane carries a normalized
ErrorCode (rate_limit | quota_exceeded | billing_error | auth_failed |
server_error | network_error | unknown) alongside the free-text native
reason — never one instead of the other. GET /lanes and the dashboard
surface both. This isn’t just richer display: InProcessScheduler uses the
code to take real scheduling action — auth_failed lanes back off to a fixed
5-minute recheck instead of the fine ~5s cadence, since an auth failure has
no self-healing event to miss (only an operator fixing the credential helps);
every other error class keeps the SLA-driven cadence unchanged.
Model catalog. Each installation fetches its own live model list per
configured provider and stores a local enable/disable per model — newest
generation on by default, older generations available but off, never shipped in
git (an OSS install has its own real provider access). GET /available-route
and POST /route both substitute automatically when a declared model is
disabled or has vanished from the live catalog.
Scheduling. Pluggable per-lane Scheduler: MulticaAutopilotScheduler
(default, coarse cron registered as a Multica autopilot — honoring the “no
local box runners” rule) and InProcessScheduler (fine ~5s, suspect-lanes only,
backs off immediately on recovery, reset_at-aware when a real recovery time is
known). The flat ~5s cadence for suspect lanes with an unknown reset_at is
load-bearing for the 10-second SLA, not an arbitrary default — the SLA
harness’s own finding is that a scheduler ticking slower than ~5s risks missing
the 2-tick corroboration window. Backing this off further trades away a shipped,
tested guarantee and isn’t a routine tuning pass (see “Goals” below).
Actuation — status-only, by design. Heimdall senses and reports; it no
longer actuates Multica directly. Multica’s real API has no lever that can
stop new dispatch to an agent without cancelling its in-flight work (see
docs/decisions/DEC-hdl-multica-disable-contract.md), so heimdall#83’s
disable lever was retired rather than patched. Every lane’s ControlAdapter
is StubControlAdapter (loud logging via ActuationStub, never a silent
no-op); GET /lanes reports multica_agent_ids per lane so a downstream
actuator — Pantheon’s own facade — can build the real lever against
Multica’s real constraints.
Routing — pluggable, scored, and closed-loop. Route selection sits behind a
RoutingStrategy interface: priority (default), round-robin, scored
(weighted candidate scoring against config/routing-policy.yaml, deterministic
A/B experiment arms, generated rationale, a decision ledger), and off. Manual
lane override gates candidacy the same way actuation does. A caller that gets a
decision_id from POST /route can report back what actually happened via
POST /route/:decisionId/outcome — the ledger now records outcomes, not just
decisions.
Multi-account rotation. RotationController is wired into the live service
for any provider with 2+ credentialed lanes — detects Claude-specific cap
signals, marks the account capped, and can rotate to the next healthy one,
manually or via GET/POST /rotation/:provider[/rotate]. Not wired into the
live completion-call path itself (deliberately — see “Long-term vision”, the
“avoid a full gateway” boundary).
Heimdall’s own telemetry. GET /metrics (Prometheus text format) and a
dashboard Telemetry panel, aggregated entirely from local state — actuation
results, rotation events, model substitutions, routing decisions, lane counts.
Argus (or anything else OTEL/Prometheus-compatible) is a downstream consumer,
composed alongside the local recorder, never Heimdall’s only source of truth.
UI. A self-contained dashboard (no build step, no framework) — live lane
status, per-lane override/reset-at controls, add-lane form, routing-strategy
picker, model-catalog toggles, a read-only routing-policy panel (per-task-
type weights, headroom floor, cost preference, experiment status — the same
config/routing-policy.yaml the scored strategy reads, made visible without
reading YAML), and the telemetry panel. GET /docs and GET /docs/:slug
render the project’s own markdown docs in-app, with Mermaid diagrams
rendered client-side against a locally-vendored bundle — no CDN, no network
call, docs and diagrams browsable from the running service itself.
Standalone desktop app. A real, installable macOS app (Tauri v2,
app/) — a genuine single point of install, not just the headless service.
A Rust shell spawns Heimdall’s own compiled service as a sidecar (captures
the real login-shell PATH, binds a free port, wires a stable per-user
app-data directory for the SQLite DB and .env), health-checks it before
showing the dashboard, and provides a tray icon with close-to-tray behavior
and gh-CLI-backed self-update. Ad-hoc signed, single-machine target — no
Apple Developer Program distribution. Live-verified end to end, including
the actual release .app bundle installed and run standalone, not just the
dev-mode wrapper.
Pantheon integration. Heimdall’s real L2 descriptor (capabilities,
healthz, port, transport) is registered in pantheon-v2.
Installable CLI and agent onboarding. Heimdall installs as a real global
CLI, not just a repo checkout — one-liner: curl -fsSL
https://mdostal.github.io/heimdall/install.sh | bash. Not yet on the npm
registry (pantheon-heimdall isn’t published there yet — tracked as t-003
in .pHive/triage/queue.yaml); the install script installs straight from
this repo’s main branch (npm install -g git+https://github.com/mdostal/
heimdall.git#main), which npm builds locally via this package’s own
prepare script. Same end result for the operator either way — switching
scripts/install.sh’s INSTALL_SOURCE back to the plain package name is
the only change needed once publishing catches up. The heimdall bin
(bin/heimdall.js) is a cross-platform shim that
dispatches to the compiled CLI — heimdall lanes/route/route-outcome for
one-shot calls, and heimdall mcp to speak the MCP protocol over stdio.
heimdall agent init is the onboarding command: it detects which coding
harnesses (claude, codex) are actually installed on the machine,
idempotently registers Heimdall as an MCP server with each one (heimdall
agent status reports current registration state without changing anything),
and installs the four real usage skills (heimdall-lanes, heimdall-routing,
heimdall-models, heimdall-status) into the harness’s skills directory —
turning “clone the repo and read the source” into “install, run one command,
start asking your agent about lane health.” scripts/install.sh wraps the
same two steps (global install + agent init) with Node-version and PATH
error handling for the curl-to-bash path.
Honest gaps.
- Heimdall no longer has real Multica actuation to verify end-to-end — it’s
status-only by design now (
DEC-hdl-multica-disable-contract.md). The open item is on the Pantheon side: building the real disable lever against Multica’s actual constraints, informed by the mapping this repo now exposes onGET /lanes. Not tracked here — different repo, different planning. - Credentials come from local env vars (
.env) by default — standalone mode’s behavior, unchanged. Plugin-mode credential resolution through Portunus now has a real path:PantheonSecretCredentialSource(opt-in viaHEIMDALL_CREDENTIAL_SOURCE=pantheon) calls Pantheon Core’s own secrets facade, never Portunus directly.DEC-hdl-portunus-deferral.md’s prerequisite is fully met; the real shared-volume wiring between Portunus’s container and wherever this credential source runs is the remaining, separately-tracked follow-up (pantheon-v2’spantheon-secret-resolution-facadeepic, story C) — not assumed complete by this class existing alone.
② Goals — near-term next steps
Both items previously listed here (probe-cadence tuning; headroom/cost-tier
defaults) are done — closed by the hdl-backoff-policies epic: a pluggable
BackoffPolicy (static/progressive/exponential-progressive, operator-chosen,
per-provider overridable) replaces the flat cadence, and headroom/cost-tier
are now live-editable per-lane settings instead of env-var-only.
- Automatic headroom inference, explicitly deferred by that same epic as
its natural follow-on: whether a cheap, automatic headroom signal (e.g.
inferred from recent
out_of_creditfrequency) should feed the now-existing live-editable headroom setting, rather than requiring an operator to set it by hand. Needs an operator call on the actual inference approach, not a routine pass — manual tunability (already shipped) is the real prerequisite for this, not a blocker to it.
③ Long-term vision
Heimdall grows from a health gateway into a full health-aware router: the
component Auriga calls on every dispatch — input {task-type, est-cost,
constraints}, output {chosen lane + creds handle}, with the loop closed by
outcome feedback.
- Full multi-lane token routing. Every runner is used, routed by live health and headroom — premium/architecture work to Claude/Fable, bulk/grunt to cheaper lanes, images to the right image model, and never real feature work to a distrusted cheap tier. On rate-limit, swap lanes, don’t halt; recover, don’t recreate.
- Rotation stays credential-selection, not call-wrapping. The north star’s
own
avoidclause rules out Heimdall becoming a full LLM proxy — rotation answers “which account,” not “make this call for me.” That boundary is deliberate, not a gap to close. - An SLA harness as a first-class product surface, not just a test: continuously proving that routing decisions honor per-lane correctness and latency guarantees.
- Per-account long-lived tokens via Portunus — the prerequisite that makes cross-account sharing real. Minting and storing them harness-side is blocking for cross-account routing and is tracked as a dependency, not owned here.
- Two distribution modes, always. Like every Pantheon god, Heimdall is
open-source and ships standalone — now a real installable desktop app
(
app/), carrying its own dashboard/docs UI, usable from any harness that can spin up multiple agents — and as a Pantheon plugin (config through Vesta/Multica). Same core, two front doors — the descriptor is registered, the standalone side is real and dogfoodable, and plugin-side credential resolution has a real path now (PantheonSecretCredentialSource,DEC-hdl-portunus-deferral.md); the remaining blocker is the real shared-volume wiring between Portunus’s container and wherever this credential source runs, tracked inpantheon-v2.
Platform-wide, this rides Pantheon’s core principle: everything is swappable. Any language, model, plugin, or god can be toggled on/off and compared on metrics at every step — Heimdall is exactly the god that makes “compare lanes on live health and cost, then route” a first-class, measurable operation.
Good first contributions
- Widen the CLI
--format tableoutput or add a--watchmode over the existinggetLaneStatuses()core. - Extend the SLA harness (
test/sla-harness/) with new state-transition scenarios. - Make the routing-policy panel editable, not just read-only — the current
panel (
GET /routing-policy) is a deliberate read-only-first scope; aPOSTthat writes back toconfig/routing-policy.yaml(with the same validationPolicyLoaderalready does) would close the loop. - Harden
resolveStatus()against additional malformed-signal shapes with new table-driven tests insrc/core/status-model.test.ts.
New to the codebase? Start at src/main.ts (composeService()) — it wires every
piece together and is the fastest map of how sensing, scheduling, routing, and
actuation compose into the running service.