Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20e26ad28d | ||
|
|
80c33f5108 | ||
|
|
7e0190e27c | ||
|
|
c24ca4dfe1 | ||
|
|
ec5c7d49c4 | ||
|
|
adb76f3cdf | ||
|
|
d6debd4762 |
9 changed files with 47 additions and 45 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 2–3 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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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? }`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue