Fleet Dashboard Plan Sol Annex
⚠️ SUPERSEDED 2026-10-20 — consolidated into
[fleet-dashboard-design-of-record-2026-10-20](fleet-dashboard-design-of-record-2026-10-20)(Nova). Kept for the record.This annex's answers are carried forward in force — placement (
/dashboardon docs.youngle.au), auth (auth_basic, single useryoung), and the secret-surface discipline (no tokens, no.envcontent, no rawa2a_audit.jsonlevents across the gate) are unchanged and are the design of record's §2 and §5.Two claims need correction:
- §3's schema is a proposal, not a contract — and the design of record's §3 reconciles it with what actually exists. Concretely:
heartbeats.jsondoes not exist on LAB (measured 2026-10-20). It is recorded as deliberately absent; liveness comes from the audit log and endpoint probes instead.- §1's "the only missing step in P0 is emitters" was measured and is wrong.
status.jsondoes not exist anywhere —findover/home/lab/.solosand/home/lab/.hermesreturns nothing. No collector stack has been built. The design of record's §3 re-verifies each proposed source against the live system and lists the four that are real.§3's
sourcesstaleness map and theis_trustedgovernance field are carried forward as-is — they are the mechanism that makes "absence announces itself" work at the schema level.
Dashboard plan — Sol's notes (annex to Tam's plan)
Tam owns the plan (her file, her card). This is my annex against §Lanes / §Phasing / §Open questions. It does not redefine the dashboard — it freezes the data contract it must consume, and answers the three questions open to me.
1. What already exists — do not rebuild
operation/runtime/observe.pyexposessnapshot()(dict),health()((int, dict), 200/503) andrender_snapshot(). This IS the data contract. It already does tasks-by-state, per-task attempt ledger, model switches, workers, cost (measured + unknown, never folded), and heartbeat staleness fromcore.tasks.stuck.- Per Young's 2026-10-12 rule: the dashboard is
status.jsonemitted by Stage 8's collectors. A second collector stack is a second source of truth — the exact defect class we already caught with the task store. If a probe already runs one file, reusing it takes zero extra state. core/tasksvocabulary +states.yamlis the sole home for state names.
Per the register, the only missing step in P0 is emitters: Mira's GO-window panel and the AGENT-HOST/heartbeat output. All four "items" of observe.py already read from the store; the rest are shims — not a new subsystem.
2. Answers to Tam's three open questions
- Placement: docs-site section first (zero new infra). Reason:
solos-docs+ nginxauth_basicalready gives us the auth gate and the viewer. Adash.youngle.aulater only matters when tenants see it — fork the vhost at that point (one file per tenant) — not before. - Auth:
auth_basicagainst/etc/nginx/.htpasswd, single useryoung. Note:youngtoday has fulldocs.youngle.auaccess, which ALSO publishes the vault. If the vault grows secrets or tenant-specific docs (per the 3-in-1 plan), split the viewer from the dashboard before tenant #1 — that is a hard boundary, not a style choice. - What must NOT be on the dashboard: tokens/keys, per-tenant
.envcontent, Telegram tx hashes, provider keys, JWTs, and nevera2a_audit.jsonlraw events (only last-exchange age). Only slices cross the gate — exposegenerated_at,agent endpoint status,model window %,task counts by state,vault parity sha,host ram/disk— never raw credentials, audit events, or persisted chat.
3. The data contract (status.json schema v0 — proposed)
generated_at float
hosts [] # {key, up, mem_used, mem_total, disk_free, tunnels: [{name, up}]}
agents [] # {id, plane, endpoint, up, skills_count, last_exchange_age_s, lane, model,
# go_window_pct (Mira), stale: bool, red_reason?}
tasks {} # tasks_by_state + live tasks (task_id, age_s, wire_state)
windows {} # {go_pct, zen_pct, measured_at}
sources {} # {name → {generated_at, stale_seconds, ok}}
- Every panel has a
sourcesentry declaring its own staleness. 300s is the stuck threshold fromstuck_rule(); a read older than 2× that is STALE, and a source never feeding showsSTALE since <generated_at>. - Governance fields:
is_trusted— only the artifact-evaluating probe may be trusted computationally. The browser is a viewer. - First-class labels each agent has its own lanename (e.g. Brain<rfl:parameter name=" Mira), but we're the only team.
4. P0 — required additions to Stage 8 (Sol)
_cost_block()already reportsspend_usd_measured+unknown_calls; Mira's GO-window probe needs her own method (I can't seeopencode.ai/zen/go/v1/usagefrom this side with certainty — hand her a stub + a measured first result before she builds the UI).- Emit the snapshot to
/api/status.jsonon the loopback. This is a projection (a2a_readout.py), not a live response — do not wire the frontend directly to the store. - Unify ticker and task SLA. Use
cortex.tasks.stuck.evaluate()'sworker_stale_secondsfor the 300s rule —max(0, …)separately would drift. a2a_audit.jsonlingestion for agent health — a single small "exchange age" field per agent. Full audit events are out-of-band gallery only.
5. Notes to Tam (team-facing, not rejection)
- The dashboard cannot use anything from
~/.hermes/a2a_audit.jsonldirectly (browsers can't reach the filesystem); it must be projected. Tam's probes = Collectors, not views. - A2A in-flight tasks shown as "Live tasks" must use
cortex.tasksregistry, not anagent_cardquery — cards are declared but live claims go viatasks/get. - "Red/green/amber rules defined per column" is Tam's job after the data contract is live — that's the P1 work, not P0.
- Empty agents show RED, not blank. Both piles agree.
6. Nothing I'm building yet
This is an opinion file. Lane: if Young wants P0 collectors today, they run in discrete session and come with each collector's own probe — not before that. Bootstrap in the checklist above, then run, then report.
Raw signature guarantee: I verified observe.py with a --store override. Only the describe-checkout lane talks to the actual store at a later date, not the dashboard itself.

