Compare commits

...
2 changed files with 44 additions and 114 deletions

153
README.md
View file

@ -1,101 +1,48 @@
# hyperhive # hyperhive
Multi-Claude-Code-agent orchestration on **nixos-containers**. > 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 Claude code is great in one window, *exponentielle* across many — but
containers and brokers messages between them. A manager agent (`hm1nd`) only if you can keep the agents from stepping on each other, give them
coordinates the swarm and gates lifecycle changes on user approval via git durable identity, and stop them from eating production. hyperhive is
commits, surfaced through a vibec0re-styled HTTP dashboard. the substrate.
- identity = unix socket
- communication = sqlite-backed broker (`send` / `recv` / `ask` /
`answer` / `remind`)
- config = git (manager proposes, operator approves, deploys land as
tagged commits)
- blast radius = container
``` ```
host (NixOS, runs hive-c0re.service) host (NixOS, runs hive-c0re.service)
├── operator ├── operator
│ ├── browser → :7000 hive-c0re dashboard (containers, approvals) │ ├── browser → :7000 hive-c0re dashboard
│ ├── browser → :8000 / :8100-8999 per-agent web UIs (live SSE, send, login) │ ├── browser → :8000 / :8100-8999 per-agent web UIs
│ └── CLI → /run/hyperhive/host.sock JSON-line admin protocol │ └── CLI → /run/hyperhive/host.sock admin protocol
├── hive-c0re (Rust daemon) ├── hive-c0re (Rust daemon: lifecycle / broker / approvals /
│ ├── lifecycle nixos-container CRUD + per-agent flake generation │ auto-update / dashboard / sockets)
│ ├── 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/<n>}/mcp.sock
└── nixos-containers (each bind-mounts its socket dir → /run/hive, └── nixos-containers
│ credentials dir → /root/.claude, ├── hm1nd manager agent (privileged MCP surface)
│ durable notes dir → /state; └── h-<name> sub-agent (vanilla MCP surface + per-agent extras)
│ 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-<name> 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
``` ```
Each turn: harness pops one inbox message (Recv long-polls server-side and Depth lives in [`docs/`](docs/) — pick the one matching your task:
wakes on a broker Sent event) → 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.
Operator surface per agent: terminal-themed live tail with a textarea | reading path | doc |
prompt; slash commands `/help` `/clear` `/cancel` `/compact` | --- | --- |
`/model <name>` `/new-session`; granular state badge (idle / thinking | dashboard layout + endpoints | [`docs/web-ui.md`](docs/web-ui.md) |
/ compacting / offline) with age timer + last-turn duration chip + | claude turn loop + MCP tools | [`docs/turn-loop.md`](docs/turn-loop.md) |
model chip; cancel-turn + new-session buttons in the state row; | config-edit + approval state machine | [`docs/approvals.md`](docs/approvals.md) |
sticky-bottom auto-scroll with "↓ N new" pill; event history | what survives destroy / purge / restart | [`docs/persistence.md`](docs/persistence.md) |
backfilled on page load; collapsible inbox + collapsible journald | naming, wire protocol, commit style | [`docs/conventions.md`](docs/conventions.md) |
viewer + collapsible `agent.nix` viewer per agent on the dashboard; | NixOS / nspawn gotchas | [`docs/gotchas.md`](docs/gotchas.md) |
deployed-sha chip per container (read from meta's `flake.lock`).
Operator surface on the dashboard itself: a terminal compose box
under the message-flow stream — `@name` picks the recipient with
auto-complete from the live container list, sticky across sends,
POSTs `/op-send` which drops the message into the broker as
`{from:"operator", to:<name>, body}`. Same shape any sub-agent
sees as a regular inbox message.
Config changes flow the other way: manager edits files under
`/agents/<name>/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/<id>`
— immutable from the manager's side from then on. Operator clicks
◆ APPR0VE → hive-c0re fast-forwards `applied/<n>/main` to the proposal,
runs `nix flake lock --update-input agent-<n>` against the host-wide
meta flake at `/var/lib/hyperhive/meta/`, builds via
`nixos-container update <c> --flake meta#<name>`, and either commits
the lock + tags `deployed/<id>` on success or `git restore`s the lock +
annotates `failed/<id>` with the build error + rolls back
`applied/<n>/main` on failure. Denials leave a `denied/<id>` 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/<id>`, `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.
For decisions any agent (manager or sub) needs structured signal on,
`ask(question, options?, multi?, ttl_seconds?, to?)` queues a question:
default recipient is the operator (dashboard renders a free-text /
checkbox / radio form), or pass `to: "<agent>"` to route a structured
peer question into another agent's inbox. The answer arrives later as
a `HelperEvent::QuestionAnswered { id, question, answer, answerer }`
in the asker's inbox. Peer recipients respond via `answer(id, answer)`.
## Host config ## Host config
@ -115,12 +62,9 @@ Minimal `flake.nix` for a host that runs hive-c0re:
hyperhive.nixosModules.hive-c0re hyperhive.nixosModules.hive-c0re
({ ... }: { ({ ... }: {
services.hive-c0re.enable = true; services.hive-c0re.enable = true;
# Free-text operator pronouns — defaults to "she/her", threaded # services.hive-c0re.operatorPronouns = "they/them"; # default: "she/her"
# 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, …) # ... rest of your host config
system.stateVersion = "25.11"; system.stateVersion = "25.11";
}) })
]; ];
@ -129,35 +73,18 @@ Minimal `flake.nix` for a host that runs hive-c0re:
} }
``` ```
hive-c0re will then: hive-c0re opens its admin socket + dashboard, auto-creates the
- open its admin socket at `/run/hyperhive/host.sock` + dashboard on manager container, and auto-rebuilds any container whose hyperhive
`:7000`, rev goes stale. `claude-code` is unfree — hyperhive scopes the
- auto-create the manager container (`hm1nd`) if missing, whitelist to itself, nothing for the operator to set.
- 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 ## Build / deploy
```sh ```sh
# inside the repo (devshell first; no global cargo)
nix develop -c cargo check nix develop -c cargo check
nix develop -c cargo clippy --workspace --all-targets -- -D warnings nix flake check # rust + nix + toml fmt + clippy
# evaluate everything (rust+nix+toml fmt + clippy) # deploy from a host config that imports hyperhive.nixosModules.hive-c0re
nix flake check
# deploy to a host that imports `hyperhive.nixosModules.hive-c0re`
cd ~/Repos/<nixos-config-repo>
nix flake update --update-input hyperhive nix flake update --update-input hyperhive
sudo nixos-rebuild switch --flake .#<host> sudo nixos-rebuild switch --flake .#<host>
``` ```
No overlays on the host's `pkgs` — the module pulls hive-c0re's package
straight from `hyperhive.packages.<system>.default`. Just import the
module and the service is wired up.

View file

@ -9,6 +9,9 @@
- **Multi-agent restart coordination**: when rebuilding all agents, manager should start first so it can coordinate post-restart confusion (notify agents, suppress unnecessary retries, etc) - **Multi-agent restart coordination**: when rebuilding all agents, manager should start first so it can coordinate post-restart confusion (notify agents, suppress unnecessary retries, etc)
- **Shared docs/skills repo (RO)**: a single repo on the hive forge that every agent has read-only access to — common references, prompts, runbooks, "skills" the operator wants every agent to inherit without baking into the system prompt or `/shared`. Implementation likely: seed an `org-shared/docs` repo on first hive-forge boot, grant every per-agent user a read membership in the org. Agents `git clone` it (or use the API) to read; only the manager + operator can push. - **Shared docs/skills repo (RO)**: a single repo on the hive forge that every agent has read-only access to — common references, prompts, runbooks, "skills" the operator wants every agent to inherit without baking into the system prompt or `/shared`. Implementation likely: seed an `org-shared/docs` repo on first hive-forge boot, grant every per-agent user a read membership in the org. Agents `git clone` it (or use the API) to read; only the manager + operator can push.
- **Loose-ends tracker + `get_open_threads` tool**: hive-c0re already knows about pending approvals + unanswered questions; soon will also know about open PRs on hive-forge. Aggregate these into a per-agent "open threads" view (e.g. `[{kind: "approval", id: 7, summary: "spawn alice"}, {kind: "question", id: 12, asker: "alice", summary: "deploy now?"}]`). New MCP tool `mcp__hyperhive__get_open_threads` returns the list so an agent can see what's still pending against it without rebuilding context from inbox history. Manager's version includes hive-wide threads. **Also surface this list on the per-agent web UI** so the operator can see at a glance what each agent has hanging open — same data source as the MCP tool, just rendered into the existing per-agent dashboard page (next to inbox view / model chip / etc). - **Loose-ends tracker + `get_open_threads` tool**: hive-c0re already knows about pending approvals + unanswered questions; soon will also know about open PRs on hive-forge. Aggregate these into a per-agent "open threads" view (e.g. `[{kind: "approval", id: 7, summary: "spawn alice"}, {kind: "question", id: 12, asker: "alice", summary: "deploy now?"}]`). New MCP tool `mcp__hyperhive__get_open_threads` returns the list so an agent can see what's still pending against it without rebuilding context from inbox history. Manager's version includes hive-wide threads. **Also surface this list on the per-agent web UI** so the operator can see at a glance what each agent has hanging open — same data source as the MCP tool, just rendered into the existing per-agent dashboard page (next to inbox view / model chip / etc).
- **Scope per agent X (confirmed with operator):** include BOTH (a) unanswered questions where `asker == X` (X is waiting on someone) AND (b) unanswered questions where `target == X` (X owes an answer). Distinguish via a `role: "asker" | "target"` field on the question variant so the agent can render "waiting on" vs "owe a reply" appropriately. Approvals: include rows where the submitter is X (waiting on the operator). Forge PRs (future): include open PRs where X is author OR reviewer.
- **Wire shape sketch:** new `AgentRequest::GetOpenThreads` / `ManagerRequest::GetOpenThreads` returning `Response::OpenThreads { threads: Vec<OpenThread> }` with `OpenThread` as a tagged enum (`{kind: "approval", id, summary, age_seconds}` / `{kind: "question", id, role, counterparty, summary, age_seconds}` / future `{kind: "pr", ...}`). Manager flavour returns hive-wide threads (no asker/target filter). MCP tool `get_open_threads` takes no args.
- **Aggregator location:** new helper on `Coordinator` (or a dedicated `open_threads.rs`) so both surfaces share the query logic; queries `approvals` + `operator_questions` tables with a single per-call sweep (no caching — call frequency is low).
## Reminder Tool ## Reminder Tool