Compare commits

...
Author SHA1 Message Date
iris
20e26ad28d docs(persistence): correct the root-container destroy claim (imperative, not declarative)
The 'non-destroyable (declarative; would fight with host NixOS config)'
claim was stale: the root/bootstrap container is imperative
infrastructure, managed end-to-end by hive-c0re (auto_update::
ensure_root_agent recreates it on startup if absent). The actions::
destroy bail is a soft policy guard, and destroying the container is
transient anyway — it's recreated on the next hive-c0re startup.
2026-06-23 14:48:10 +02:00
iris
80c33f5108 docs(security): drop redundant bootstrap-container allowlist entry
The hive-priv container allowlist's explicit MANAGER_NAME allow is
redundant — the bootstrap container's name passes the generic
agent-name char validation anyway. Describe the allowlist as just the
agent-name convention + the sibling service containers.
2026-06-23 14:48:10 +02:00
iris
7e0190e27c docs: address mara review on #1936
- Drop the redundant 'including the root/bootstrap container' /
  'sub-agents + root' enumerations in the port-range + state-dir
  uniformity statements — 'every agent' / 'all agents' is enough (and
  calling out '+ root' reintroduces the special-case framing).
- Stop describing the root agent's privileged (manager-flavour) socket
  as a current thing (it's being removed): drop the conventions.md
  capability clause, the network.md per-agent-privileged-socket list
  entry, and the persistence.md /run/hyperhive/manager/mcp.sock
  runtime-dir bullet.
2026-06-23 14:48:10 +02:00
iris
c24ca4dfe1 docs(dashboard): reframe stale 'the manager' as the root/submitting agent
Schedule columns, tool-group/capability defaults, terminal addressing
(@root), destroy/move guards, and approval-resolution notes now refer to
the root agent (or the submitting agent, for approval flows) rather than
'the manager'. Also fixed the stale 'moving the manager' move-refusal
example (the root agent is reparentable) to a cycle example.
2026-06-23 14:48:10 +02:00
iris
ec5c7d49c4 docs: reframe stale 'the manager' as the root/bootstrap container (infra + web-ui)
Port-range, network-isolation, state-dir, web-UI-port, priv allowlist,
and rebuild-source mentions now refer to the root/bootstrap container
rather than 'the manager' — no structural manager exists (root-ness is
topological). Real artifact names (the manager.nix template toplevel,
/run/hyperhive/manager/mcp.sock) are left as-is.
2026-06-23 14:48:10 +02:00
iris
adb76f3cdf docs(persistence): reframe stale 'the manager' as the root agent
Config-repo editor, meta RO-mount, non-destroyable container, and the
topology.json example now refer to the root agent / bootstrap container
rather than 'the manager'. The /run/hyperhive/manager/mcp.sock path is a
real runtime path, kept (clarified as the root agent's manager-flavour
socket).
2026-06-23 14:48:10 +02:00
iris
d6debd4762 docs(conventions): reframe stale 'the manager' as the root/bootstrap agent
The manager is no longer a structural role — root-ness is purely
topological (an agent with no parent). Reframe the naming + socket
lines that presented 'the manager' as a fixed coordinating agent,
while keeping the manager-flavour socket/protocol identifiers (those
still name live code).
2026-06-23 14:48:10 +02:00
9 changed files with 47 additions and 45 deletions

View file

@ -7,10 +7,10 @@ exist because something already went wrong without them.
- Containers are length-bounded by `nixos-container` (≤ 11 chars).
- Sub-agents are `h-<name>` with `<name>` ≤ 9 chars.
- The manager is `ruth` (fixed name).
- One agent is the bootstrap/root container, with a fixed name (`ruth` today).
- `MAX_AGENT_NAME` in `lifecycle.rs` enforces the cap.
- Per-agent web UI port = `WEB_PORT_BASE + FNV1a(name) % WEB_PORT_RANGE`
(8100..8999) for every agent including the manager; dashboard
(8100..8999) for every agent; dashboard
`cfg.dashboardPort` (default 7000).
## Hive identity (label + domain + display names)
@ -55,9 +55,8 @@ peer's name when the caller knows it's hive-local.
There are no auth tokens on the per-agent unix sockets. The socket
*path* identifies the principal; perms come from "who has the
bind-mount." A sub-agent only sees its own `/run/hive/mcp.sock`; the
manager has access to its privileged socket; hive-c0re owns the host
admin socket.
bind-mount." A sub-agent only sees its own `/run/hive/mcp.sock`;
hive-c0re owns the host admin socket.
### Wake injection

View file

@ -38,7 +38,7 @@ somewhere."
| `MetaUpdate` | `nix flake update` on the meta flake. The worker runs the lock bump itself, then enqueues a cascade of `Rebuild` entries with `parent_id` set to the meta-update's id. |
| `Spawn` | First-deploy of a new agent (approval-driven). Same serialisation as `Rebuild` from the operator's POV. |
| `Destroy` | For future use (`destroy --purge` does real I/O). Variant exists so the wire shape doesn't change later; not currently routed through the queue. |
| `Restart` | Stop + start a container without touching config (~5-10s). Routed through the queue so it serialises against in-flight rebuilds for the same agent — prevents a restart racing a rebuild mid-flight. Sources: dashboard ↺ button, manager `restart` MCP tool. |
| `Restart` | Stop + start a container without touching config (~5-10s). Routed through the queue so it serialises against in-flight rebuilds for the same agent — prevents a restart racing a rebuild mid-flight. Sources: dashboard ↺ button, the `restart` MCP tool. |
| `PermChange` | Write a tool-group or capability change to the shared JSON file (`tool-groups.json` / `capabilities.json`), then rebuild the agent so the updated `HIVE_TOOL_GROUPS` / `HIVE_CAPABILITIES` env var takes effect. Serialising the file write through the queue prevents concurrent dashboard batch-apply actions from racing on the shared file. After a successful file write, emits `CapabilitiesChanged` or `ToolGroupsChanged` SSE snapshot so the P3RM1SS10NS tab updates live. |
| `GracefulStop` | Quiesce then stop a container (the `?graceful=1` path on `/kill/<agent>`). Signals the harness (its next `Recv` returns `GracefulStop` — the inbound fence — so it runs one stop-checkpoint turn to flush durable `/state`, then exits), waits for it to drain (bounded by a 3-min timeout → hard-stop fallback), then runs the normal container-stop teardown. Queued so it can't race an in-flight rebuild for the same agent. |
@ -55,7 +55,7 @@ since the current run started).
| Source | Meaning |
|--------|---------|
| `Manual` | Operator clicked rebuild / update-all / meta-update on the dashboard, or any other direct human action (CLI, manager tool). |
| `Manual` | Operator clicked rebuild / update-all / meta-update on the dashboard, or any other direct human action (CLI, an agent MCP tool). |
| `AutoUpdate` | Legacy startup-sweep source (flat, no parent). Replaced by `StartupSweep` for new boots. |
| `StartupSweep` | Child of a `StartupSweep` parent entry; boot-time per-agent rebuild with the sweep as the visual group header. |
| `Approval` | Triggered by an operator-approved `ApprovalKind::{Spawn, ApplyCommit}`. |

View file

@ -269,9 +269,8 @@ firewall-open would defeat the single-front-door story.
`httpsPort` — the gateway always terminates TLS (self-signed floor), so
both are always served.
The manager hashes into the same port range as sub-agents (no
"manager pinned at 8000" special case), so one range opening covers
every container.
Every agent hashes into the same port range (no special case), so
one range opening covers every container.
The dashboard port (`cfg.dashboardPort`, default 7000) is *not*
listed in either case — it binds `127.0.0.1` only, so a firewall

View file

@ -96,7 +96,7 @@ across `destroy`/recreate (`--purge` wipes them).
## Persistent notes dir per agent
`/var/lib/hyperhive/agents/<name>/state/` bind-mounts to
`/agents/<name>/state` (RW; uniform for sub-agents + manager).
`/agents/<name>/state` (RW; uniform for all agents).
The harness exposes the same path
via `$HYPERHIVE_STATE_DIR`. System prompts tell agents to keep
durable knowledge here (`notes.md`, anything else). The harness also
@ -111,8 +111,8 @@ collision rate gets meaningful; at 23 agents you can still get
unlucky. Operator resolves a collision by renaming the offending
agent (different hash → different port) and rebuilding. No state
file, no probing, no port-allocation drift — the value is
reproducible from just the name. Every agent — including the
manager — hashes into 8100..8999 via the same FNV-1a; dashboard
reproducible from just the name. Every agent hashes into
8100..8999 via the same FNV-1a; dashboard
at `cfg.dashboardPort` (default 7000).
## Restart races on TCP bind

View file

@ -130,7 +130,7 @@ address arithmetic.
`lifecycle::set_nspawn_flags` when creating or updating containers. Each
agent gets a deterministic IP derived from its name so the address is
reproducible across destroy/recreate. This applies uniformly to all
containers including the manager — no special case.
containers — no special case.
`HOST_ADDRESS` is the bridge gateway IP (the address part of
`HIVE_NETWORK_SUBNET`, via `lifecycle::bridge_gateway_ip` — taken verbatim
@ -163,9 +163,9 @@ wiring is runtime:
an instant no-op in shared-netns mode (the marker is absent, so
`ConditionPathExists` skips it).
**Why isolation is safe for the manager**: all hive-c0re communication goes
**Why isolation is safe**: all hive-c0re communication goes
through unix domain sockets (`/run/hive/mcp.sock` for agent requests,
`/run/hive/priv.sock` for privileged ops, per-agent manager sockets).
`/run/hive/priv.sock` for privileged ops).
These are bind-mounted into containers via the nspawn conf. UDS paths
traverse the VFS, not the network stack, so `PRIVATE_NETWORK=1` does not
affect them.

View file

@ -180,16 +180,16 @@ Survives destroy/recreate, gone on `--purge`.
Under `/var/lib/hyperhive/agents/<name>/`:
- `config/` — the proposed nix repo (manager-editable). Bind-mounted
- `config/` — the proposed nix repo (root-agent-editable). Bind-mounted
**read-only** to `/agents/<name>/config` inside the sub-agent's own
container so the agent can inspect what defines it and request
precise changes from the manager; RW into the manager via the
precise changes from the root agent; RW into the root agent via the
`/agents` tree bind.
- `claude/` — claude OAuth credentials, bind-mounted RW to
`/home/<name>/.claude` inside the container.
- `state/` — durable notes and `hyperhive-harness.json`. Bind-mounted
to `/agents/<name>/state` inside the container (uniform for
sub-agents + manager). The `$HYPERHIVE_STATE_DIR` env var exposes
all agents). The `$HYPERHIVE_STATE_DIR` env var exposes
the same path to in-container scripts. Notable files written here
by the harness:
- `hyperhive-status` — single-line free-text status string written
@ -237,8 +237,8 @@ sibling's.
Under `/var/lib/hyperhive/applied/<name>/` — the hive-c0re-only
applied repo. Tracks `flake.nix` (module-only boilerplate; never
edited after first spawn) + `agent.nix` (the actual config; the
manager's edits land here via the approval flow) + any other
files the manager committed. `.git/` carries the proposal /
root agent's edits land here via the approval flow) + any other
files committed via the approval flow. `.git/` carries the proposal /
approved / building / deployed / failed / denied tag history.
Under `/var/lib/hyperhive/meta/` — the swarm-wide deploy flake plus
@ -251,7 +251,7 @@ Contents:
canonical "what's deployed where." The git log is the deploy
audit trail (one commit per successful deploy or hyperhive bump).
- `topology.json` — parent/child agent graph
(`{ "alice": "manager", "bob": "alice", "manager": null }`).
(`{ "alice": "root", "bob": "alice", "root": null }`).
Written by `topology::set_parent`; read by the dashboard, the
renderer, and `<parent>` / `<children>` recipient resolution.
- `tool-groups.json` — per-agent MCP tool group grants
@ -263,7 +263,7 @@ Contents:
`capabilities::set_caps`; injected as `HIVE_CAPABILITIES` env
var. Absent agents have no extra capabilities.
Manager has the meta dir RO-mounted at `/meta/`.
The root agent has the meta dir RO-mounted at `/meta/`.
Marker file `/var/lib/hyperhive/.meta-migration-done` is
written by the startup migration after every container has
@ -285,8 +285,13 @@ step would re-fire).
claude creds, /state/ notes, and the harness dir are all gone.
No undo.
The manager is non-destroyable from both paths (declarative
container; would fight with the host's NixOS config).
The root/bootstrap container is **imperative** infrastructure — managed
end-to-end by hive-c0re, not declared in the host's NixOS config.
`auto_update::ensure_root_agent` recreates it on the next hive-c0re
startup if it's absent (bypassing the approval queue, as required
infrastructure). A soft policy guard in `actions::destroy` currently
refuses to destroy it; even without that guard, destroying it would only
be transient — hive-c0re brings it back on the next startup.
### btrfs subvolumes for `/var/lib/hyperhive/agents/<name>`
@ -323,8 +328,7 @@ Without that, every restart wipes bind sources and existing
containers can't be started.
- `/run/hyperhive/host.sock` — admin socket (host-side CLI).
- `/run/hyperhive/manager/mcp.sock` — manager-privileged socket.
- `/run/hyperhive/agents/<name>/mcp.sock` — per-sub-agent socket
- `/run/hyperhive/agents/<name>/mcp.sock` — per-agent socket
(bind-mounted into the container as `/run/hive/mcp.sock`).
On startup, `Coordinator::register_agent` drops any prior socket

View file

@ -109,10 +109,10 @@ known operations; there is no arbitrary command pass-through:
| `RestartMatrixDaemon` | `systemctl --machine=h-<name> restart hive-matrix-daemon.service` |
**Container allowlist** — every request is validated against an
allowlist before any operation: only names matching `h-<agent>` (the
standard agent prefix), the manager container, or the known sibling
service containers (`hive-gateway`, `hive-forge`, `hive-matrix`,
`hive-ci`) are accepted. Arbitrary container names are rejected.
allowlist before any operation: only names matching the agent-name
convention (char-validated) or the known sibling service containers
(`hive-gateway`, `hive-forge`, `hive-matrix`, `hive-ci`) are accepted.
Arbitrary container names are rejected.
**Socket-activated** — systemd starts `hive-priv` on the first
incoming connection (`LISTEN_FDS=1`); it is not running between calls.

View file

@ -1,8 +1,8 @@
# Web UI
Two web surfaces share the same skeleton: the dashboard (port 7000)
and the per-agent UIs (every container — including the manager —
hashes into :8100-8999 via `lifecycle::agent_web_port`'s FNV-1a).
and the per-agent UIs (every container hashes into :8100-8999 via
`lifecycle::agent_web_port`'s FNV-1a).
Both are SPAs — `GET /` returns a static shell, `/api/state`
returns JSON, JS renders. No full-page reloads.

View file

@ -144,7 +144,7 @@ lock graph with a `visited` set — `follows` aliases and rev-less
nodes are skipped. A `select all / select none`
control sits above the tree. Checking inputs + submitting bumps
the lock in `/meta/` and rebuilds the selected agents in
sequence; each outcome reaches the manager as a `rebuilt`
sequence; each outcome reaches the root agent as a `rebuilt`
system event. `POST /meta-update`. While a lock-bump ripple runs,
the panel shows a pulsing "⏳ meta-update running" banner and the
update button is disabled (snapshot field `meta_update_running`,
@ -305,7 +305,7 @@ The current capabilities are:
| Name | Effect |
|------|--------|
| `manage_root_agent` | allows the `set_status` / lifecycle tools on the root manager |
| `manage_root_agent` | allows the `set_status` / lifecycle tools on the root agent |
| `read_host_journal` | unlocks `get_host_journal` to read journald from inside a container |
| `query_agent_state` | allows `get_loose_ends(agent: "<name>")` calls targeting other agents |
@ -319,7 +319,7 @@ agents in the assignment map have no extra capabilities.
**T00L GR0UPS** — per-agent tool-group permissions. Tool groups are
named buckets of MCP tools; each agent starts with a role default
(agents: `messaging`, `meta`, `inbox`, `execution`; manager: all
(sub-agents: `messaging`, `meta`, `inbox`, `execution`; root agent: all
groups). Checking / unchecking stages which groups are active for the
agent; the page-level **save all** button (below) commits it. Columns
come from `GET /api/tool-groups`. A rebuild is queued so
@ -367,7 +367,7 @@ share enough conceptual ground to live together.
scheduled prompts. **Single-table layout**: each schedule is
one `<tr>`; columns are
`# | src | next | every | owner | body | …agents… | actions`.
Agent columns are dynamic — `operator` + `manager` + every
Agent columns are dynamic — `operator` + `root` + every
live container + any extra name that appears as a target on
some schedule but isn't a current container (same
`buildTargetChips` membership rule the new/edit forms use,
@ -583,7 +583,7 @@ stream sits a terminal-style compose box: `@name` picks the recipient
(sticky via localStorage; auto-complete from the live container list,
Tab/Enter to confirm; `@*` broadcasts). `POST /api/op-send` drops
`{from:"operator", to, body}` into the broker; the resulting SSE frame
re-renders the terminal row. Manager is addressed as `@root`.
re-renders the terminal row. The root agent is addressed as `@root`.
## H0M3 page (`/`)
@ -925,12 +925,12 @@ frosted-mauve bar slides up from the bottom of the viewport
- `■ ST0P` — running agents only
- `▶ ST4RT` — stopped agents only
- `↻ R3BU1LD` — always available
- `DESTR0Y` / `PURG3` — sub-agents only (disabled if manager selected)
- `DESTR0Y` / `PURG3` — sub-agents only (disabled if the root/bootstrap container selected)
- `⇡ M0V3 → ROOT` — promote selected agents to top-level
(parent = null); disabled when all selected are already at root.
Backend `topology::set_parent` refuses moves it can't satisfy
(e.g. moving the manager) and the refusal surfaces in the
failure roll-up.
(e.g. a move that would create a cycle) and the refusal surfaces
in the failure roll-up.
- `⇢ M0V3 → [select]` — inline picker available for any
selection size. The dropdown lists every container that isn't IN
the selection itself nor a descendant of any selected agent
@ -958,14 +958,14 @@ renderApprovals`) with three stacked sections:
so a stale approval stands out; the `.stale` class flips
precisely at the 3600s boundary rather than at the next
`renderApprovals` call.
- **what-changed body** — the manager's description, then
- **what-changed body** — the submitting agent's description, then
drill-in triggers: `↳ view diff` opens the diff in the side
panel; `↳ commit on forge ↗` deep-links the proposal commit
into `agent-configs/<agent>` (shown only when `forge_present`).
Spawn approvals show a one-line "container will be created"
note instead.
- **decision actions**`◆ APPR0VE` and `DENY`. Deny pops a
`prompt()` for an optional reason carried to the manager as
`prompt()` for an optional reason carried to the submitting agent as
`HelperEvent::ApprovalResolved.note`.
The diff panel has a 3-way base toggle — **vs applied** (the
@ -1012,7 +1012,7 @@ that's a browser-level decision, not ours.
updates derived approvals state from the event.
- `POST /api/deny/{id}` (`note=<reason>`, optional) — deny a pending
approval with an optional operator-supplied reason. The reason
travels to the manager as `HelperEvent::ApprovalResolved.note`
travels to the submitting agent as `HelperEvent::ApprovalResolved.note`
and also rides on the dashboard's `ApprovalResolved` event.
Dashboard prompts via the themed `themedPrompt()` dialog on
click — a resizable `<textarea>` where Enter submits and
@ -1157,7 +1157,7 @@ that's a browser-level decision, not ours.
- `POST /api/schedules` — operator-direct schedule create:
`{ targets, body, first_fire_at_unix, interval_seconds?, description? }`.
Agent-initiated schedules go through the approval queue instead
(manager MCP `request_schedule_prompt`).
(via the `request_schedule_prompt` MCP tool).
- `PATCH /api/schedules/{id}` — partial edit. JSON body
`{ body?, description?, interval_seconds?, next_fire_at_unix?,
targets_add?, targets_remove? }`.