claude.md was eating 400 lines of subsystem detail that's useful
when you're working on that subsystem and noise the rest of the
time. split into:
- docs/conventions.md naming, identity, async forms, commit style
- docs/gotchas.md nspawn / nixos-container quirks
- docs/web-ui.md dashboard + per-agent layouts and endpoints
- docs/turn-loop.md claude invocation, wake prompt, mcp surface
- docs/approvals.md approval flow, manager policy, helper events
- docs/persistence.md sqlite dbs, retention, state dir layout
claude.md is now the entry point — file map, reading paths
("pick the doc that matches your task"), quick reminders that
fit on one screen, and a small scratchpad section for in-flight
context. references the docs; the docs don't reference claude.md.
no content was lost — the docs/ files cover everything the old
claude.md did, plus things i wrote up better while extracting.
6.1 KiB
Web UI
Two web surfaces share the same skeleton: the dashboard (port 7000)
and the per-agent UIs (manager on :8000, sub-agents on a hashed
:8100-8999). Both are SPAs — GET / returns a static shell,
/api/state returns JSON, JS renders. No full-page reloads.
Shape (shared by both)
GET /→assets/index.html(placeholders for state-driven sections, shipped viainclude_str!so the binary has no runtime file dependency).GET /static/*.css+GET /static/*.js→ static assets.GET /api/state→ JSON snapshot the JS app renders into the DOM.GET /events/stream(per-agent) /GET /messages/stream(dashboard) →text/event-streamSSE for live updates.
The JS app handles all form[data-async] submissions via a delegated
listener: read data-confirm, swap the button to a spinner, POST
application/x-www-form-urlencoded, re-enable the button on success
(refreshState may keep the form mounted, so we don't rely on a
re-render), call refreshState(). State shapes live in
dashboard.rs::StateSnapshot and web_ui.rs::StateSnapshot —
when adding state fields, plumb through the snapshot struct and the
relevant assets/app.js render function.
Dashboard sections (top to bottom)
- C0NTAINERS — live containers with their action surface.
- K3PT ST4T3 — destroyed-but-state-kept tombstones (size +
age + claude-creds badge). Two actions:
⊕ R3V1V3(queues a Spawn approval; existing state is reused),PURG3(wipes state + applied dirs;POST /purge-tombstone/{name}). - M1ND H4S QU3STI0NS — pending
ask_operatorquestions (amber pulsing border). Always renders a free-text fallback alongside any option list;multi=truerenders options as checkboxes; submit merges selections + free text comma-joined. - 0PER4T0R 1NB0X — recent messages addressed to
operator(last 50, from the broker). - P3NDING APPR0VALS — the queue. The R3QU3ST SP4WN form lives at the top of this section since submitting it immediately queues an approval that lands directly below.
- MESS4GE FL0W — live broker SSE tail.
Container row
Two-line layout (assets/app.js::renderContainers):
- Line 1: agent name (link → new tab), m1nd/ag3nt chip,
needs login/needs updatewarning badges, in-flight◐ pending-state…pill (replaces buttons during start/stop/restart/rebuild/destroy), container name + port. - Line 2: action buttons —
↻ R3BU1LDalways,DESTR0Y+PURG3on sub-agents,↺ R3ST4RT+ (sub-agents)■ ST0Pwhen running,▶ ST4RTwhen stopped. Buttons dim + disable while a transient lifecycle action is in flight.
↻ UPD4TE 4LL button appears above the containers list when any
agent is stale.
Banner pulses on each broker SSE event (pulseBanner with a 4 s
grace timer).
Dashboard endpoints
POST /{approve,deny}/{id}— approve/deny a pending approval.POST /{rebuild,kill,restart,start,destroy}/{name}— lifecycle.POST /purge-tombstone/{name}— wipe a tombstone's state dirs.POST /answer-question/{id}— answer a pending operator question.POST /request-spawn— queue a Spawn approval.POST /update-all— rebuild every stale container.
Per-agent page
Layout, top to bottom:
- Banner (gradient shimmer while state=thinking).
- Title with
↻ R3BU1LDbutton. - Status section (online / needs login / login-in-progress).
- State badge row (
💤 idle / 🧠 thinking / ○ offline · <age>) +■ cancel turnbutton visible while state=thinking. - Terminal-wrap: live event tail (with sticky-bottom auto-scroll
and a
↓ N newpill when not at bottom) followed by an operator-input textarea acting as a prompt.
Live view
Each agent runs an events::Bus: a tokio::sync::broadcast<LiveEvent>
plus a sqlite-backed history at /state/hyperhive-events.sqlite.
The harness emits TurnStart { from, body, unread },
Stream(value) (one per parsed stream-json line), Note,
TurnEnd { ok, note }. The web UI:
- fetches
GET /events/historyon page load and replays the last 2000 events (oldest first, with.no-animso they don't stagger); - then subscribes to
GET /events/stream(SSE) for live tail; - shows a granular state badge above the terminal, driven from
turn_start/turn_end, with a flash animation on transition; - sticky-bottom auto-scroll: scrolling up parks the view; new rows surface a "↓ N new" pill instead of yanking;
- terminal-themed: phosphor mauve glow, Crust bg, backdrop-filter blur, row fade-in slide-up.
Per-stream rendering:
Streamtool_use→→ Read /path/→ Bash $ cmd/→ send → operator: "..."etc., per-tool pretty rather than raw JSON.Streamtool_resultshort → flat← ...; long → collapsed<details>▸ ← Nl · headline(click to expand full body).Streamthinking→ text content if claude provided one, otherwise the bare· thinking …indicator.Streamsystem init,result,rate_limit_eventare dropped — too noisy.Note→· text.TurnEnd→✓ turn ok/✗ turn fail — note, triggers arefreshState().
Terminal-embedded prompt
The operator input lives inside the terminal-wrap as a prompt-style textarea below the live tail: multi-line (Enter sends, Shift+Enter newlines), tab-completes slash commands.
Slash commands today:
/help— list commands locally/clear— wipe the local terminal view (server history kept)/cancel—POST /api/cancel→ host shelloutspkill -INT claude, emits a Note. Also surfaces as a■ cancel turnbutton in the state row while state=thinking./compact—POST /api/compact→ host spawnsturn::compact_sessionin the background; output streams into the live panel.
Unknown /foo shows an error row instead of being silently sent.
Per-agent endpoints
POST /send— operator-injected message into this agent's inbox.POST /login/{start,code,cancel}— claude OAuth login flow.POST /api/cancel— SIGINT the in-flight claude turn.POST /api/compact— run/compacton the persistent session.GET /events/history— replay buffer for the terminal.