Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Statusline and observability

Castellan exposes complementary observability surfaces that share one schema:

  1. statusline.jsonObservabilitySnapshot (file + MCP-readable)
  2. castellan run --json — typed castellan-events NDJSON stream
  3. castellan telemetry --json — SQLite history (sessions, vitals, cost rollups) for CI/metrics

TUI (castellan dashboard) and headless (castellan statusline refresh) write the same snapshot — no parallel JSON schemas.

statusline.json

Default path: ~/.config/castellan/statusline.json.

WriterWhen
castellan dashboardEvery ~250ms while the TUI is open
castellan statusline refreshOne-shot (or --watch) without ratatui
{
  "schema_version": 1,
  "written_at": "2026-07-18T04:00:00+00:00",
  "connected": true,
  "socket": "/Users/you/.config/castellan/castellan.sock",
  "bus": { "socket": "up", "events": "live", "panes": "socket" },
  "agents": { "total": 3, "idle": 1, "working": 1, "blocked": 0, "done": 1, "unknown": 0 },
  "selected": { "pane_id": "w1:p1", "agent": "claude", "status": "working" },
  "vitals": { "circulation_score": 0.82, "deferred_ratio": 0.1, "cold_zone_count": 0, "pane_pulse": 0.4 },
  "genome": { "genome_id": "gen-abc123", "generation": 4, "lineage_depth": 2 },
  "budget": { "mcp_headroom": 40, "zone_headroom": 12, "deferred_ratio": 0.1 },
  "session_cost": { "usd": 0.42, "tokens": 18300 },
  "live_pressure": { "pane": 0.55 },
  "plan_verify": { "passed": false, "tool": "shell", "reason_code": "missing_plan" },
  "bio": { "kind": "quorum_fired", "summary": "phase=commit ρ=0.80" },
  "topology_apply": { "tick": 3, "born": 1, "pruned": 0, "wake_threshold": 0.4 },
  "potential_delta": null,
  "telemetry": { "agent_transitions": 42 },
  "last_events": []
}

potential_delta (Φ) is omitted or null unless the wire exposes a real value — never a fake number. Footer shows Φ=— when unknown.

Dual-bus honesty (bus)

FieldMeaning
socketup / down — mux Unix socket ping
eventslive / stale / idle — EventBus activity
panessocket — Enter→attach uses the socket pane tree (in-process --mux run panes are a separate host; events may still fan out on the dual bus)

Read paths

ConsumerHow
MCPcastellan_multiplexer_status — returns schema_version, written_at, age_secs, stale, plus status body
Headless / CIcastellan statusline refresh --json then jq .schema_version
History DBcastellan telemetry --limit 20 (or dashboard --history)
castellan multiplexer ensure
castellan statusline refresh --once --json | jq '{schema_version, connected, bus, live_pressure}'
# MCP freshness
# castellan_multiplexer_status → { ok, stale, age_secs, status }

castellan-events schema (--json)

castellan run --json emits one serde-tagged event per line. Canonical variants include:

EventWhen
episode_start / episode_endSession boundaries
scheduler_tick / wake_summary / task_dispatchedDispatch
depositPheromone deposit (drives dashboard field heat)
cost_rollupPer-tick usd/tokens + session totals
plan_verify_passed / plan_verify_deniedGuardians prove-before-execute
quorum_fired / zone_quarantine / morphogenesis / genome_expression_appliedBio chips
topology_apply_drainedMid-run topology apply
verify_result / tool_decision / permission_denied / budget_exhaustedGovernance
mutation_proposed / mutation_resolvedEvolve proposals
archive_write_backPost-run archive merge

Multiplexer events.subscribe bridges the same schema via EmittedEvent::from_castellan_event (fields live under data).

Metrics JSON for CI

castellan telemetry --limit 20   # vitals_count, session_cost, recent_* arrays
castellan statusline refresh --json | jq '{schema_version, session_cost, live_pressure, plan_verify, bio}'

Honest gap: Thin HTTP/SSE of the same snapshot is not shipped yet — not a browser SPA. Prefer statusline.json and MCP for headless observers.

Failure paths

SymptomCauseFix
statusline.json missingNever refreshedcastellan statusline refresh or open castellan dashboard
vitals nullNo episode vitals yetcastellan run --goal "..." --plugin gridworld
MCP stale: trueFile older than ~30sRe-run statusline refresh or keep dashboard open
bus.panes=socket but attach emptyLooking at in-process run panesAttach uses socket tree; ensure panes on mux socket