# hyperhive > a swarm of claude-code agents, each in its own nspawn cage, gossiping > over unix sockets. config changes flow as git commits, the operator > approves them in a browser, every deploy is a tag. cyberpunk-themed > dashboard included. 💜⚡ A host-side Rust daemon (`hive-c0re`) spawns nspawn-isolated agent containers, runs each one's claude turn loop, and brokers messages between them. A privileged manager agent (`hm1nd`) drives the swarm — proposing new agents, editing their NixOS modules, escalating ambiguous decisions to the operator. Every lifecycle change (spawn, config edit, destroy) is gated on a human ◆ APPR0VE click in the dashboard. Every approved change lands as a fast-forward on a git tag, so the deploy history is just `git log`. **Why this exists:** claude code is great in one window. claude code is *exponentielle* across many — but only if you can keep the agents from stepping on each other, give them durable identity across restarts, and stop them from eating production. hyperhive is the substrate: identity = unix socket, communication = sqlite-backed broker, config = git, deploys = tagged commits, blast radius = container. The operator stays in the loop without becoming the bottleneck. ``` host (NixOS, runs hive-c0re.service) │ ├── operator │ ├── browser → :7000 hive-c0re dashboard (containers, approvals) │ ├── browser → :8000 / :8100-8999 per-agent web UIs (live SSE, send, login) │ └── CLI → /run/hyperhive/host.sock JSON-line admin protocol │ ├── hive-c0re (Rust daemon) │ ├── lifecycle nixos-container CRUD + per-agent flake generation │ ├── broker sqlite messages + tokio broadcast (powers SSE + wake-ups) │ ├── approvals sqlite queue, two kinds: ApplyCommit (config) + Spawn │ ├── auto_update rebuilds any container whose recorded flake rev is stale │ ├── dashboard axum HTTP + async-form actions + SSE message flow │ └── sockets /run/hyperhive/{host,manager,agents/}/mcp.sock │ └── nixos-containers (each bind-mounts its socket dir → /run/hive, │ credentials dir → /root/.claude, │ durable notes dir → /state; │ manager additionally gets /agents RW, │ /applied RO (deployed-tag mirror), │ /meta RO (swarm-wide deploy flake)) │ ├── hm1nd hive-m1nd serve : claude turn loop + │ MCP (send / recv / request_spawn / kill / start / │ restart / update / request_apply_commit / │ ask / answer / remind) + web UI on :8000 │ └── h- hive-ag3nt serve : claude turn loop + MCP (send / recv / ask / answer / remind + agent-declared extras via hyperhive.extraMcpServers) + web UI on a hashed :8100-8999 ``` ## The turn loop One message in, one turn out. The harness pops a single inbox message (`Recv` long-polls server-side, wakes the instant a broker `Sent` event fires) → builds a wake prompt → spawns `claude --print --continue --output-format stream-json --mcp-config …` → streams JSON events into the per-agent SSE bus + a sqlite history db → claude drives any further `recv`/`send` itself via the embedded MCP server. `--continue` preserves the prior session so context spans turns without rebuilding from history every wake. If the turn dies mid-stream, the next wake picks up clean — sessions are durable but cheap to discard. ## Operator surfaces **Per agent (`:8100-8999`):** terminal-themed live tail with a textarea prompt; slash commands `/help` `/clear` `/cancel` `/compact` `/model ` `/new-session`; granular state badge (idle / thinking / compacting / offline) with age timer + last-turn duration chip + model chip; cancel-turn + new-session buttons in the state row; sticky-bottom auto-scroll with "↓ N new" pill; event history backfilled on page load; collapsible inbox + collapsible journald viewer + collapsible `agent.nix` viewer per agent on the dashboard; deployed-sha chip per container (read from meta's `flake.lock`). **Dashboard itself (`:7000`):** the swarm-wide view — every container with status chips, every pending approval with inline diff, every open question with operator-answer affordance, every recent message on a unified live stream. A terminal compose box under the message-flow lets the operator drop messages into any agent's inbox: `@name` picks the recipient with auto-complete from the live container list, sticky across sends, POSTs `/op-send` which lands in the broker as `{from:"operator", to:, body}` — same shape any sub-agent sees as a regular inbox message. No special channel, no out-of-band notification system. If the operator says it, the broker carries it. ## The config-edit loop Inverted flow — agents propose, the operator disposes. The manager edits files under `/agents//config/` — `agent.nix` is a plain NixOS module function `{ config, pkgs, lib, ... }: { ... }`, and arbitrary sibling files in the commit are preserved → commits → submits the sha via `request_apply_commit`. Hive-c0re immediately fetches that commit from the proposed repo into the applied repo and pins it as `proposal/` — immutable from the manager's side from then on. Operator clicks ◆ APPR0VE → hive-c0re fast-forwards `applied//main` to the proposal, runs `nix flake lock --update-input agent-` against the host-wide meta flake at `/var/lib/hyperhive/meta/`, builds via `nixos-container update --flake meta#`, and either commits the lock + tags `deployed/` on success or `git restore`s the lock + annotates `failed/` with the build error + rolls back `applied//main` on failure. Denials leave a `denied/` annotated tag carrying the operator's note. Meta's git log is the swarm-wide deploy audit trail (one commit per successful deploy). Per-agent applied repos carry the tag-rich state machine for inside-baseball decisions. The manager sees both — proposed repos ship with an `applied` remote pre-wired, and `/meta/` is RO-bound inside the container — so `git fetch applied`, `git show applied/refs/tags/deployed/`, `git log /meta`, `cat /meta/flake.lock` all just work without constructing paths by hand. See [`docs/approvals.md`](docs/approvals.md) for the full state machine + lock-flow walkthrough. ## Structured Q&A Agents don't have to guess. `ask(question, options?, multi?, ttl_seconds?, to?)` queues a structured question — default recipient is the operator (dashboard renders a free-text / checkbox / radio form), or pass `to: ""` to route the question into a peer agent's inbox instead. The answer arrives later as a `HelperEvent::QuestionAnswered { id, question, answer, answerer }` in the asker's inbox; peer recipients respond via `answer(id, answer)`. Plus `remind(message, delay_seconds | at_unix_timestamp, file_path?)` for self-scheduled wake-ups when an agent wants to nudge itself later without holding a connection open. ## Host config Minimal `flake.nix` for a host that runs hive-c0re: ```nix { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; hyperhive.url = "git+https://git.berlin.ccc.de/vinzenz/hyperhive"; }; outputs = { nixpkgs, hyperhive, ... }: { nixosConfigurations.my-host = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ hyperhive.nixosModules.hive-c0re ({ ... }: { services.hive-c0re.enable = true; # Free-text operator pronouns — defaults to "she/her", threaded # through to every agent's system prompt as HIVE_OPERATOR_PRONOUNS # so claude refers to you naturally in third person. # services.hive-c0re.operatorPronouns = "they/them"; # ... rest of your host config (hardware, networking, users, …) system.stateVersion = "25.11"; }) ]; }; }; } ``` hive-c0re will then: - open its admin socket at `/run/hyperhive/host.sock` + dashboard on `:7000`, - auto-create the manager container (`hm1nd`) if missing, - auto-rebuild any managed container whose hyperhive rev is stale. `claude-code` is unfree; hyperhive whitelists it for itself (scoped: only `claude-code`, nothing else) inside the `claude-unstable` overlay and `harness-base.nix`. Per-agent containers evaluate their own nixpkgs instance so the operator's host-level `allowUnfree` doesn't propagate in — the predicate has to live inline. Nothing to set on the operator side. ## Build / deploy ```sh # inside the repo (devshell first; no global cargo) nix develop -c cargo check nix develop -c cargo clippy --workspace --all-targets -- -D warnings # evaluate everything (rust+nix+toml fmt + clippy) nix flake check # deploy to a host that imports `hyperhive.nixosModules.hive-c0re` cd ~/Repos/ nix flake update --update-input hyperhive sudo nixos-rebuild switch --flake .# ``` No overlays on the host's `pkgs` — the module pulls hive-c0re's package straight from `hyperhive.packages..default`. Just import the module and the service is wired up.