Young's Mind
02 Architecture

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 (/dashboard on docs.youngle.au), auth (auth_basic, single user young), and the secret-surface discipline (no tokens, no .env content, no raw a2a_audit.jsonl events across the gate) are unchanged and are the design of record's §2 and §5.

Two claims need correction:

  1. §3's schema is a proposal, not a contract — and the design of record's §3 reconciles it with what actually exists. Concretely: heartbeats.json does not exist on LAB (measured 2026-10-20). It is recorded as deliberately absent; liveness comes from the audit log and endpoint probes instead.
  2. §1's "the only missing step in P0 is emitters" was measured and is wrong.status.json does not exist anywhere — find over /home/lab/.solos and /home/lab/.hermes returns 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 sources staleness map and the is_trusted governance 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.py exposes snapshot() (dict), health() ((int, dict), 200/503) and render_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 from core.tasks.stuck.
  • Per Young's 2026-10-12 rule: the dashboard is status.json emitted 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/tasks vocabulary + states.yaml is 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

  1. Placement: docs-site section first (zero new infra). Reason: solos-docs + nginx auth_basic already gives us the auth gate and the viewer. A dash.youngle.au later only matters when tenants see it — fork the vhost at that point (one file per tenant) — not before.
  2. Auth: auth_basic against /etc/nginx/.htpasswd, single user young. Note: young today has full docs.youngle.au access, 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.
  3. What must NOT be on the dashboard: tokens/keys, per-tenant .env content, Telegram tx hashes, provider keys, JWTs, and never a2a_audit.jsonl raw events (only last-exchange age). Only slices cross the gate — expose generated_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 sources entry declaring its own staleness. 300s is the stuck threshold from stuck_rule(); a read older than 2× that is STALE, and a source never feeding shows STALE 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)

  1. _cost_block() already reports spend_usd_measured + unknown_calls; Mira's GO-window probe needs her own method (I can't see opencode.ai/zen/go/v1/usage from this side with certainty — hand her a stub + a measured first result before she builds the UI).
  2. Emit the snapshot to /api/status.json on the loopback. This is a projection (a2a_readout.py), not a live response — do not wire the frontend directly to the store.
  3. Unify ticker and task SLA. Use cortex.tasks.stuck.evaluate()'s worker_stale_seconds for the 300s rule — max(0, …) separately would drift.
  4. a2a_audit.jsonl ingestion 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.jsonl directly (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.tasks registry, not an agent_card query — cards are declared but live claims go via tasks/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.

Copyright © 2026