new NixOS option in harness-base.nix:
hyperhive.extraMcpServers.<key> = {
command = "/path/to/server";
args = [ ... ];
env = { KEY = "value"; };
allowedTools = [ "send_message" "join_room" ]; # or ["*"]
};
declared as attrsOf submodule so agents stack arbitrarily many.
the module writes the whole map as JSON to
/etc/hyperhive/extra-mcp.json at activation; the harness reads
that file in mcp::render_claude_config and merges each entry
into the rendered --mcp-config under its own mcpServers.<key>
block. allowed_mcp_tools(flavor) extends the --allowedTools
arg with mcp__<key>__<pattern> for every entry — "*" (the
default) becomes mcp__<key>__* so every tool from that server
is auto-approved, or pass a concrete list to tighten.
collision guard: an extra server keyed "hyperhive" is dropped
with a warn-log so user config can't shadow the built-in
surface. malformed JSON / missing file fall back to "no
extras" silently.
prompt note added: agents see "(some agents only) extra MCP
tools surfaced as mcp__<server>__<tool>" and learn they're
declared via agent.nix. retires the matching TODO under
Per-agent extension. matrix-chat agents + bitburner-agent
migration unblocked.
7.6 KiB
TODO
Pick anything from here when relevant. Cross-cutting design notes live in CLAUDE.md; high-level project intro in README.md.
Turn loop
recvwith nowait_secondsshould return immediately. Today omitting the argument falls through to the 30s default long-poll (RECV_LONG_POLL_DEFAULTinhive-c0re/src/agent_server.rs); a manager that wants a cheap "anything in the inbox right now?" peek has to explicitly passwait_seconds: 0. Flip the semantics soNone= no sleep, returningNone(or the empty inbox shape) right away. The agent opts into the long-poll by setting a positive value. Update bothAgentRequest::RecvandManagerRequest::Recvhandlers + the prompt language inprompts/{agent,manager}.md. Tighten the cap (180s) too — only meaningful when the agent is choosing to wait.
Permissions / policy
- Per-agent send allow-list. Today any agent can
sendto any other recipient (peer, manager, operator). Add a per-agent policy that constrains thetofield — declared inagent.nix, e.g.hyperhive.allowedRecipients = [ "manager" "alice" ]. Broker rejects with anErr { 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. The per-agent git identity (currently injected viaprograms.git.config.useragainst the root user insetup_applied's generated flake) also needs to be provisioned for whatever non-root user claude runs as, or commits the manager makes against/agents/<n>/configwill fall back to a genericnixos@…identity. - Bash command allow-list. Replace the blanket
Bashallow with a pattern allow-list (Bash(git *),Bash(nix build .*), etc.) per claude-code's--allowedToolsextended grammar. Likely lives inagent.nixso each agent can scope its own shell surface.
Operational hygiene (post-meta-flake)
-
Tag retention. Every approval mints up to 5 tags in
applied/<n>/.git(proposal/,approved/,building/,deployed/, plusfailed/ordenied/). Every successful deploy adds one commit to/var/lib/hyperhive/meta/.git. Both grow unbounded. A retention policy — keep alldeployed/*indefinitely, age-outfailed/+denied/after N days, dropproposal/+approved/+building/once a terminal sibling lands — would keep the audit trails browsable without forever-growth. -
Inert
nix flake lockno-args call inmeta::sync_agents. Still valid in current nix (resolves missing inputs without bumping existing ones) but parallel to the deprecated--update-inputwe just had to migrate. Worth keeping an eye on; if it gets renamed too, sync_agents stops being able to seed a fresh meta repo. -
Stream
nixos-container updatestdout into tracing. Todaylifecycle::runcalls.output()which buffers until exit, so a 5-minute rebuild looks identical to a hang in the journal — only the success/failure summary lands. Pipe stdout + stderr line-by-line into tracing (or into the per-agent live event bus when we know which agent) so the operator seesbuilding '/nix/store/…drv'/copying path …in real time and can tell "slow" from "stuck." Same applies to every othernixos-containershellout that takes meaningful time (create,update).
Bugs
- Pending question doesn't always appear on the dashboard.
Repro: manager calls
ask_operator, tool result isquestion queued (id=N)(so the row is in sqlite), but the M1ND H4S QU3STI0NS section keeps showing "no pending questions". Last seen with id=5. Suspected paths:OperatorQuestions::pending()returns Err and theunwrap_or_default()inapi_statehides it. Surface the error (warn-log) and check.- serialization: a new field in
OpQuestion(e.g.deadline_at: Option<i64>) deserializes wrong against an old row whose columns don't match the new SELECT order →row.get(N)?panics for that row, the whole iterator errors,pending()returns Err. Diagnose by curl/api/state | jq '.questions'and compare with sqlite counts. - dashboard JS swallows a render error. Open browser console
and look for exceptions during
renderQuestions.
UI / UX
-
Web UI for config repos + meta deploy log. Browse per-agent proposed / applied tags (
proposal/* / approved/* / building/* / deployed/* / failed/* / denied/*) plus the swarm-wide meta repo's git log on the dashboard. Read-only log + diff + raw-file view is enough — something lighter than a full forge. The meta log already answers "what's deployed where + when"; this surfaces it without an ssh-to-host detour. -
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
statstable with(agent, ts, key, value)written from the harness onTurnEnd;GET /api/stats?since=…returns the series; agent page renders with a small chart lib (uPlot is light).
Spawn flow
- Two-step spawn. Today
request_spawn(name)is one shot: manager asks → operator approves → container is created with a defaultagent.nixand 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:request_spawn_draft(name)— host creates the per-agentproposed/repo (initial commit) andstate/dir with no container; manager now has/agents/<name>/{config,state}/to edit + commit just like an existing agent.request_spawn_commit(name, commit_ref)— submits the queued approval; operator sees the diff in the dashboard like a normalapply_commit; on approve the container is created from that commit. Backwards-compat: keep the existing one-shotrequest_spawnfor 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.mdfor 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.