docs: retire the agent hierarchy from every page that described it
The topology doc keeps its filename and its second half (manager special-casing, harness unit shape) — both are cross-referenced from other pages and neither is about the parent field. Its first half is rewritten: what topology.json is now, and a table of what the removal took with it, so a reader who finds `<parent>` or `set-parent` in an old issue thread learns it went away rather than moved. The dashboard's tree-rendering section is marked dormant rather than deleted: the walk is still in swarm.js and retiring it is the frontend owner's call.
This commit is contained in:
parent
d94bc2188d
commit
179f873722
17 changed files with 186 additions and 262 deletions
|
|
@ -30,7 +30,7 @@ declarations.
|
||||||
state machine, `flake.lock` validation).
|
state machine, `flake.lock` validation).
|
||||||
- **What state survives destroy / purge / restart?** →
|
- **What state survives destroy / purge / restart?** →
|
||||||
[`agent-lifecycle/persistence.md`](agent-lifecycle/persistence.md).
|
[`agent-lifecycle/persistence.md`](agent-lifecycle/persistence.md).
|
||||||
- **Who can do what to whom — agent hierarchy and privilege?** →
|
- **Who can do what to whom — the agent roster and privilege?** →
|
||||||
[`agent-lifecycle/agent-hierarchy.md`](agent-lifecycle/agent-hierarchy.md).
|
[`agent-lifecycle/agent-hierarchy.md`](agent-lifecycle/agent-hierarchy.md).
|
||||||
- **How does claude get its prompt, and what tools does it have?** →
|
- **How does claude get its prompt, and what tools does it have?** →
|
||||||
[`turn-loop/`](turn-loop/README.md) — the loop, binary shape, turn
|
[`turn-loop/`](turn-loop/README.md) — the loop, binary shape, turn
|
||||||
|
|
|
||||||
|
|
@ -1,109 +1,97 @@
|
||||||
# Agent hierarchy & privileges
|
# Agent roster & privileges
|
||||||
|
|
||||||
<!-- vale write-good.Passive = NO -->
|
<!-- vale write-good.Passive = NO -->
|
||||||
|
|
||||||
Every agent has a place in an operator-editable parent/child tree, used
|
Agents are a **flat set**, with no parent/child tree: #4472 removed the
|
||||||
to scope which agents can manage which others. This doc covers how
|
`parent` field `topology.json` used to carry, and every mechanism that
|
||||||
hive-c0re stores and edits the tree today, the rules that are meant to run on top
|
read it. The capability store scopes which agents can manage which
|
||||||
of it once enforcement is finished, and where the manager still gets
|
others; a tree position no longer scopes anything.
|
||||||
special-cased in the meantime, as a tracked cleanup.
|
|
||||||
|
|
||||||
<!-- vale write-good.Passive = YES -->
|
<!-- vale write-good.Passive = YES -->
|
||||||
|
|
||||||
## Where the tree lives
|
This doc covers what the roster file is now, what the removal took with
|
||||||
|
it, and where the manager still gets special-cased, as a tracked
|
||||||
|
cleanup.
|
||||||
|
|
||||||
Topology lives in the hive-c0re-owned **meta repo**, alongside
|
## Where the roster lives
|
||||||
|
|
||||||
|
The roster lives in the hive-c0re-owned **meta repo**, alongside
|
||||||
`flake.nix`, at `/var/lib/hyperhive/meta/topology.json`:
|
`flake.nix`, at `/var/lib/hyperhive/meta/topology.json`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
["alice", "bob", "ruth"]
|
||||||
"ruth": null,
|
|
||||||
"alice": null,
|
|
||||||
"bob": "alice"
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`null` = root-level agent. New agents **default to root** — there is no
|
One entry per agent the hive knows about, in name order. The file
|
||||||
structural manager that everything hangs under. The operator builds
|
carries no per-agent value any more, and encodes no ordering or
|
||||||
hierarchy explicitly: an agent gets a parent edge written before its
|
grouping — it answers exactly one question, _which agents exist,_ and
|
||||||
first spawn, or the operator reparents it afterwards (so `bob` above sits under
|
`topology::all_agents` is the only reader that matters.
|
||||||
`alice`). Any agent is reparentable, the bootstrap container (`ruth`)
|
|
||||||
included — it's just another root. The manager is reparentable like any other agent; there's no
|
|
||||||
"structurally root" carve-out. Its privileges live on its MCP socket,
|
|
||||||
not its tree position (see _Manager special-casing today_ below).
|
|
||||||
|
|
||||||
### Reparenting
|
That reader is a permission boundary: the set it returns is what an agent
|
||||||
|
holding the `ManageRootAgent` capability gets bind-mounted (each other
|
||||||
|
agent's `state` read-write and `config` read-only; never `harness`). An
|
||||||
|
agent holding no capability sees its own dirs and nothing else. See
|
||||||
|
[`persistence.md`](persistence.md)'s _Cross-agent access to state._
|
||||||
|
|
||||||
- CLI: `hivectl agent <child> set-parent --parent <new>` (or `--root`
|
### Reading the pre-#4472 format
|
||||||
to promote). Pass exactly one of `--parent` / `--root`.
|
|
||||||
- Dashboard: `POST /api/topology/set-parent` (form fields `child`,
|
|
||||||
optional `new_parent` — absent / empty ⇒ promote to root).
|
|
||||||
- Wire: `HostRequest::SetParent { child, new_parent: Option<String> }`.
|
|
||||||
|
|
||||||
All three go through the same validation, which refuses:
|
`topology.json` used to be a map of `name → parent | null`. The reader
|
||||||
|
still accepts that shape and keeps its keys, so a hive upgrading across
|
||||||
- unknown `child` / `new_parent` (typo guard),
|
the change reads the same roster rather than an empty one. An empty
|
||||||
- self-parenting,
|
roster costs more than a cosmetic gap: every capability holder loses its
|
||||||
- cycles (a bounded ancestor walk — moving the manager under one of
|
mounts until the next reconcile pass writes the array form.
|
||||||
its own descendants is the only real safety concern here, and it's
|
|
||||||
caught the same way as any other agent).
|
|
||||||
|
|
||||||
Setting a parent to its current value is a no-op (no disk write). A
|
|
||||||
successful change triggers an immediate rescan, so connected dashboard
|
|
||||||
viewers see the tree repaint without polling.
|
|
||||||
|
|
||||||
### Why meta, not per-agent `agent.nix`
|
### Why meta, not per-agent `agent.nix`
|
||||||
|
|
||||||
An agent shouldn't be able to claim a parent without that parent's
|
An agent shouldn't be able to add itself to a set that governs who may
|
||||||
consent, and operator-driven re-parenting shouldn't require touching
|
reach its state dir. The roster IS a system-level fact; meta is where
|
||||||
the moved agent's config. Topology IS a system-level concern; meta is
|
system-level facts live.
|
||||||
where system-level facts live.
|
|
||||||
|
|
||||||
### How `topology.json` gets updated
|
### How `topology.json` gets updated
|
||||||
|
|
||||||
- **Read** — parsed into an agent→parent map; a missing or unparsable
|
- **Read** — parsed into a set of names; a missing or unparsable file
|
||||||
file degrades safely to "every agent is root" (covers a fresh
|
degrades safely to "no agents" (covers a fresh install that hasn't
|
||||||
install that hasn't synced yet).
|
synced yet).
|
||||||
- **Reconcile** — runs alongside the periodic meta/flake regeneration.
|
- **Reconcile** — runs alongside the periodic meta/flake regeneration.
|
||||||
New agents default to root unless they already carry an explicit
|
Adds newly-spawned agents, drops removed ones. Reconcile keeps agents
|
||||||
parent edge written before their first spawn; Reconcile preserves
|
whose config repo exists but that haven't spawned yet, so the gap until
|
||||||
existing entries (including operator overrides); removed agents drop.
|
the container appears doesn't churn the file.
|
||||||
Agents whose config repo exists but that haven't spawned yet keep
|
|
||||||
their edge too, so it survives the gap until the container actually
|
No write API and no operator verb reach this file. Reconcile derives it
|
||||||
appears.
|
from which agents exist, so the next pass overwrites a hand edit.
|
||||||
- **Inject** — hive-c0re exposes each container's parent (if any) to its own
|
|
||||||
environment as `HIVE_PARENT`, so the harness / system-prompt
|
|
||||||
renderer can see it.
|
|
||||||
- **Surface** — every rescan re-reads `topology.json` and populates
|
|
||||||
`ContainerView.parent`, which the dashboard renders as a tree.
|
|
||||||
|
|
||||||
See `hive-c0re/src/agent_config/topology.rs` and `hive-c0re/src/meta.rs`'s
|
See `hive-c0re/src/agent_config/topology.rs` and `hive-c0re/src/meta.rs`'s
|
||||||
module docs for the exact call chain.
|
module docs for the exact call chain.
|
||||||
|
|
||||||
### Current limitation: state-dir visibility lags topology
|
## What the parent field used to do
|
||||||
|
|
||||||
Reparenting today is purely a JSON edit. Only the top-level manager
|
Recorded so a reader who finds one of these in an old branch, an issue
|
||||||
(`root`) gets `/var/lib/hyperhive/agents` bind-mounted at `/agents` in
|
thread or a stale comment knows each one went away rather than moved:
|
||||||
its container, so sub-agents don't yet see their would-be children's
|
|
||||||
state dirs. Once sub-manager bind mounts land alongside capability
|
|
||||||
enforcement, reparenting will grow a companion
|
|
||||||
umount-old / mount-new / restart-cascade step.
|
|
||||||
|
|
||||||
## Planned topology semantics (once ancestor-based enforcement lands)
|
| gone | what replaced it |
|
||||||
|
| -------------------------------------------- | --------------------------------------------------- |
|
||||||
|
| `<parent>` recipient sentinel | address `operator` directly |
|
||||||
|
| `<children>` fan-out recipient | nothing — name the recipients, or broadcast to `*` |
|
||||||
|
| `hivectl agent <name> set-parent` | nothing |
|
||||||
|
| `POST /api/topology/set-parent{,-bulk}` | nothing |
|
||||||
|
| `HostRequest::SetParent` | nothing |
|
||||||
|
| `NodeKind::Reparent` and its DAG template | nothing |
|
||||||
|
| `HIVE_PARENT` on the container | nothing — no consumer ever read it |
|
||||||
|
| every agent's grant over its direct children | the `ManageRootAgent` capability, for every agent |
|
||||||
|
| rebuild ordering by topology depth | alphabetical, which the depth sort already produced |
|
||||||
|
|
||||||
| operation | who can do it |
|
<!-- vale write-good.Passive = NO -->
|
||||||
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
||||||
| config change via forge PR (any descendant's config) | any ancestor |
|
|
||||||
| moderate reminders (cancel any open thread of a descendant) | any ancestor |
|
|
||||||
| `send` / `recv` routing | parent ↔ same-parent siblings ↔ self ↔ descendants; explicit allow-list for anyone else |
|
|
||||||
|
|
||||||
"Ancestor" walks `ContainerView.parent` chains; a visited-set guards against
|
The last row is the one with teeth: an agent that used to reach a child's
|
||||||
cycles at dispatch time (a malformed `topology.json` can't lock
|
state dir by virtue of being its parent no longer reaches it at all
|
||||||
the dispatcher into a loop).
|
unless it holds `ManageRootAgent`. That narrowing is the intended
|
||||||
|
consequence of removing the field, not a side effect of it.
|
||||||
|
|
||||||
|
<!-- vale write-good.Passive = YES -->
|
||||||
|
|
||||||
## Manager special-casing today
|
## Manager special-casing today
|
||||||
|
|
||||||
Enforcement of the ancestor rules above isn't fully wired yet, so the
|
Capability enforcement isn't fully wired yet, so the
|
||||||
**manager (`ruth`) still gets some hard-coded special treatment**
|
**manager (`ruth`) still gets some hard-coded special treatment**
|
||||||
other agents don't:
|
other agents don't:
|
||||||
|
|
||||||
|
|
@ -113,13 +101,12 @@ other agents don't:
|
||||||
key, and nixos-container name are all `ruth` (container `h-ruth`).
|
key, and nixos-container name are all `ruth` (container `h-ruth`).
|
||||||
`hive-c0re` spawns it directly at boot if missing, with no operator
|
`hive-c0re` spawns it directly at boot if missing, with no operator
|
||||||
approval step — every other agent goes through a `Spawn` approval.
|
approval step — every other agent goes through a `Spawn` approval.
|
||||||
Topology-wise, `ruth` is still just another root agent.
|
Roster-wise, `ruth` is just another entry.
|
||||||
- **Wire-protocol** — the privileged `Request` variants
|
- **Wire-protocol** — the privileged `Request` variants
|
||||||
(`Kill` / `Start` / `Restart` / `Update`; `GetLogs`) — marked
|
(`Kill` / `Start` / `Restart` / `Update`; `GetLogs`) — marked
|
||||||
`*(privileged)*` in `hive-core-agent-sock`'s unified `Request` enum —
|
`*(privileged)*` in `hive-core-agent-sock`'s unified `Request` enum —
|
||||||
are reachable only from the manager's socket flavour today. Planned
|
are reachable only from the manager's socket flavour today; each is
|
||||||
rule for each is in the table above ("any ancestor" for
|
planned to become a capability check. One exception: `Wake` (inject a `from: <X>` message into the
|
||||||
lifecycle/logs). One exception: `Wake` (inject a `from: <X>` message into the
|
|
||||||
caller's own inbox) isn't really privileged — every per-agent daemon
|
caller's own inbox) isn't really privileged — every per-agent daemon
|
||||||
(for example `hive-forge-notify`) needs it, and sub-agents already have the
|
(for example `hive-forge-notify`) needs it, and sub-agents already have the
|
||||||
equivalent on their own socket.
|
equivalent on their own socket.
|
||||||
|
|
@ -128,9 +115,8 @@ other agents don't:
|
||||||
manage any agent's state dir — config isn't authored there, since a
|
manage any agent's state dir — config isn't authored there, since a
|
||||||
real config change is a PR from a clone), plus RO mounts for
|
real config change is a PR from a clone), plus RO mounts for
|
||||||
`/applied` (diff against what's deployed) and `/meta` (system-wide
|
`/applied` (diff against what's deployed) and `/meta` (system-wide
|
||||||
deploy log). Planned: each agent gets RW to `/agents/<descendant>/`
|
deploy log). That grant is the `ManageRootAgent` capability now, and
|
||||||
for just its own subtree — the manager's full-forest RW becomes the
|
ruth holds it; no name check remains. hive-c0re will
|
||||||
"root's subtree is everything" case of that same rule. hive-c0re will
|
|
||||||
gate RO `/meta` access on a "meta read" capability; no agent-facing
|
gate RO `/meta` access on a "meta read" capability; no agent-facing
|
||||||
path writes `flake.lock` any more — `request_update_meta_inputs` was
|
path writes `flake.lock` any more — `request_update_meta_inputs` was
|
||||||
removed, leaving the operator dashboard's `POST
|
removed, leaving the operator dashboard's `POST
|
||||||
|
|
@ -150,8 +136,8 @@ manager-only overrides exist across `hive-c0re` today: loose-ends
|
||||||
visibility (manager sees hive-wide, sub-agents only their own),
|
visibility (manager sees hive-wide, sub-agents only their own),
|
||||||
`destroy` refusing to act on the manager, and crash-watch skipping
|
`destroy` refusing to act on the manager, and crash-watch skipping
|
||||||
the manager (it autorestarts via systemd instead of going through
|
the manager (it autorestarts via systemd instead of going through
|
||||||
the crash-watch loop). Each is planned to become an
|
the crash-watch loop). Each is planned to become a capability
|
||||||
ancestor/descendant check instead of a manager-name check — see the
|
check instead of a manager-name check — see the
|
||||||
module docs for `loose_ends.rs`, `stores/broker.rs`, `actions.rs`,
|
module docs for `loose_ends.rs`, `stores/broker.rs`, `actions.rs`,
|
||||||
and `workers/crash_watch.rs` for the current owner-check logic in
|
and `workers/crash_watch.rs` for the current owner-check logic in
|
||||||
each. (The harness handles reminder cancellation fully in-agent — see
|
each. (The harness handles reminder cancellation fully in-agent — see
|
||||||
|
|
@ -162,18 +148,6 @@ the note on `CancelLooseEndKind::Reminder` in
|
||||||
None of the above is a stable interface — treat the module doc
|
None of the above is a stable interface — treat the module doc
|
||||||
comments as the source of truth for exactly which checks exist today.
|
comments as the source of truth for exactly which checks exist today.
|
||||||
|
|
||||||
## Future work: sub-agents inside the same container
|
|
||||||
|
|
||||||
When enabled for an agent, it will be able to spawn temporary
|
|
||||||
"sub-agents" that run inside its own container — lighter than a full
|
|
||||||
nspawn agent. Open questions, not yet wired:
|
|
||||||
|
|
||||||
- Inherit caps from parent, or take an explicit narrower set?
|
|
||||||
- Survive container restart, or always ephemeral?
|
|
||||||
- Inbox: separate from parent, or shared?
|
|
||||||
- Filesystem: share parent's `/state` RW, or a sub-dir?
|
|
||||||
- Identity: distinct broker recipient name, or address the parent?
|
|
||||||
|
|
||||||
## Harness systemd unit shape
|
## Harness systemd unit shape
|
||||||
|
|
||||||
One harness serve binary (`hive-agent`, with its `hive-agent-mcp`
|
One harness serve binary (`hive-agent`, with its `hive-agent-mcp`
|
||||||
|
|
@ -240,6 +214,5 @@ uid 0 and have the setuid bit set."
|
||||||
## Cross-references
|
## Cross-references
|
||||||
|
|
||||||
- Milestone: "Agent privileges and sub-agents" (tracked internally)
|
- Milestone: "Agent privileges and sub-agents" (tracked internally)
|
||||||
- Dashboard render: "show agent topology in container list" (tracked internally)
|
|
||||||
- Audit table source: milestone comment (tracked internally)
|
- Audit table source: milestone comment (tracked internally)
|
||||||
- Operator/agent trust boundary (orthogonal axis): [`boundary.md`](../trust-boundary/boundary.md)
|
- Operator/agent trust boundary (orthogonal axis): [`boundary.md`](../trust-boundary/boundary.md)
|
||||||
|
|
|
||||||
|
|
@ -483,12 +483,12 @@ approval card. See `docs/web-ui/dashboard.md`.
|
||||||
|
|
||||||
### Submitting agent's view of config repos
|
### Submitting agent's view of config repos
|
||||||
|
|
||||||
Every parent agent's container has its **direct children's** config
|
An agent holding `ManageRootAgent` has every other agent's config repo
|
||||||
repos bind-mounted **read-only** (topology-driven:
|
bind-mounted **read-only** (`hive-c0re/src/lifecycle/host_config.rs`
|
||||||
`hive-c0re/src/lifecycle/host_config.rs` calls `bind_child_agent_dirs`
|
calls `bind_child_agent_dirs` for each entry in
|
||||||
for each entry in
|
`topology::all_agents()`). it's a copy to *read* another agent's
|
||||||
`topology::children_of(agent_name)`). it's a copy to *read* a child's
|
current config — not an editing surface. An agent without the
|
||||||
current config — not an editing surface.
|
capability sees no other agent's config at all.
|
||||||
|
|
||||||
An agent with the `approvals` tool group submits a change the same way
|
An agent with the `approvals` tool group submits a change the same way
|
||||||
it makes any other change: **clone the child's config repo from the
|
it makes any other change: **clone the child's config repo from the
|
||||||
|
|
|
||||||
|
|
@ -335,24 +335,29 @@ Under `/var/lib/hyperhive/agents/<name>/`:
|
||||||
- `hyperhive-turn-stats.sqlite` — per-turn timing stats.
|
- `hyperhive-turn-stats.sqlite` — per-turn timing stats.
|
||||||
- `hyperhive-model` — single-line model name override file.
|
- `hyperhive-model` — single-line model name override file.
|
||||||
|
|
||||||
### Parent access to child state
|
### Cross-agent access to state
|
||||||
|
|
||||||
A parent agent gets each direct child's `state` dir bind-mounted
|
An agent holding the `ManageRootAgent` capability gets every other
|
||||||
**read-write** and its `config` dir **read-only**
|
agent's `state` dir bind-mounted **read-write** and its `config` dir
|
||||||
(`bind_child_agent_dirs` in `lifecycle/host_config.rs`). The RW on
|
**read-only** (`bind_child_agent_dirs` in `lifecycle/host_config.rs`).
|
||||||
`state` is deliberate, not an oversight: a parent manages its children,
|
The RW on `state` is deliberate, not an oversight: the holder recovers
|
||||||
which includes writing into a child's state for recovery (for example seeding
|
other agents, which includes writing into their state (for example
|
||||||
notes, clearing a stuck sentinel) as well as reading it.
|
seeding notes, clearing a stuck sentinel) as well as reading it.
|
||||||
|
|
||||||
**`harness` isn't mounted at all.** It holds the child's own runtime
|
This is the **only** cross-agent mount. #4472 removed the topology
|
||||||
|
parent field, and with it the unconditional grant every agent used to
|
||||||
|
get over its own direct children — an agent holding no capability now
|
||||||
|
sees its own dirs and nothing else.
|
||||||
|
|
||||||
|
**`harness` isn't mounted at all.** It holds that agent's own runtime
|
||||||
material — `bash-tasks/`, the turn-stats and event sqlite dbs — and
|
material — `bash-tasks/`, the turn-stats and event sqlite dbs — and
|
||||||
nothing argues for a parent reading it, let alone writing it. hive-c0re
|
nothing argues for anyone else reading it, let alone writing it.
|
||||||
reads a child's harness dir **directly on the host** when it wants
|
hive-c0re reads a harness dir **directly on the host** when it wants
|
||||||
those stats, which needs no mount into the parent.
|
those stats, which needs no mount into another container.
|
||||||
|
|
||||||
<!-- vale write-good.Passive = NO -->
|
<!-- vale write-good.Passive = NO -->
|
||||||
**`config` is read-only, including for the parent.** A config change is
|
**`config` is read-only, including for the holder.** A config change is
|
||||||
a PR on the child's config repo, made from a clone and merged after
|
a PR on that agent's config repo, made from a clone and merged after
|
||||||
review — so the bind-mounted `config` dir is a *copy to read*, never a
|
review — so the bind-mounted `config` dir is a *copy to read*, never a
|
||||||
tree anyone edits in place. Mounting it writable would leave a second
|
tree anyone edits in place. Mounting it writable would leave a second
|
||||||
path to the same file that skips the review entirely, which makes the
|
path to the same file that skips the review entirely, which makes the
|
||||||
|
|
@ -361,15 +366,15 @@ boundary a convention rather than a permission.
|
||||||
|
|
||||||
<!-- vale write-good.Passive = NO -->
|
<!-- vale write-good.Passive = NO -->
|
||||||
⚠️ Don't confuse it with the config-repo seeding hive-c0re does at
|
⚠️ Don't confuse it with the config-repo seeding hive-c0re does at
|
||||||
spawn (`lifecycle::setup_proposed`): that writes the child's initial
|
spawn (`lifecycle::setup_proposed`): that writes the agent's initial
|
||||||
config repo as **hive-c0re, against the host path**, and `read_only` on a bind
|
config repo as **hive-c0re, against the host path**, and `read_only` on a bind
|
||||||
constrains writers *inside* a container only. The two are unrelated —
|
constrains writers *inside* a container only. The two are unrelated —
|
||||||
conflating them can lead you to reason your way into thinking this
|
conflating them can lead you to reason your way into thinking this
|
||||||
mount should be writable when it shouldn't.
|
mount should be writable when it shouldn't.
|
||||||
<!-- vale write-good.Passive = YES -->
|
<!-- vale write-good.Passive = YES -->
|
||||||
|
|
||||||
Per-child isolation still holds: a container only ever has its *own*
|
Isolation still holds by default: a container has its *own* dirs and,
|
||||||
dirs plus its direct children's bind-mounted, never a sibling's.
|
unless it holds the capability, nothing else.
|
||||||
|
|
||||||
Under `/var/lib/hyperhive/applied/<name>/` — the hive-c0re-only
|
Under `/var/lib/hyperhive/applied/<name>/` — the hive-c0re-only
|
||||||
applied repo. Tracks `flake.nix` (module-only boilerplate; never
|
applied repo. Tracks `flake.nix` (module-only boilerplate; never
|
||||||
|
|
@ -387,13 +392,11 @@ Contents:
|
||||||
`nixosConfigurations.<n>` output per agent. `flake.lock` is the
|
`nixosConfigurations.<n>` output per agent. `flake.lock` is the
|
||||||
canonical "what's deployed where." The git log is the deploy
|
canonical "what's deployed where." The git log is the deploy
|
||||||
audit trail (one commit per successful deploy or hyperhive bump).
|
audit trail (one commit per successful deploy or hyperhive bump).
|
||||||
- `topology.json` — parent/child agent graph
|
- `topology.json` — the agent roster (`["alice", "bob", "ruth"]`).
|
||||||
(`{ "alice": "root", "bob": "alice", "root": null }`).
|
Written by `topology::reconcile` on every meta sync; read by
|
||||||
Written by `topology::apply_set_parent` (the pure move-validating
|
`topology::all_agents`, which is the set the `ManageRootAgent`
|
||||||
transform) via `meta::bulk_commit_topology` (the committer — see the
|
capability grants mounts over. Carried a `parent` per agent until
|
||||||
`Reparent` node in [`docs/scheduler/coordinator.md`](../scheduler/coordinator.md)); read by
|
#4472; the reader still accepts that shape and keeps its keys.
|
||||||
the dashboard, the renderer, and `<parent>` / `<children>` recipient
|
|
||||||
resolution.
|
|
||||||
- `tool-groups.json` — per-agent MCP tool group grants
|
- `tool-groups.json` — per-agent MCP tool group grants
|
||||||
(`{ "alice": ["messaging", "inbox", "execution"] }`). Written by
|
(`{ "alice": ["messaging", "inbox", "execution"] }`). Written by
|
||||||
`tool_groups::set_groups`; injected as `HIVE_TOOL_GROUPS` env
|
`tool_groups::set_groups`; injected as `HIVE_TOOL_GROUPS` env
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ See [`approvals.md`](../agent-lifecycle/approvals.md) for the full flow.
|
||||||
### 8 · Useful host commands
|
### 8 · Useful host commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Roster: all agents, status, rev, parent, pending reminders
|
# Roster: all agents, status, rev, pending reminders
|
||||||
hivectl list-agents
|
hivectl list-agents
|
||||||
|
|
||||||
# Restart a stuck container (no rebuild)
|
# Restart a stuck container (no rebuild)
|
||||||
|
|
|
||||||
|
|
@ -87,24 +87,13 @@ angle-bracket and asterisk shapes below are structurally safe.
|
||||||
(`socket_server::handle_send` fans out via `Coordinator::broadcast_send`).
|
(`socket_server::handle_send` fans out via `Coordinator::broadcast_send`).
|
||||||
- `operator` — the human at the dashboard. Messages accumulate in the
|
- `operator` — the human at the dashboard. Messages accumulate in the
|
||||||
inbox view; no agent ever `recv`'s them.
|
inbox view; no agent ever `recv`'s them.
|
||||||
- `<parent>` — the sender's parent per `topology.json`. Rewritten at
|
|
||||||
send time by `topology::resolve_recipient`: looks up
|
|
||||||
`parent_of(sender)` and falls back to `operator` when the sender is
|
|
||||||
a root agent (or absent from topology entirely). Lets agents address
|
|
||||||
their parent without learning the label, so runtime reparenting
|
|
||||||
propagates with zero agent-side restart.
|
|
||||||
- `<children>` — fan-out to every direct descendant of the sender per
|
|
||||||
`topology.json`. Resolved in `socket_server::handle_send` via
|
|
||||||
`topology::children_of(sender)`: it delivers one message to each
|
|
||||||
child, bypassing the allow-list check (structural fan-out targets are
|
|
||||||
never user-listed peers). No-op for leaf agents (returns `Ok` when the
|
|
||||||
child set is empty). Lets a sub-manager nudge its subtree without
|
|
||||||
enumerating labels.
|
|
||||||
|
|
||||||
When a `<children>` or `<parent>` send resolves to real recipients, the
|
`<parent>` and `<children>` were two more, resolved against a
|
||||||
broker stores the *resolved* labels as the message recipients — the
|
`topology.json` parent field. #4472 removed that field and both
|
||||||
dashboard and recv side see the real routes. The sentinels are purely
|
sentinels with it: address `operator` where you would have said
|
||||||
send-time addressing conveniences.
|
`<parent>`, and name the recipients (or broadcast to `*`) where you
|
||||||
|
would have said `<children>`. Nothing rewrites a recipient at send time
|
||||||
|
any more — what an agent passes is what the broker stores.
|
||||||
|
|
||||||
## Wire protocol
|
## Wire protocol
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,37 +55,36 @@ Cheap — no build slot:
|
||||||
|
|
||||||
<!-- vale write-good.Passive = NO -->
|
<!-- vale write-good.Passive = NO -->
|
||||||
|
|
||||||
| Node | Behavior |
|
| Node | Behavior |
|
||||||
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `MergeVerify` | the deploy's pre-merge gate — PR-head drift check, fetch, `verify_commit` eval. Mutates nothing, so a rejection here needs no compensation |
|
| `MergeVerify` | the deploy's pre-merge gate — PR-head drift check, fetch, `verify_commit` eval. Mutates nothing, so a rejection here needs no compensation |
|
||||||
| `DeployTail` | the deploy's `AfterAny` compensation + bookkeeping tail: (1) rolls `applied/main` back from the parked `refs/hyperhive/rollback/<id>` and aborts the staged meta lock when the deploy never confirmed good; (2) mirrors whichever deploy tag landed to the forge config repo, always, best-effort; (3) posts the failing build log back onto the config PR when the deploy failed. Named for (2)/(3), which run on the success path too — not `AbortDeploy`. Infallible by construction |
|
| `DeployTail` | the deploy's `AfterAny` compensation + bookkeeping tail: (1) rolls `applied/main` back from the parked `refs/hyperhive/rollback/<id>` and aborts the staged meta lock when the deploy never confirmed good; (2) mirrors whichever deploy tag landed to the forge config repo, always, best-effort; (3) posts the failing build log back onto the config PR when the deploy failed. Named for (2)/(3), which run on the success path too — not `AbortDeploy`. Infallible by construction |
|
||||||
| `MetaSync` | the rebuild's meta preamble — rebuild-dir prep, idempotent meta `sync_agents`, optional per-agent relock. Holds the `MetaWindow` resource (below); deliberately its own node so the window never covers `Prebuild`'s multi-minute build |
|
| `MetaSync` | the rebuild's meta preamble — rebuild-dir prep, idempotent meta `sync_agents`, optional per-agent relock. Holds the `MetaWindow` resource (below); deliberately its own node so the window never covers `Prebuild`'s multi-minute build |
|
||||||
| `Provision` | first-spawn pre-create provisioning — proposed/applied repos, state subvolume, meta registration (`sync_agents`); runs ahead of `Create` so the `nixos-container create --flake meta#<name>` ref resolves. Store/meta-only, no container yet |
|
| `Provision` | first-spawn pre-create provisioning — proposed/applied repos, state subvolume, meta registration (`sync_agents`); runs ahead of `Create` so the `nixos-container create --flake meta#<name>` ref resolves. Store/meta-only, no container yet |
|
||||||
| `Reconcile` | idempotent power converge: read `wanted` (below) + observed state; start if `Up` & down (cold-start fallback included), stop if `Offline` & up, else noop |
|
| `Reconcile` | idempotent power converge: read `wanted` (below) + observed state; start if `Up` & down (cold-start fallback included), stop if `Offline` & up, else noop |
|
||||||
| `Start` | mechanical container start — runtime dir + drop-ins, `start_with_fallback`, MCP listener registration, the manager kick. Fanned out by a `Reconcile` that observed `wanted = Up` and the container down |
|
| `Start` | mechanical container start — runtime dir + drop-ins, `start_with_fallback`, MCP listener registration, the manager kick. Fanned out by a `Reconcile` that observed `wanted = Up` and the container down |
|
||||||
| `Stop` | mechanical container stop — `nixos-container` kill, MCP listener unregister, the `Killed` manager notify. Fanned out by a `Reconcile` that observed `wanted = Offline` and up |
|
| `Stop` | mechanical container stop — `nixos-container` kill, MCP listener unregister, the `Killed` manager notify. Fanned out by a `Reconcile` that observed `wanted = Offline` and up |
|
||||||
| `StopForUpdate` | mechanical `nixos-container stop` for the profile swap; never touches `wanted`; noop if already stopped |
|
| `StopForUpdate` | mechanical `nixos-container stop` for the profile swap; never touches `wanted`; noop if already stopped |
|
||||||
| `RebuildBookkeeping` | the swap's Ok-only bookkeeping tail — rev marker, forge/matrix sync, manager kick, rescan, meta-inputs snapshot; `AfterOk(Swap)` so it runs only on a successful swap (the DAG's `EmitRebuilt` tail node emits the `Rebuilt` manager event, not here). Split out of `Swap` for dashboard visibility + retry granularity, declares no resources of its own — a coordinated child of the `AgentWindow` brace |
|
| `RebuildBookkeeping` | the swap's Ok-only bookkeeping tail — rev marker, forge/matrix sync, manager kick, rescan, meta-inputs snapshot; `AfterOk(Swap)` so it runs only on a successful swap (the DAG's `EmitRebuilt` tail node emits the `Rebuilt` manager event, not here). Split out of `Swap` for dashboard visibility + retry granularity, declares no resources of its own — a coordinated child of the `AgentWindow` brace |
|
||||||
| `AgentWindow` | pure resource holder — the brace for one agent's rebuild. Declares the build slot + agent lease atomically and holds both for its whole subtree, so `Prebuild` and the `Signal`→`Drain` quiesce window run concurrently instead of one nested under the other. Performs no work; see _Braces_ |
|
| `AgentWindow` | pure resource holder — the brace for one agent's rebuild. Declares the build slot + agent lease atomically and holds both for its whole subtree, so `Prebuild` and the `Signal`→`Drain` quiesce window run concurrently instead of one nested under the other. Performs no work; see _Braces_ |
|
||||||
| `Signal` | set the graceful fence + kick, so the harness runs one stop-checkpoint turn |
|
| `Signal` | set the graceful fence + kick, so the harness runs one stop-checkpoint turn |
|
||||||
| `Drain` | await the harness clearing the fence, bounded by the 3-min graceful-stop timeout; resolves ok either way |
|
| `Drain` | await the harness clearing the fence, bounded by the 3-min graceful-stop timeout; resolves ok either way |
|
||||||
| `PauseSignal` | write the pause marker + mark `pause_pending`. No kick, unlike `Signal` — the harness's between-turns poll is already responsive enough, and `Signal`'s kick-message body ("you were just (re)started") would be actively misleading here |
|
| `PauseSignal` | write the pause marker + mark `pause_pending`. No kick, unlike `Signal` — the harness's between-turns poll is already responsive enough, and `Signal`'s kick-message body ("you were just (re)started") would be actively misleading here |
|
||||||
| `PauseDrain` | await the harness reporting `PauseAcknowledged`, bounded timeout; best-effort like `Drain` |
|
| `PauseDrain` | await the harness reporting `PauseAcknowledged`, bounded timeout; best-effort like `Drain` |
|
||||||
| `DestroyContainer` | `nixos-container destroy` + un-registration (drop from the roster, clear the ephemeral runtime dir). Runs downstream of a `Stop`, so deliberately excluded from `takes_container_down` — the container is already down by the time it claims |
|
| `DestroyContainer` | `nixos-container destroy` + un-registration (drop from the roster, clear the ephemeral runtime dir). Runs downstream of a `Stop`, so deliberately excluded from `takes_container_down` — the container is already down by the time it claims |
|
||||||
| `PurgeState` | the `purge = true` half of a destroy: delete the agent's state subvolume (via hive-priv) plus its state/applied dirs. Own node because it's conditional and the irreversible step |
|
| `PurgeState` | the `purge = true` half of a destroy: delete the agent's state subvolume (via hive-priv) plus its state/applied dirs. Own node because it's conditional and the irreversible step |
|
||||||
| `DestroyBookkeeping` | the post-destroy tail — meta sync, fail pending approvals, drop the power intent, notify the manager, rescan, re-emit the tombstone, resync tmpfiles. Same split rationale as `RebuildBookkeeping`/`Swap`. Its `purge` flag only selects the wording of the approval-failure reason and the manager notification — the destructive work is `PurgeState`'s |
|
| `DestroyBookkeeping` | the post-destroy tail — meta sync, fail pending approvals, drop the power intent, notify the manager, rescan, re-emit the tombstone, resync tmpfiles. Same split rationale as `RebuildBookkeeping`/`Swap`. Its `purge` flag only selects the wording of the approval-failure reason and the manager notification — the destructive work is `PurgeState`'s |
|
||||||
| `SetWanted` | write the durable power intent (`wanted = Up`/`Offline`) as the head node of a power-op DAG, replacing the old pre-submit side effect. Takes the agent lease even though it's a store write, so the intent write and the tail `Reconcile` are atomic per-agent — two racing power ops can't clobber each other's intent before either reconciles |
|
| `SetWanted` | write the durable power intent (`wanted = Up`/`Offline`) as the head node of a power-op DAG, replacing the old pre-submit side effect. Takes the agent lease even though it's a store write, so the intent write and the tail `Reconcile` are atomic per-agent — two racing power ops can't clobber each other's intent before either reconciles |
|
||||||
| `FinalizeDeploy` | deploy phase 3 — drop the rollback ref, plant `deployed/<id>`, commit the staged `flake.lock`. The first two git steps are fatal on purpose, so a confirmed-good deploy's outcome and the repo's state can't disagree |
|
| `FinalizeDeploy` | deploy phase 3 — drop the rollback ref, plant `deployed/<id>`, commit the staged `flake.lock`. The first two git steps are fatal on purpose, so a confirmed-good deploy's outcome and the repo's state can't disagree |
|
||||||
| `ResolveApproval` | tail of an approval-carrying DAG — resolve the approval row from how the work ended (`AfterAny`, one node emitted per outcome). Agentless: the approval row already names its agent |
|
| `ResolveApproval` | tail of an approval-carrying DAG — resolve the approval row from how the work ended (`AfterAny`, one node emitted per outcome). Agentless: the approval row already names its agent |
|
||||||
| `EmitRebuilt` | tail of a rebuild/perm-change — emit the agent's `Rebuilt` manager event (ok/fail per outcome, nothing on cancel). One node per agent _and_ per outcome |
|
| `EmitRebuilt` | tail of a rebuild/perm-change — emit the agent's `Rebuilt` manager event (ok/fail per outcome, nothing on cancel). One node per agent _and_ per outcome |
|
||||||
| `WriteDropin` | `set_nspawn_flags` + `set_resource_limits` + daemon-reload |
|
| `WriteDropin` | `set_nspawn_flags` + `set_resource_limits` + daemon-reload |
|
||||||
| `WritePermFile` | commit `tool-groups.json` / `capabilities.json` (single git commit under `META_LOCK`) + emit the P3RM1SS10NS snapshots |
|
| `WritePermFile` | commit `tool-groups.json` / `capabilities.json` (single git commit under `META_LOCK`) + emit the P3RM1SS10NS snapshots |
|
||||||
| `Reparent` | `set-parent` / `set-parent-bulk`: apply every `(child, new_parent)` move under one `META_LOCK` commit (`meta::bulk_commit_topology`), send the per-agent move notifications, rescan + diff-emit. Agentless like `MetaLock` — a bulk move can span multiple agents, and a reparent touches the meta repo, not any one container. `moves` is typed `(Ident, Option<Ident>)` pairs, not raw strings. Rides the existing `Template::MetaUpdate` variant rather than a dedicated one — it's internal-only (never reaches the graph wire), so the stand-in only affects `terminal_hook` dispatch (resolves to no hook either way) and history-retention bucketing |
|
| `ForgeSweep` | one-shot boot-time forge user/token sweep for every container (`forge::ensure_all`) as a first-class node, so it shows as real work on the dashboard instead of running invisibly in a bare `tokio::spawn`. Agentless |
|
||||||
| `ForgeSweep` | one-shot boot-time forge user/token sweep for every container (`forge::ensure_all`) as a first-class node, so it shows as real work on the dashboard instead of running invisibly in a bare `tokio::spawn`. Agentless |
|
| `MatrixSweep` | same as `ForgeSweep`, for matrix (`matrix::ensure_all`). The periodic 30-min re-sweep stays a background loop in `main.rs`; only the boot-time instance is a node |
|
||||||
| `MatrixSweep` | same as `ForgeSweep`, for matrix (`matrix::ensure_all`). The periodic 30-min re-sweep stays a background loop in `main.rs`; only the boot-time instance is a node |
|
| `WebhookRegister` | one-shot boot-time Forgejo webhook registration (`internal/knowledge` push→pull, `agent-configs` PR→approval). No-op until the core token, hive domain, and HMAC secret are all available. Agentless |
|
||||||
| `WebhookRegister` | one-shot boot-time Forgejo webhook registration (`internal/knowledge` push→pull, `agent-configs` PR→approval). No-op until the core token, hive domain, and HMAC secret are all available. Agentless |
|
| `KnowledgePull` | one-shot boot-time `/knowledge` pull (`knowledge::pull`), reconciling commits that landed while `hive-c0re` was down. Same rationale as `MatrixSweep`: the periodic hourly re-pull stays a background loop |
|
||||||
| `KnowledgePull` | one-shot boot-time `/knowledge` pull (`knowledge::pull`), reconciling commits that landed while `hive-c0re` was down. Same rationale as `MatrixSweep`: the periodic hourly re-pull stays a background loop |
|
| `WantedPull` | one-shot boot-time pull of the agent set the swarm controller declares for this hive (`wanted::pull`), converging the agents it names. No background loop behind this one — boot is the whole cadence; the deploy event (`swarm_status`) is the fast path, this repairs a missed one. Agentless |
|
||||||
| `WantedPull` | one-shot boot-time pull of the agent set the swarm controller declares for this hive (`wanted::pull`), converging the agents it names. No background loop behind this one — boot is the whole cadence; the deploy event (`swarm_status`) is the fast path, this repairs a missed one. Agentless |
|
|
||||||
|
|
||||||
<!-- vale write-good.Passive = YES -->
|
<!-- vale write-good.Passive = YES -->
|
||||||
|
|
||||||
|
|
@ -100,7 +99,7 @@ container build:
|
||||||
|
|
||||||
- **The deploy window** (`Resource::MetaWindow`): a global, capacity-1 queue
|
- **The deploy window** (`Resource::MetaWindow`): a global, capacity-1 queue
|
||||||
resource declared by every node kind that mutates the meta repo — `MetaSync`,
|
resource declared by every node kind that mutates the meta repo — `MetaSync`,
|
||||||
`MetaLock`, `WritePermFile`, `Reparent`, `Provision`'s agent registration, and
|
`MetaLock`, `WritePermFile`, `Provision`'s agent registration, and
|
||||||
`DeployWindow` — the deploy subtree's root, which holds it across every
|
`DeployWindow` — the deploy subtree's root, which holds it across every
|
||||||
phase below it (it declares `Resource::MetaWindow`). Two meta
|
phase below it (it declares `Resource::MetaWindow`). Two meta
|
||||||
mutations can therefore never interleave, so no commit lands inside another
|
mutations can therefore never interleave, so no commit lands inside another
|
||||||
|
|
@ -156,7 +155,6 @@ perm-change(a): WritePermFile(a) → «rebuild subgraph»
|
||||||
meta-update(inp): MetaLock(inp) →(in-DAG) «rebuild subgraph» per affected agent
|
meta-update(inp): MetaLock(inp) →(in-DAG) «rebuild subgraph» per affected agent
|
||||||
boot: (if any rev marker stale) MetaLock(hyperhive) →(in-DAG) «rebuild subgraph» per stale agent;
|
boot: (if any rev marker stale) MetaLock(hyperhive) →(in-DAG) «rebuild subgraph» per stale agent;
|
||||||
plus Reconcile(a) for every drifted agent (all ONE DAG)
|
plus Reconcile(a) for every drifted agent (all ONE DAG)
|
||||||
reparent(moves): Reparent(moves) (no rebuild — topology.json is read live)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Notable collapses:
|
Notable collapses:
|
||||||
|
|
@ -229,7 +227,7 @@ resources are free. Resources:
|
||||||
(`SetWanted` is a store write, not a container op, but takes the lease anyway
|
(`SetWanted` is a store write, not a container op, but takes the lease anyway
|
||||||
so a power-op DAG's intent write + reconcile is atomic — two racing ops can't
|
so a power-op DAG's intent write + reconcile is atomic — two racing ops can't
|
||||||
clobber intent before either reconciles.) **Lease-exempt**: `MetaSync`,
|
clobber intent before either reconciles.) **Lease-exempt**: `MetaSync`,
|
||||||
`Prebuild`, `Provision`, `MetaLock`, `WritePermFile`, `Reparent` — they
|
`Prebuild`, `Provision`, `MetaLock`, `WritePermFile` — they
|
||||||
touch the store / meta, not the running container, which is exactly why a
|
touch the store / meta, not the running container, which is exactly why a
|
||||||
stop can land while another DAG's prebuild is still building. Also exempt, for a different
|
stop can land while another DAG's prebuild is still building. Also exempt, for a different
|
||||||
reason, are the rebuild subtree's own members (`StopForUpdate`, `Swap`,
|
reason, are the rebuild subtree's own members (`StopForUpdate`, `Swap`,
|
||||||
|
|
@ -374,8 +372,7 @@ vs the current flake path) and persisted `wanted` intent, then:
|
||||||
|
|
||||||
1. **Config path** — when _any_ marker is stale, submit one `Boot`
|
1. **Config path** — when _any_ marker is stale, submit one `Boot`
|
||||||
DAG: a `MetaLock` (hyperhive input bump, non-fatal) that grows an in-DAG
|
DAG: a `MetaLock` (hyperhive input bump, non-fatal) that grows an in-DAG
|
||||||
`Rebuild` subgraph for each stale agent whose `wanted = Up` (topology-sorted,
|
`Rebuild` subgraph for each stale agent whose `wanted = Up` (name-sorted). Stale but wanted-offline agents get no boot-time nix work — their
|
||||||
parents first). Stale but wanted-offline agents get no boot-time nix work — their
|
|
||||||
rebuild happens on their next start (the start submit path upgrades a
|
rebuild happens on their next start (the start submit path upgrades a
|
||||||
stale start to rebuild+start), which is also why the lock bump runs even
|
stale start to rebuild+start), which is also why the lock bump runs even
|
||||||
when every stale agent is offline: those later start-upgrades must build
|
when every stale agent is offline: those later start-upgrades must build
|
||||||
|
|
@ -584,4 +581,4 @@ which is almost never desired. Leave off on non-x86 hosts.
|
||||||
- `docs/agent-lifecycle/approvals.md` — approval flow + scheduled prompts
|
- `docs/agent-lifecycle/approvals.md` — approval flow + scheduled prompts
|
||||||
- `docs/agent-lifecycle/persistence.md` — SQLite schema, state-dir layout
|
- `docs/agent-lifecycle/persistence.md` — SQLite schema, state-dir layout
|
||||||
- `docs/process/conventions.md` — wire protocol, recipient sentinels
|
- `docs/process/conventions.md` — wire protocol, recipient sentinels
|
||||||
- `docs/agent-lifecycle/agent-hierarchy.md` — topology and parent/child relations
|
- `docs/agent-lifecycle/agent-hierarchy.md` — the agent roster and privilege
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ This document contains the help content for the `hivectl` command-line program.
|
||||||
* [`hivectl agent kill`↴](#hivectl-agent-kill)
|
* [`hivectl agent kill`↴](#hivectl-agent-kill)
|
||||||
* [`hivectl agent destroy`↴](#hivectl-agent-destroy)
|
* [`hivectl agent destroy`↴](#hivectl-agent-destroy)
|
||||||
* [`hivectl agent rebuild`↴](#hivectl-agent-rebuild)
|
* [`hivectl agent rebuild`↴](#hivectl-agent-rebuild)
|
||||||
* [`hivectl agent set-parent`↴](#hivectl-agent-set-parent)
|
|
||||||
* [`hivectl agent set-limits`↴](#hivectl-agent-set-limits)
|
* [`hivectl agent set-limits`↴](#hivectl-agent-set-limits)
|
||||||
* [`hivectl agent choom`↴](#hivectl-agent-choom)
|
* [`hivectl agent choom`↴](#hivectl-agent-choom)
|
||||||
* [`hivectl agent watch`↴](#hivectl-agent-watch)
|
* [`hivectl agent watch`↴](#hivectl-agent-watch)
|
||||||
|
|
@ -351,7 +350,6 @@ Everything here targets a single named agent (`hivectl agent foo restart`, `hive
|
||||||
* `kill` — Hard-stop this managed container
|
* `kill` — Hard-stop this managed container
|
||||||
* `destroy` — Tear down this sub-agent container, keeping its state by default. No undo
|
* `destroy` — Tear down this sub-agent container, keeping its state by default. No undo
|
||||||
* `rebuild` — Apply pending config to this managed container
|
* `rebuild` — Apply pending config to this managed container
|
||||||
* `set-parent` — Move this agent in the topology tree — under a new parent, or to root
|
|
||||||
* `set-limits` — Declare this agent's CPU/memory limits, overriding the hive-wide defaults
|
* `set-limits` — Declare this agent's CPU/memory limits, overriding the hive-wide defaults
|
||||||
* `choom` — Open an interactive Claude session inside this agent's container
|
* `choom` — Open an interactive Claude session inside this agent's container
|
||||||
* `watch` — Follow this agent's live turn/tool-call event stream from the CLI
|
* `watch` — Follow this agent's live turn/tool-call event stream from the CLI
|
||||||
|
|
@ -464,19 +462,6 @@ Apply pending config to this managed container
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agent set-parent`
|
|
||||||
|
|
||||||
Move this agent in the topology tree — under a new parent, or to root
|
|
||||||
|
|
||||||
**Usage:** `hivectl agent set-parent [OPTIONS]`
|
|
||||||
|
|
||||||
###### **Options:**
|
|
||||||
|
|
||||||
* `--parent <PARENT>` — New parent agent name. Mutually exclusive with `--root`
|
|
||||||
* `--root` — Promote this agent to root (no parent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl agent set-limits`
|
## `hivectl agent set-limits`
|
||||||
|
|
||||||
Declare this agent's CPU/memory limits, overriding the hive-wide defaults.
|
Declare this agent's CPU/memory limits, overriding the hive-wide defaults.
|
||||||
|
|
|
||||||
|
|
@ -153,12 +153,11 @@ hivectl agent iris resume # let it drive turns again, draining what que
|
||||||
```
|
```
|
||||||
|
|
||||||
`list-agents` prints a padded table with one row per managed agent —
|
`list-agents` prints a padded table with one row per managed agent —
|
||||||
`NAME STATUS REV PARENT REMIND`. STATUS collapses the health flags
|
`NAME STATUS REV REMIND`. STATUS collapses the health flags
|
||||||
(`running` / `stopped`, plus ` paused` / ` needs-login` / ` needs-update`
|
(`running` / `stopped`, plus ` paused` / ` needs-login` / ` needs-update`
|
||||||
when set — `paused` is orthogonal to running, see below);
|
when set — `paused` is orthogonal to running, see below);
|
||||||
REV is the first 12 chars of the agent's locked config sha; PARENT is its
|
REV is the first 12 chars of the agent's locked config sha; REMIND is the
|
||||||
place in the topology tree (`-` for a root agent); REMIND is the count of
|
count of pending reminders. It reuses the same per-agent aggregation the dashboard
|
||||||
pending reminders. It reuses the same per-agent aggregation the dashboard
|
|
||||||
renders, so the CLI roster and the web UI never drift. `--json` emits the
|
renders, so the CLI roster and the web UI never drift. `--json` emits the
|
||||||
raw rows instead of the table.
|
raw rows instead of the table.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,9 +111,7 @@ merge work.
|
||||||
|
|
||||||
Plugin install failures aren't fatal: each entry comes back as a
|
Plugin install failures aren't fatal: each entry comes back as a
|
||||||
human-readable failure string that gets routed via
|
human-readable failure string that gets routed via
|
||||||
`Surface::send_to_parent` to the agent's topology parent (the
|
`Surface::send_to_operator` to the operator.
|
||||||
broker resolves `<parent>` per `topology::resolve_recipient`; root
|
|
||||||
agents and the manager fall through to operator).
|
|
||||||
|
|
||||||
### Turn outcomes
|
### Turn outcomes
|
||||||
|
|
||||||
|
|
@ -128,7 +126,7 @@ else a `TurnError`) drives the post-claude branch:
|
||||||
| `Err(AuthFailed)` | emit `needs_login_idle` sentinel, requeue inflight, park in `wait_for_login` |
|
| `Err(AuthFailed)` | emit `needs_login_idle` sentinel, requeue inflight, park in `wait_for_login` |
|
||||||
| `Err(SessionNotFound)` | resume + create self-heal both missed ("shouldn't happen"); requeue inflight so the next turn creates fresh — no status park, message not dropped |
|
| `Err(SessionNotFound)` | resume + create self-heal both missed ("shouldn't happen"); requeue inflight so the next turn creates fresh — no status park, message not dropped |
|
||||||
| `Err(ApiStall)` | idle watchdog killed claude after `HIVE_TURN_IDLE_SECS` (default 600) of output silence; sleep `HIVE_STALL_SLEEP_SECS` (default 60), requeue inflight, status back to `online` |
|
| `Err(ApiStall)` | idle watchdog killed claude after `HIVE_TURN_IDLE_SECS` (default 600) of output silence; sleep `HIVE_STALL_SLEEP_SECS` (default 60), requeue inflight, status back to `online` |
|
||||||
| `Err(Failed(err))` | route `[system] \`<qualified-label>\` claude turn failed:\n<err>` to `<parent>` via `send_to_parent` |
|
| `Err(Failed(err))` | route `[system] \`<qualified-label>\` claude turn failed:\n<err>` to `operator` via `send_to_operator` |
|
||||||
|
|
||||||
`ApiStall` catches an Anthropic API stall — a multi-retry connection storm where
|
`ApiStall` catches an Anthropic API stall — a multi-retry connection storm where
|
||||||
the stream goes silent for minutes. The idle watchdog lives in `hive-claude`'s
|
the stream goes silent for minutes. The idle watchdog lives in `hive-claude`'s
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,13 @@ are opt-in via the P3RM1SS10NS tab.
|
||||||
`recv(max?)`, `ack_until(up_to)`.
|
`recv(max?)`, `ack_until(up_to)`.
|
||||||
|
|
||||||
- `send` — message a peer (logical name) or the operator
|
- `send` — message a peer (logical name) or the operator
|
||||||
(`to: "operator"`). Use `to: "<parent>"` to address the topology
|
(`to: "operator"`). Optional `in_reply_to: i64` links the
|
||||||
parent without hardcoding the label; the broker resolves the
|
|
||||||
sentinel at delivery time. Optional `in_reply_to: i64` links the
|
|
||||||
message to a prior id for thread rendering. Per-agent
|
message to a prior id for thread rendering. Per-agent
|
||||||
`services.hyperhive.agent.allowedRecipients` (default: empty = unrestricted) limits
|
`services.hyperhive.agent.allowedRecipients` (default: empty = unrestricted) limits
|
||||||
which names `send` accepts — useful for sandboxing: set
|
which names `send` accepts — useful for sandboxing: set
|
||||||
`[ "operator" ]` to restrict a sub-agent to operator messages only
|
`[ "operator" ]` to restrict a sub-agent to operator messages only.
|
||||||
(the topology parent is always reachable regardless of this list —
|
The operator stays reachable regardless of this list, so an agent can
|
||||||
that carve-out is structural, keyed on parent relationship, not name).
|
always report a block.
|
||||||
- `recv` — drain inbox. Always an immediate peek, never blocks. `max`
|
- `recv` — drain inbox. Always an immediate peek, never blocks. `max`
|
||||||
(default 1, cap 5) drains up to N rows. `recv` prefixes each returned row
|
(default 1, cap 5) drains up to N rows. `recv` prefixes each returned row
|
||||||
with `[msg #<id>]` (broker row id; note the highest id seen, then pass
|
with `[msg #<id>]` (broker row id; note the highest id seen, then pass
|
||||||
|
|
|
||||||
|
|
@ -56,16 +56,12 @@ surfaces, not tab panes.
|
||||||
|
|
||||||
## SW4RM tab
|
## SW4RM tab
|
||||||
|
|
||||||
**C0NTAINERS** — live containers rendered as a depth-first
|
**C0NTAINERS** — live containers rendered as a flat,
|
||||||
tree using `ContainerView.parent` (populated by
|
alphabetically-sorted list. The renderer still walks
|
||||||
`hive-c0re/src/agent_config/topology.rs` — don't confuse it with
|
`ContainerView.parent` for a depth-first tree with ASCII glyphs
|
||||||
`hive-c0re/src/dashboard/topology.rs`, which only holds the
|
(`├─`, `└─`, `│ ` continuation columns), but #4472 removed that field,
|
||||||
set-parent endpoints).
|
so every row sits at depth 0 and the renderer paints no glyph. The tree
|
||||||
ASCII tree glyphs (`├─`, `└─`, `│ ` continuation columns) prefix
|
sorts alphabetically;
|
||||||
each container's row, showing the agent parent/child hierarchy.
|
|
||||||
When every container has `parent = null`
|
|
||||||
(flat topology) the tree collapses to a plain list with no
|
|
||||||
glyphs. The tree sorts children alphabetically within each parent;
|
|
||||||
roots likewise. The tree tolerates cycles in the parent graph —
|
roots likewise. The tree tolerates cycles in the parent graph —
|
||||||
it appends orphaned containers (not reachable from any root)
|
it appends orphaned containers (not reachable from any root)
|
||||||
as roots so no agent disappears. Pulsing red banner at the top
|
as roots so no agent disappears. Pulsing red banner at the top
|
||||||
|
|
@ -961,7 +957,7 @@ Three primitives, all built on the `openDialog` core:
|
||||||
summaries are sticky (select to dismiss) so they aren't missed.
|
summaries are sticky (select to dismiss) so they aren't missed.
|
||||||
|
|
||||||
Every destructive run-state action (`ST0P`, `R3ST4RT`, `R3BU1LD`,
|
Every destructive run-state action (`ST0P`, `R3ST4RT`, `R3BU1LD`,
|
||||||
`DESTR0Y`, `PURG3`, `M0V3`) routes through `themedConfirm`, on both
|
`DESTR0Y`, `PURG3`) routes through `themedConfirm`, on both
|
||||||
the per-agent `⋮` menu and the bulk selection bar.
|
the per-agent `⋮` menu and the bulk selection bar.
|
||||||
|
|
||||||
**Graceful stop** — the `■ ST0P` confirm dialog (per-agent and
|
**Graceful stop** — the `■ ST0P` confirm dialog (per-agent and
|
||||||
|
|
@ -985,10 +981,12 @@ identically.)
|
||||||
|
|
||||||
### Topology tree
|
### Topology tree
|
||||||
|
|
||||||
See **SW4RM tab** above for the parent/child derivation, sibling
|
⚠️ **Dormant since #4472,** which removed `ContainerView.parent`:
|
||||||
sort order, and cycle-safety rules (`swarm.js::buildAgentTree` walks
|
`buildAgentTree` now puts every container at depth 0 and the rules below are
|
||||||
`ContainerView.parent`) — this section covers only how the tree is
|
all no-ops — the list renders flat. The walk and the prefix painting are
|
||||||
*drawn*.
|
still in `swarm.js`; retiring them falls to the frontend owner rather
|
||||||
|
than to this removal. What follows describes what the code
|
||||||
|
still does, for whoever makes that call.
|
||||||
|
|
||||||
The per-row prefix column (`.tree-prefix`) is **DOM-painted, not
|
The per-row prefix column (`.tree-prefix`) is **DOM-painted, not
|
||||||
text-glyph-painted**: each indent lane is its own positioned `<span>`
|
text-glyph-painted**: each indent lane is its own positioned `<span>`
|
||||||
|
|
@ -1002,8 +1000,8 @@ ancestor's still-open subtree, and the joint at a row's own depth is
|
||||||
the row's icon midline). Exact lane widths and positioning live in
|
the row's icon midline). Exact lane widths and positioning live in
|
||||||
`swarm.js`'s tree-prefix rendering and its paired CSS rules — not
|
`swarm.js`'s tree-prefix rendering and its paired CSS rules — not
|
||||||
reproduced here since they're tuned in pixel units and will drift.
|
reproduced here since they're tuned in pixel units and will drift.
|
||||||
When every container is at depth 0 (no `parent` set) these rules are
|
With every container at depth 0 these rules are all no-ops and the
|
||||||
all no-ops and the layout reads like a plain flat list.
|
layout reads like a plain flat list, which is what a hive renders today.
|
||||||
|
|
||||||
## Selection bar
|
## Selection bar
|
||||||
|
|
||||||
|
|
@ -1032,25 +1030,10 @@ frosted-mauve bar slides up from the bottom of the viewport
|
||||||
- `▶ R3SUM3` — paused agents only
|
- `▶ R3SUM3` — paused agents only
|
||||||
- `↻ R3BU1LD` — always available
|
- `↻ R3BU1LD` — always available
|
||||||
- `DESTR0Y` / `PURG3` — always available
|
- `DESTR0Y` / `PURG3` — always available
|
||||||
- `⇡ M0V3 → ROOT` — promote selected agents to top-level
|
|
||||||
(parent = null); disabled when all selected are already at root.
|
|
||||||
hive-c0re's `topology::set_parent` refuses moves it can't satisfy
|
|
||||||
(for example 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
|
|
||||||
(client-side BFS cycle prevention across the whole batch; the
|
|
||||||
hive-c0re re-checks per-agent). On submit:
|
|
||||||
- **single agent** → `POST /api/topology/set-parent`
|
|
||||||
(form-encoded `child=<name>&new_parent=<target>`)
|
|
||||||
- **multiple agents** → `POST /api/topology/set-parent-bulk`
|
|
||||||
(JSON `[{ child, new_parent }]`; all moves land in a single
|
|
||||||
`topology.json` commit instead of one per agent)
|
|
||||||
<!-- vale write-good.Passive = YES -->
|
<!-- vale write-good.Passive = YES -->
|
||||||
|
|
||||||
Both write `topology.json` and re-emit a container snapshot so
|
#4472 removed the `M0V3` picker that used to sit here — an agent has
|
||||||
the tree repaints without a page reload.
|
no parent to move it to any more.
|
||||||
- **`✕ clear`** button + `Esc` key clear the entire selection.
|
- **`✕ clear`** button + `Esc` key clear the entire selection.
|
||||||
|
|
||||||
Every render prunes stale selections (agents destroyed while
|
Every render prunes stale selections (agents destroyed while
|
||||||
|
|
|
||||||
|
|
@ -952,7 +952,6 @@ export function renderSelectionBar(containers) {
|
||||||
confirm: (names) =>
|
confirm: (names) =>
|
||||||
`PURGE ${names.length} agent${names.length === 1 ? "" : "s"} (${names.join(", ")})? containers, config history, claude creds, and notes are all WIPED. no undo.`,
|
`PURGE ${names.length} agent${names.length === 1 ? "" : "s"} (${names.join(", ")})? containers, config history, claude creds, and notes are all WIPED. no undo.`,
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBulkButton(parent, btnClass, label, enabled, selected, opts) {
|
function addBulkButton(parent, btnClass, label, enabled, selected, opts) {
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn all_agents_in_returns_every_name_sorted() {
|
fn all_agents_in_returns_every_name_sorted() {
|
||||||
assert_eq!(all_agents_in(&roster_three()), vec!["alice", "bob", "carol"]);
|
assert_eq!(
|
||||||
|
all_agents_in(&roster_three()),
|
||||||
|
vec!["alice", "bob", "carol"]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -151,9 +151,9 @@ in
|
||||||
Place the file inside the agent's bind-mounted **harness** dir (e.g.
|
Place the file inside the agent's bind-mounted **harness** dir (e.g.
|
||||||
`/agents/<name>/harness/openrouter.env`, `$HYPERHIVE_HARNESS_DIR`),
|
`/agents/<name>/harness/openrouter.env`, `$HYPERHIVE_HARNESS_DIR`),
|
||||||
not `state/` — `harness/` survives container rebuilds exactly like
|
not `state/` — `harness/` survives container rebuilds exactly like
|
||||||
`state/` does, but is never bind-mounted into a parent agent's
|
`state/` does, but is never bind-mounted into another agent's
|
||||||
container (unlike `state/`, which a parent gets read-write for child
|
container (unlike `state/`, which a `ManageRootAgent` holder gets
|
||||||
recovery — see `docs/agent-lifecycle/persistence.md`'s "Parent access to child
|
read-write for recovery — see `docs/agent-lifecycle/persistence.md`'s "Cross-agent access to
|
||||||
state"), so this credential is reachable by nothing but this agent
|
state"), so this credential is reachable by nothing but this agent
|
||||||
and the host. Permissions should be `0600`, owned by the agent's
|
and the host. Permissions should be `0600`, owned by the agent's
|
||||||
unix user. Loaded with a leading `-` (optional `EnvironmentFile`),
|
unix user. Loaded with a leading `-` (optional `EnvironmentFile`),
|
||||||
|
|
|
||||||
|
|
@ -183,20 +183,21 @@ in
|
||||||
mkdir -p "$(dirname "$marker")"
|
mkdir -p "$(dirname "$marker")"
|
||||||
: > "$marker"
|
: > "$marker"
|
||||||
# Scope state + harness chowns to THIS container's own dirs only.
|
# Scope state + harness chowns to THIS container's own dirs only.
|
||||||
# The glob `/agents/*/state` also matches child-agent state dirs that
|
# The glob `/agents/*/state` also matches other agents' state dirs
|
||||||
# are bind-mounted into parent containers, which would clobber the
|
# bind-mounted into a `ManageRootAgent` holder's container, which
|
||||||
# ownership those dirs' own activation scripts set — producing
|
# would clobber the ownership those dirs' own activation scripts set
|
||||||
# intermittent EACCES for the child agent's harness between a parent
|
# — producing intermittent EACCES for that agent's harness between
|
||||||
# rebuild and the child's next activation. Config dirs are kept broad
|
# the holder's rebuild and its own next activation. Config dirs are
|
||||||
# because the parent legitimately owns child proposed-config repos.
|
# kept broad because the holder legitimately owns the proposed-config
|
||||||
|
# repos it edits.
|
||||||
if [ -d "/agents/$userName/state" ]; then
|
if [ -d "/agents/$userName/state" ]; then
|
||||||
chown -hR "$userName:$userName" "/agents/$userName/state" 2>/dev/null || true
|
chown -hR "$userName:$userName" "/agents/$userName/state" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
if [ -d "/agents/$userName/harness" ]; then
|
if [ -d "/agents/$userName/harness" ]; then
|
||||||
chown -hR "$userName:$userName" "/agents/$userName/harness" 2>/dev/null || true
|
chown -hR "$userName:$userName" "/agents/$userName/harness" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
# The proposed-config repo is RW-mounted into the editing (parent/
|
# The proposed-config repo is RW-mounted into the editing agent and
|
||||||
# manager) agent and owned by it; hive-c0re only pulls from it. Heal
|
# owned by it; hive-c0re only pulls from it. Heal
|
||||||
# it to this user too — same as state/harness. In an agent's own
|
# it to this user too — same as state/harness. In an agent's own
|
||||||
# container its config is RO-mounted, so the chown there just fails
|
# container its config is RO-mounted, so the chown there just fails
|
||||||
# harmlessly (|| true).
|
# harmlessly (|| true).
|
||||||
|
|
|
||||||
|
|
@ -60,11 +60,7 @@
|
||||||
# against hive names, in `swarm-otel.nix`'s own `reservedOwners`.
|
# against hive names, in `swarm-otel.nix`'s own `reservedOwners`.
|
||||||
"swarm"
|
"swarm"
|
||||||
]
|
]
|
||||||
# Deliberately absent, and both are load-bearing omissions:
|
# Deliberately absent, and it is a load-bearing omission:
|
||||||
#
|
|
||||||
# `<parent>` / `<children>` — routing recipients the ident charset already
|
|
||||||
# rejects, so no name can ever equal them; listing them would imply a guard
|
|
||||||
# that never fires.
|
|
||||||
#
|
#
|
||||||
# `ruth` — a real agent, not a literal. A second agent wanting that name is a
|
# `ruth` — a real agent, not a literal. A second agent wanting that name is a
|
||||||
# name that is TAKEN, which the roster answers, not this list.
|
# name that is TAKEN, which the roster answers, not this list.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue