diff --git a/README.md b/README.md index ed56da6..70d28a4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,27 @@ # 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 -containers and brokers messages between them. A manager agent (`hm1nd`) -coordinates the swarm and gates lifecycle changes on user approval via git -commits, surfaced through a vibec0re-styled HTTP dashboard. +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) @@ -41,31 +57,49 @@ host (NixOS, runs hive-c0re.service) on a hashed :8100-8999 ``` -Each turn: harness pops one inbox message (Recv long-polls server-side and -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. +## The turn loop -Operator surface per agent: 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`). +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 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:, body}`. Same shape any sub-agent -sees as a regular inbox message. +## Operator surfaces -Config changes flow the other way: manager edits files under -`/agents//config/` — `agent.nix` is a plain NixOS module function +**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 @@ -82,20 +116,26 @@ 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`, +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. -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: ""` 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)`. + +## 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