readme: aggressive cut — depth lives in docs/, readme is the hook
This commit is contained in:
parent
61f296fc83
commit
f153639cb4
1 changed files with 35 additions and 148 deletions
183
README.md
183
README.md
|
|
@ -5,137 +5,44 @@
|
|||
> 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`.
|
||||
Claude code is great in one window, *exponentielle* across many — but
|
||||
only if you can keep the agents from stepping on each other, give them
|
||||
durable identity, and stop them from eating production. hyperhive is
|
||||
the substrate.
|
||||
|
||||
**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.
|
||||
- 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)
|
||||
│
|
||||
├── 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
|
||||
│ ├── browser → :7000 hive-c0re dashboard
|
||||
│ ├── browser → :8000 / :8100-8999 per-agent web UIs
|
||||
│ └── CLI → /run/hyperhive/host.sock 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/<n>}/mcp.sock
|
||||
├── hive-c0re (Rust daemon: lifecycle / broker / approvals /
|
||||
│ auto-update / dashboard / sockets)
|
||||
│
|
||||
└── 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-<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
|
||||
└── nixos-containers
|
||||
├── hm1nd manager agent (privileged MCP surface)
|
||||
└── h-<name> sub-agent (vanilla MCP surface + per-agent extras)
|
||||
```
|
||||
|
||||
## 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
|
||||
(`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 <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.
|
||||
| reading path | doc |
|
||||
| --- | --- |
|
||||
| dashboard layout + endpoints | [`docs/web-ui.md`](docs/web-ui.md) |
|
||||
| claude turn loop + MCP tools | [`docs/turn-loop.md`](docs/turn-loop.md) |
|
||||
| config-edit + approval state machine | [`docs/approvals.md`](docs/approvals.md) |
|
||||
| what survives destroy / purge / restart | [`docs/persistence.md`](docs/persistence.md) |
|
||||
| naming, wire protocol, commit style | [`docs/conventions.md`](docs/conventions.md) |
|
||||
| NixOS / nspawn gotchas | [`docs/gotchas.md`](docs/gotchas.md) |
|
||||
|
||||
## Host config
|
||||
|
||||
|
|
@ -155,12 +62,9 @@ Minimal `flake.nix` for a host that runs hive-c0re:
|
|||
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";
|
||||
# services.hive-c0re.operatorPronouns = "they/them"; # default: "she/her"
|
||||
|
||||
# ... rest of your host config (hardware, networking, users, …)
|
||||
# ... rest of your host config
|
||||
system.stateVersion = "25.11";
|
||||
})
|
||||
];
|
||||
|
|
@ -169,35 +73,18 @@ Minimal `flake.nix` for a host that runs hive-c0re:
|
|||
}
|
||||
```
|
||||
|
||||
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.
|
||||
hive-c0re opens its admin socket + dashboard, auto-creates the
|
||||
manager container, and auto-rebuilds any container whose hyperhive
|
||||
rev goes stale. `claude-code` is unfree — hyperhive scopes the
|
||||
whitelist to itself, nothing for the operator to set.
|
||||
|
||||
## 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
|
||||
nix flake check # rust + nix + toml fmt + clippy
|
||||
|
||||
# evaluate everything (rust+nix+toml fmt + clippy)
|
||||
nix flake check
|
||||
|
||||
# deploy to a host that imports `hyperhive.nixosModules.hive-c0re`
|
||||
cd ~/Repos/<nixos-config-repo>
|
||||
# deploy from a host config that imports hyperhive.nixosModules.hive-c0re
|
||||
nix flake update --update-input hyperhive
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue