From c423ce9e398326e1039c879148fe1efdf110ef2f Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 17 May 2026 14:43:08 +0200 Subject: [PATCH 1/3] todo: lock down get_open_threads scope (asker + target questions) --- TODO.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO.md b/TODO.md index 54d5442a..fcbd796c 100644 --- a/TODO.md +++ b/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) - **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). + - **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 }` 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 From 61f296fc83b527c12414d2f5d2ff729e867947e0 Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 17 May 2026 14:46:46 +0200 Subject: [PATCH 2/3] =?UTF-8?q?readme:=20voice=20pass=20=E2=80=94=20opener?= =?UTF-8?q?=20hook,=20why-this-exists=20framing,=20section=20breaks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 112 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index ed56da66..70d28a46 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 From f153639cb4dbabb4d4603ac01b9cffb2cc806867 Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 17 May 2026 14:47:43 +0200 Subject: [PATCH 3/3] =?UTF-8?q?readme:=20aggressive=20cut=20=E2=80=94=20de?= =?UTF-8?q?pth=20lives=20in=20docs/,=20readme=20is=20the=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 183 +++++++++++------------------------------------------- 1 file changed, 35 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index 70d28a46..a81a5d8f 100644 --- a/README.md +++ b/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/}/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- 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- 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 ` `/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 -the proposed repo into the applied repo and pins it as `proposal/` -— immutable from the manager's side from then on. Operator clicks -◆ APPR0VE → hive-c0re fast-forwards `applied//main` to the proposal, -runs `nix flake lock --update-input agent-` against the host-wide -meta flake at `/var/lib/hyperhive/meta/`, builds via -`nixos-container update --flake meta#`, and either commits -the lock + tags `deployed/` on success or `git restore`s the lock + -annotates `failed/` with the build error + rolls back -`applied//main` on failure. Denials leave a `denied/` 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/`, `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: ""` 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/ +# deploy from a host config that imports hyperhive.nixosModules.hive-c0re nix flake update --update-input hyperhive sudo nixos-rebuild switch --flake .# ``` - -No overlays on the host's `pkgs` — the module pulls hive-c0re's package -straight from `hyperhive.packages..default`. Just import the -module and the service is wired up.