todo: drop landed entries (terminal coherence pass, get_state_file defense-in-depth, self-management of loose ends, persist+cold-load ctx-badge). claude.md: - scratchpad: new just-landed entries for ctx+cost badge split, terminal coherence pass, loose_ends rename + cancel_loose_end, whoami, reminder failure persistence, path linkify, tombstones+ meta_inputs events, agent open-threads section + container pending- reminder chip + task event rendering. drops the meta-flake "just landed" — structural facts live in the file map + approvals.md, the narrative was no longer load-bearing. - file map: hive-fr0nt now lists MARKED_JS + marked.min.js + the unified prefix-column terminal.css update. - reading paths: terminal-rendering.md description matches as-built. docs/terminal-rendering.md: rewritten as as-built reference. layout contract documents the padding-left + negative text-indent prefix column + how details inherits it. row taxonomy reflects current state (notes split into .note / .note.stderr / .note.op; .sys is amber; recv tool_results default-open with markdown body via tool_use_id correlation; rich send/ask/answer renderers). new sections for renderer dispatch flow, markdown integration, fmtArgsGeneric extra-MCP fallback, dashboard msgrow text-indent reset.
22 KiB
22 KiB
hyperhive — claude entry point
Hey claude. This is your starting page. The detailed docs live in
docs/ and are written for humans + you both — read them
when you need depth on a subsystem. This file is the index +
scratchpad.
File map
hive-c0re/ host daemon + CLI (one binary, subcommand-dispatched)
src/main.rs clap setup; serve / spawn / kill / rebuild / list /
pending / approve / deny / destroy [--purge] /
request-spawn; periodic vacuum tasks
src/server.rs host admin socket (HostRequest → dispatch)
src/client.rs admin-socket client
src/manager_server.rs manager-privileged socket (ManagerRequest)
src/agent_server.rs per-sub-agent socket listener (long-poll Recv)
src/broker.rs sqlite Message store + intra-process broadcast
channel (`MessageEvent`) for `recv_blocking` +
the dashboard forwarder; hourly vacuum of
delivered>30d
src/dashboard_events.rs unified wire-facing event channel feeding
`/dashboard/stream`. Carries broker `Sent` /
`Delivered` (mirrored by the forwarder task
in main.rs) + mutation events
(`ApprovalAdded` / `ApprovalResolved`,
`QuestionAdded` / `QuestionResolved`,
`TransientSet` / `TransientCleared`). Each
frame carries a monotonic per-process `seq`
clients use to dedupe against snapshot reads.
src/approvals.rs sqlite Approval queue + kinds
src/operator_questions.rs sqlite question queue backing `ask` /
`answer` (both operator + agent-to-agent)
src/questions.rs shared dispatch for `Ask` / `Answer` —
used by both agent + manager surfaces
src/reminder_scheduler.rs 5s poll loop: drains due reminders,
resolves file_path container→host, persists
payload + delivers pointer string
src/events_vacuum.rs host-side hourly sweep of every agent's
/state/hyperhive-events.sqlite
src/crash_watch.rs poll every 10s; fire HelperEvent::ContainerCrash
when a previously-running container disappears
without an operator-initiated transient
src/container_view.rs ContainerView struct + build_all helper;
shared between dashboard.rs (cold-load via
/api/state) and coordinator.rs's
rescan_containers_and_emit
src/coordinator.rs shared state (broker/approvals/operator_questions/
transient/sockets) + tombstone enumeration +
kick_agent + notify_agent (helper-event push) +
last_containers cache + rescan_and_emit diff helper
src/loose_ends.rs loose-ends aggregator (pending approvals +
unanswered questions + pending reminders) —
for_agent (filtered) and hive_wide (manager
surface). Backs AgentRequest::GetLooseEnds +
ManagerRequest::GetLooseEnds (the
get_loose_ends MCP tool).
src/actions.rs approve/deny/destroy (transient-aware)
src/auto_update.rs startup rebuild scan + ensure_manager +
meta::lock_update_hyperhive bump
src/lifecycle.rs `nixos-container` shellouts; per-agent applied
+ proposed git repo seeding; tag plumbing
src/meta.rs single hive-c0re-owned flake at /var/lib/
hyperhive/meta/ — sync_agents, two-phase
prepare/finalize/abort, lock_update_*
src/migrate.rs startup auto-migration from pre-meta layout
(idempotent, marker-guarded phase 4)
src/dashboard.rs axum HTTP: static shell + /api/state JSON + actions
+ journald viewer + bind-with-retry (SO_REUSEADDR)
+ deployed_sha chip per container +
/dashboard/{stream,history} subscribing to the
unified DashboardEvent channel
assets/ index.html, dashboard.css, app.js (include_str!)
hive-fr0nt/ shared frontend-assets crate (browser only).
src/lib.rs pub const BASE_CSS / TERMINAL_CSS / TERMINAL_JS /
MARKED_JS re-exports; both binaries
`include_str!` them and prepend to their per-
page serving routes.
assets/base.css Catppuccin palette + body typography (one source
of truth, no per-page redeclaration).
assets/terminal.css `.terminal-wrap` + `.live` + `.tail-pill` +
`.row` / `details.row` styling for both
pages' lit log panes. Unified prefix-column
(padding-left + negative text-indent) so glyph
alignment is consistent across row kinds + a
`.md` block scope for marked-rendered bodies.
assets/terminal.js `window.HiveTerminal.create(opts)`: scroll-
sticky log + "↓ N new" pill + history
backfill + SSE subscribe-buffer-snapshot-
dedupe dance. Pages register a kind→renderer
map; the terminal owns the lifecycle.
assets/marked.min.js vendored marked v4.0.2 UMD bundle. Per-agent
terminal uses the global `marked.parse` for
markdown bodies on send / recv / ask / answer
/ assistant text rows.
hive-ag3nt/ in-container harness crate; produces TWO binaries
src/lib.rs re-exports + DEFAULT_SOCKET, DEFAULT_WEB_PORT
src/client.rs generic JSON-line request/response over unix socket
src/web_ui.rs per-container axum HTTP page (incl /api/cancel,
/api/compact, /api/model, /events/history)
src/turn_stats.rs per-turn analytics sink (one sqlite row per
turn at /state/hyperhive-turn-stats.sqlite);
schema + best-effort writer
src/events.rs LiveEvent + broadcast Bus + sqlite-backed history
(/state/hyperhive-events.sqlite) + TurnState +
model selection (persisted at /state/hyperhive-model)
src/turn.rs claude --print + stream-json pump; --compact retry
src/mcp.rs embedded MCP server (rmcp): AgentServer + ManagerServer
src/login.rs probe /root/.claude/ for a valid session
src/login_session.rs drives `claude auth login` over stdio pipes
src/bin/hive-ag3nt.rs sub-agent main (Serve + Mcp subcommands)
src/bin/hive-m1nd.rs manager main (Serve + Mcp subcommands)
assets/ index.html, agent.css, app.js (include_str!)
prompts/ static role/tools/settings for claude (include_str!):
agent.md — sub-agent system prompt
manager.md — manager system prompt
claude-settings.json — --settings JSON
hive-sh4re/ wire types (HostRequest/Response, AgentRequest/Response,
ManagerRequest/Response, Message, Approval, HelperEvent)
nix/
modules/hive-c0re.nix systemd service + firewall + git wiring
templates/harness-base.nix shared scaffolding for sub-agents + manager
templates/agent-base.nix sub-agent nixosConfiguration
templates/manager.nix manager nixosConfiguration
docs/
conventions.md naming, identity=socket, async forms, commit style
gotchas.md NixOS / nspawn lessons learned the hard way
web-ui.md dashboard + per-agent page layouts and endpoints
turn-loop.md claude invocation, wake prompt, MCP tool surface
approvals.md approval flow, manager policy, helper events
persistence.md sqlite dbs, retention, state dir layout
Reading paths
Pick the doc that matches your task. None depend on the others — read them à la carte.
- "What does the dashboard look like?" →
docs/web-ui.md. - "How does the per-agent terminal classify + colour
events?" →
docs/terminal-rendering.md(as-built row taxonomy + layout contract + markdown + extra-MCP fallback). - "How does claude get its prompt and what tools does it have?" →
docs/turn-loop.md. - "How do config changes flow from manager to operator to
container?" →
docs/approvals.md. - "What state survives destroy / purge / restart?" →
docs/persistence.md. - "Naming, commit style, wire protocol, the
data-asyncpattern." →docs/conventions.md. - "Why does the nspawn flag look like that?" →
docs/gotchas.md.
Quick reminders
- Commit before test. Stage and commit when work looks ready, then run validation. Failures get a follow-up commit rather than an amend.
- Commit messages: short, lowercase, no
Co-Authored-Bytrailer. Imperative mood. rebuildis the reconcile verb. Anything that changes per-container state on the host should be re-applied there so the dashboard's↻ R3BU1LDis sufficient to recover.- Identity = socket. No auth tokens — the socket path identifies the principal.
- Actions are factored between admin socket and dashboard via
actions.rsanddashboard.rs::lifecycle_action, so the two surfaces never drift.
Scratchpad
In-flight or recent context that hasn't earned a section yet. Prune freely.
- Just landed: ctx + cost badges split. The per-agent
page now shows TWO chips —
ctx · N(last inference's prompt size = actual context window utilisation, parsed from eachassistantevent's.message.usage; the number to watch for compaction) andcost · M(sum across every inference in the turn, the previous behaviour now correctly labelled — tool-heavy turns rebill the cached prefix per call and blow past the model's window). Both fed by a singleTokenUsageChanged { ctx, cost }SSE event at turn-end viaBus::record_turn_usage.turn_statsgrew fourlast_*_tokenscolumns (idempotent ALTER migration) so cold-load seeds both badges from the most recent row. Pre-migration rows yield noctxseed (empty badge until next turn) rather than a misleading zero. - Just landed: per-agent terminal coherence pass.
Unified prefix column (padding-left + negative
text-indent so every row kind aligns);
<details>summaries drop the directional glyph and let CSS▸/▾sit in the prefix column; turn boundaries de-weighted (border-left rule only, no bold/margin/ tint); stderr lines render orange!, operator-initiated notes mauve italic, catch-all.sysescalated to orange so unrecognised stream-json surfaces. Message-bearing tool calls (send/ask/answer/recv) render default-open with markdown bodies via vendoredmarkedv4.0.2 (hive-fr0nt::MARKED_JS); assistanttextrows also markdown-rendered. Extra-MCP tools get a generic args pretty-printer (fmtArgsGeneric) instead of raw JSON. tool_use_id → name map carries through the stream sorenderToolResultknows when a result came fromrecvand should default-open with markdown. Seedocs/terminal-rendering.mdfor the as-built taxonomy. Bonus: ctx badge seeded fromturn_statson cold load viaBus::seed_usageso the chip paints real numbers before the next turn finishes. - Just landed:
open_threads→loose_endsrename (more honest about what the list is) + newcancel_loose_end(kind, id)MCP tool on both surfaces.kind = "question"posts a[cancelled by <self>]answer to unblock the asker;kind = "reminder"hard-deletes before fire. Auth: sub-agent must own the row (asker == self/owner == self); manager bypasses for hive-wide cleanup.LooseEndenum gained aReminder { id, owner, message, due_at, age_seconds }variant; sub-agent flavour filters by owner, manager unfiltered. Shared dispatch inhive-c0re/src/questions.rs::handle_cancel_loose_end. Per-agent web UI's/api/open-threads→/api/loose-ends. Closes the agent-side "I have no way to cancel what I queued" friction from the ergonomics wishlist. - Just landed:
whoamiMCP tool on both surfaces — returns{ name, role, pronouns, hyperhive_rev }. Lets an agent self-identify without scraping its own system prompt; pronouns are pulled from theHIVE_OPERATOR_PRONOUNSenv that the system prompt also substitutes, so the two stay in sync. - Just landed: reminder delivery failures persist +
surface. The 5s scheduler now records the failure
reason on the
remindersrow instead of silently dropping; the web UI surfaces them in the loose-ends section so the owner agent can see "this reminder never landed because the target was destroyed" without reading journald. - Just landed: path linkify. The broker tags every
message body server-side with
file_refsat ingest via/api/state-file/check(validates each path, attaches container→host resolution). Last segment must look likename.extso directory mentions don't fire spurious links. Dashboard renders refs as collapsible path-preview blocks below the message body; per-agent terminal picks them up from the same field. Server-side validation means the JS doesn't re-walk allow-lists on every render. - Just landed: tombstones + meta_inputs as
DashboardEvents. Closes the last two refetch loops on the dashboard side —purge-tombstoneandmeta-updatePOSTs now flip to 200 withdata-no-refresh. The 5s/api/statepoll is gone entirely; everything event-driven. - Just landed: per-agent UI gained an open-threads
section (questions + approvals + reminders pending
against this agent) + the container row on the
dashboard gained a
⏰ Npending-reminder count chip. task_started / task_notification stream-json events now pretty-render in the terminal with the⌁glyph so subagent (Task tool) activity is visually distinct from main-session tool calls. - Just landed: per-agent extra MCP servers via the
hyperhive.extraMcpServers.<key>NixOS option inagent.nix. Declares{ command, args, env, allowedTools }; the module writes the whole map to/etc/hyperhive/extra-mcp.json; the harness reads that file and merges each entry into both--mcp-configand--allowedTools(mapped tomcp__<key>__<pattern>). Unblocks matrix / bitburner / any agent with rich domain tooling — the agent flake'sinputsblock pulls the external flake,agent.nixreferences it viaflakeInputs.<name>.packages.${pkgs.system}.default. - Just landed: per-turn analytics sink. New
hive-ag3nt::turn_statswrites one row per claude turn to/state/hyperhive-turn-stats.sqlite: identity (model, wake_from, result_kind), timing (started/ended_at, duration_ms), cost (full token-usage breakdown), behaviour (tool_call_count + per-tool JSON map), and post-turn snapshot metrics (open_threads_count, open_reminders_count fetched via the existing GetOpenThreads + new CountPendingReminders RPC). Both ag3nt + m1nd bin loops capture, both Bus accumulates tool_use blocks via observe_stream during the stdout pump. Writes are best-effort. No host-side vacuum yet — TODO under Telemetry; same shape as events_vacuum, target 90d retention. - Just landed: agent web UI event-driven badges. New
LiveEvent::StatusChanged / ModelChanged / TokenUsageChanged / TurnStateChangedvariants replace the per-agent page's /api/state polling for the state row. Status/model/token/state badges all update from SSE; /api/state only fetched on cold load + during the login flow (session output isn't event- shaped). Per-agent endpoints (/api/cancel|compact|model| new-session,/login/*) all flip 303→200. Newalive-badgechip carries the harness reachability signal (replaces the "● harness alive" paragraph); newctx-badgemirrors Claude Code's bottom-right "N tokens" indicator. Every chip carries atitle=...tooltip for hover detail. - Just landed: events_vacuum simplified to age-only —
KEEP_SECS = 7d, no row cap. Chatty turn no longer evicts a quiet day's history sooner than expected. Hourly sweep unchanged. - Just landed: Phase 6 container events. New
DashboardEvent::ContainerStateChanged { container }+ContainerRemoved { name }close the last refetch loop on the dashboard side.Coordinator::rescan_containers_and_emitbuilds a freshcontainer_view::build_allsnapshot, diffs it against a cachedlast_containersmap, and fires per-row events for the delta. Called from every mutation site:actions::approve(post-spawn),actions::destroy, thelifecycle_actionwrapper indashboard.rs(start/stop/restart/rebuild),auto_update:: rebuild_agent, and the existing 10scrash_watchpoll loop.ContainerViewextracted to its own module so coordinator + dashboard can both build it. Dashboard endpoints (/restart,/destroy,/kill,/rebuild,/start,/update-all,/meta-update,/purge-tombstone) now return 200; matching forms carrydata-no-refreshwhere the event coverage is complete (purge + meta-update keep the refetch since tombstones- meta_inputs aren't event-derived yet). Client drops the 5s
periodic
/api/statepoll entirely — initial cold load + SSE for everything afterwards; pending overlay reads fromtransientsStatesince the new event payload doesn't carry it.
- meta_inputs aren't event-derived yet). Client drops the 5s
periodic
- Just landed: dashboard event refactor. New
hive-fr0ntworkspace crate hosts shared frontend assets (palette + terminal CSS +window.HiveTerminal.createJS) so both the dashboard and the per-agent web UI render their live panes through the same code; the dashboard's#msgflownow feels like the agent's terminal (sticky-bottom + pill + lit chrome). New unifiedDashboardEventchannel onCoordinator(replaces the broker-only/messages/stream); a background forwarder mirrors broker traffic onto it asSent/Deliveredvariants, and the mutation-event variants (ApprovalAdded/ApprovalResolved,QuestionAdded/QuestionResolved,TransientSet/TransientCleared) cover every in-process state change the dashboard cares about. Each frame carries a monotonic per-processseq; snapshot endpoints return their seq alongside the state, and the terminal's open-buffer-then-fetch-history dance drops any buffered frame withseq <= history_seqso an event landing between subscribe and history-fetch is neither shown twice nor lost. Operator inbox + approvals + questions + transients are now derived client-side from the event stream (cold-loaded from/api/statefor first paint, mutated live from SSE thereafter);/op-send+ per-agent/sendreturn 200 instead of 303-and-refetch. Container-list events still pending —ContainerViewis sourced from externalnixos-container list, so the 5s/api/statepoll continues to drive the containers section. Approval diffs are now raw unified-diff text on the wire (per-line classification happens in JS) so they fit in SSE payloads without HTML escaping. Bug fix:LiveEvent::Notewas a newtype variant that serde silently failed to serialize — converted toNote { text: String }(wire shape matches what the JS already read). - Just landed:
ask_operator→askrename + optionalto: <agent>param for agent-to-agent structured Q&A. Recipient defaults to the operator (dashboard); peer questions land in the target's inbox asQuestionAskedevents and the recipient replies via newanswer(id, answer)tool. Answer always flows back asQuestionAnswered { id, question, answer, answerer }(renamed fromOperatorAnswered;answererdistinguishes operator vs peer vsttl-watchdog). Authorisation: operator-targeted questions can only be answered by the operator; agent-targeted by the named target (or the operator as override). Self-ask rejected. Shared dispatch lives inhive-c0re/src/questions.rs. Dashboard'spending()filters ontarget IS NULLso peer questions never leak into the operator's queue. - Just landed: dashboard now has a terminal-style
compose textbox under the message-flow stream —
@namepicks the recipient (sticky in localStorage, auto- completed fromcontainers[]), POSTs/op-send. New per-agent↻ new sessionbutton drops--continuefor one turn. Claude spawns withcwd = /stateso relative paths in tool calls land in the durable dir. - Just landed (prior overhaul still underneath): tag-
driven config-apply. Two-repo split (proposed = manager
RW, applied = core-only);
request_apply_commitfetches the manager's commit into applied and pins it asproposal/<id>; approve / deny / build walk through tags on the same commit;applied/mainonly fast- forwards ondeployed/.failed/+denied/are annotated. Seedocs/approvals.md. - Recent (since last compaction): inline +/- diffs on
Write/Edit, send full body via collapsed details, operator
cancel + ttl on questions, deny-with-reason, dashboard
back-link + last-turn timing + model chip, per-agent inbox
view, bind-retry + SO_REUSEADDR, journald viewer,
agent.nix viewer, server-side TurnState, recv(wait_seconds)
max 180s, runtime /model switch + persistence to /state,
crash watcher + ContainerCrash / NeedsLogin / LoggedIn /
NeedsUpdate events, manager
updatetool, pure-hash agent_web_port + collision banner + spawn/rebuild preflight, browser notifications, focus-preserving refresh, generalisedsurvival, prompt-on-submit pattern. - Open threads: custom per-agent MCP tools (groundwork for
moving bitburner-agent into hyperhive), two-step spawn,
per-agent send allow-list, telemetry/charts, notes
compaction, unprivileged containers, Bash allow-list,
xterm.js. Known bug (in TODO.md): question id=5 was
queued but didn't render — likely a
pending()row-decode error swallowed byunwrap_or_default; investigate by curl /api/state | jq '.questions' + browser console.