# TODO Pick anything from here when relevant. Cross-cutting design notes live in [CLAUDE.md](CLAUDE.md); high-level project intro in [README.md](README.md). ## Permissions / policy - **Per-agent send allow-list.** Today any agent can `send` to any other recipient (peer, manager, operator). Add a per-agent policy that constrains the `to` field — declared in `agent.nix`, e.g. `hyperhive.allowedRecipients = [ "manager" "alice" ]`. Broker rejects with an `Err { message }` when the policy denies. Default: unrestricted (back-compat). The manager can still always send anywhere. Useful for sandboxing untrusted sub-agents so they can only talk to the manager, not other sub-agents. ## Security - **Unprivileged containers (userns mapping).** Today the nspawn container runs as a fully privileged root. Goal: `PrivateUsersChown=yes` (or the nixos-container equivalent) so uid 0 inside maps to an unprivileged uid on the host, and a container-root compromise lands the attacker on an ordinary user account, not the host's root. Requires per-agent state dirs to be chown'd to that uid on the host side. - **Bash command allow-list.** Replace the blanket `Bash` allow with a pattern allow-list (`Bash(git *)`, `Bash(nix build .*)`, etc.) per claude-code's `--allowedTools` extended grammar. Likely lives in `agent.nix` so each agent can scope its own shell surface. ## Per-agent extension - **Custom per-agent MCP tools.** Today every sub-agent gets the same fixed MCP surface (`send`, `recv`). To move bitburner-agent (and anything else with rich domain tooling) into hyperhive, an agent needs a way to ship its own tools alongside hyperhive's. Sketch: `agent.nix` declares a list of extra MCP servers (command + args + env), each registered into the agent's `--mcp-config` blob at flake-render time. The harness MCP server remains the hyperhive surface; new servers slot in as additional entries under `mcpServers.` so claude sees them as `mcp____`. Per-agent tool whitelist (`allowedTools`) derived from the same config so the operator stays in control of what's exposed. ## UI / UX - **Browser notifications for operator-bound events.** Dashboard pings the OS notification center when (a) a new approval lands in the queue, (b) a new `ask_operator` question is queued, (c) a broker message is sent `to: "operator"`. All three data sources are already in `/api/state` + `/messages/stream` so this is pure frontend. Sketch: 1. Small "🔔 enable notifications" button somewhere (header or near the inbox section). Clicks call `Notification.requestPermission()`. Hide once granted. 2. Track last-seen counts in the JS app (`approvals.length`, `questions.length`). On `refreshState`, if the count went up, fire `new Notification(...)` per new item. 3. SSE handler for `messages/stream` fires a notification on `kind === 'sent' && to === 'operator'` (already triggers `refreshState`; just adds a notify call alongside). 4. Notification body links back to the dashboard (`onclick → window.focus()` + section anchor). Caveats: Notification API requires a secure context (HTTPS or localhost). Most operators access via LAN / Tailscale — works fine for localhost forwards, otherwise needs a TLS cert in the module. Persist a per-browser "muted" toggle in localStorage so the operator can silence without revoking permission. - **Terminal: `/model` slash command.** Operator-typeable model override from the terminal. Depends on the model-override work above; once an override mechanism exists, wire a `/model ` command that POSTs to a new endpoint. - **xterm.js terminal** embedded per-agent, attached to a PTY exposed by the harness. Pairs well with the unprivileged-container work — would let the operator drop into the container without `nixos-container root-login`. ## Telemetry - **Harness stats per agent in sqlite, charted on the agent page.** bitburner-agent samples 18 series; for hyperhive the generally-applicable ones are: - turns/min, tool calls/turn, turn duration p50/p95 - claude exit code distribution (ok vs `--compact`-retry vs failure) - inbox depth (current + max-over-window) - messages sent/received per turn (split by recipient: peer / operator / manager / system) - approval queue length (across all agents — dashboard-level) - per-tool usage counts (Read/Edit/Bash/send/recv/…) - time-since-last-turn (helps spot stuck agents) - notes file size growth (cues compaction) Backend: a `stats` table with `(agent, ts, key, value)` written from the harness on `TurnEnd`; `GET /api/stats?since=…` returns the series; agent page renders with a small chart lib (uPlot is light). ## Manager → operator question channel ## Spawn flow - **Two-step spawn.** Today `request_spawn(name)` is one shot: manager asks → operator approves → container is created with a default `agent.nix` and empty `/state/`. Manager has no way to pre-stage per-agent prompt material, package additions, or initial notes before the agent first wakes. Split into: 1. `request_spawn_draft(name)` — host creates the per-agent `proposed/` repo (initial commit) and `state/` dir with no container; manager now has `/agents//{config,state}/` to edit + commit just like an existing agent. 2. `request_spawn_commit(name, commit_ref)` — submits the queued approval; operator sees the diff in the dashboard like a normal `apply_commit`; on approve the container is created from that commit. Backwards-compat: keep the existing one-shot `request_spawn` for trivial agents (operator can still type a name in the dashboard). Surface "drafts" as a new section between K3PT ST4T3 and approvals. ## Loop substance - **Notes compaction.** `/state/` is bind-mounted persistently and agents are told (in the system prompt) to keep `/state/notes.md` for durable knowledge — but we don't currently nudge them to compact when notes grow. Bitburner-agent's pattern: a short-lived secondary claude session that takes the existing notes + a "compact this" prompt and rewrites them in place. Add when the notes start bloating.