readme: aggressive cut — depth lives in docs/, readme is the hook

This commit is contained in:
damocles 2026-05-17 14:47:43 +02:00
parent 61f296fc83
commit f153639cb4

183
README.md
View file

@ -5,137 +5,44 @@
> approves them in a browser, every deploy is a tag. cyberpunk-themed > approves them in a browser, every deploy is a tag. cyberpunk-themed
> dashboard included. 💜⚡ > 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, runs each one's claude turn loop, and brokers messages only if you can keep the agents from stepping on each other, give them
between them. A privileged manager agent (`hm1nd`) drives the swarm — durable identity, and stop them from eating production. hyperhive is
proposing new agents, editing their NixOS modules, escalating the substrate.
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 - identity = unix socket
is *exponentielle* across many — but only if you can keep the agents - communication = sqlite-backed broker (`send` / `recv` / `ask` /
from stepping on each other, give them durable identity across `answer` / `remind`)
restarts, and stop them from eating production. hyperhive is the - config = git (manager proposes, operator approves, deploys land as
substrate: identity = unix socket, communication = sqlite-backed tagged commits)
broker, config = git, deploys = tagged commits, blast radius = - blast radius = container
container. The operator stays in the loop without becoming the
bottleneck.
``` ```
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
``` ```
## The turn loop Depth lives in [`docs/`](docs/) — pick the one matching your task:
One message in, one turn out. The harness pops a single inbox message | reading path | doc |
(`Recv` long-polls server-side, wakes the instant a broker `Sent` | --- | --- |
event fires) → builds a wake prompt → spawns | dashboard layout + endpoints | [`docs/web-ui.md`](docs/web-ui.md) |
`claude --print --continue --output-format stream-json --mcp-config …` | claude turn loop + MCP tools | [`docs/turn-loop.md`](docs/turn-loop.md) |
→ streams JSON events into the per-agent SSE bus + a sqlite history db | config-edit + approval state machine | [`docs/approvals.md`](docs/approvals.md) |
→ claude drives any further `recv`/`send` itself via the embedded MCP | what survives destroy / purge / restart | [`docs/persistence.md`](docs/persistence.md) |
server. `--continue` preserves the prior session so context spans turns | naming, wire protocol, commit style | [`docs/conventions.md`](docs/conventions.md) |
without rebuilding from history every wake. If the turn dies mid-stream, | NixOS / nspawn gotchas | [`docs/gotchas.md`](docs/gotchas.md) |
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 <name>` `/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:<name>, 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/<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.
## 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: "<agent>"` 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 ## Host config
@ -155,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";
}) })
]; ];
@ -169,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.