Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f153639cb4 | ||
|
|
61f296fc83 | ||
|
|
c423ce9e39 |
2 changed files with 44 additions and 114 deletions
153
README.md
153
README.md
|
|
@ -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.
|
|
||||||
|
|
|
||||||
3
TODO.md
3
TODO.md
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue