From e746ed1888a610c5319c5167ffe7262bd97279c6 Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 11 Nov 2026 03:05:29 +0100 Subject: [PATCH] docs: the agent roster page keeps the operator view and moves the topology detail out The page says who is on the roster, who may reach whose state and that the manager is a normal agent. How topology.json is read and rebuilt, the wire protocol and the prompt blocks move to 4-implementation/agent-state/roster.md, unchanged, since the code they describe still exists. --- docs/2-handbook/agents/agent-roster.md | 145 ++++-------------- docs/4-implementation/agent-state/roster.md | 126 +++++++++++++++ .../4-implementation/scheduler/coordinator.md | 2 +- docs/README.md | 4 +- 4 files changed, 156 insertions(+), 121 deletions(-) create mode 100644 docs/4-implementation/agent-state/roster.md diff --git a/docs/2-handbook/agents/agent-roster.md b/docs/2-handbook/agents/agent-roster.md index 676756742..200a4e499 100644 --- a/docs/2-handbook/agents/agent-roster.md +++ b/docs/2-handbook/agents/agent-roster.md @@ -1,128 +1,35 @@ -# Agent roster & privileges +# Agent roster and privileges - +Agents are a **flat set**: no agent manages another by position. The +`manage_root_agent` permission is the one grant that reaches another agent. -Agents are a **flat set**: the `manage_root_agent` permission scopes -which agents can manage which others. +## Who is on the roster - +The swarm decides. An agent exists in the swarm if and only if it has an identity +in the swarm's authelia identity store, which `swarmctl agent create` or the +swarm UI writes once. `GET /api/agents` on the swarm controller lists them. -This doc covers what the roster file holds, the map-shaped alternative -`topology::all_agents` accepts, and what's hard-coded for the manager. +Each hive also keeps a small file of its own, `topology.json`, with the agents +this hive has state or config for. You never edit it: the hive rebuilds it from +what it sees locally, so a hand edit is overwritten, and one that doesn't parse +stops the rebuild until you fix it. It matters for one thing, covered next. -## Where the roster lives +## Who may reach whose state -"The roster" means two different, non-overlapping things depending on -scope. The [README](../../../README.md)'s control-plane row ("`swarm-controller` -holds the hive directory, the agent roster and the job graph") is the -**swarm-wide** one: `GET /api/agents` on swarm-controller reads -straight from the swarm's authelia identity store over -`swarm-authelia-bridge` (`swarm-controller/src/auth.rs::list_agent_identities`) -— "every agent the swarm holds an identity for" (`swarm-controller/src/routes/agents.rs`'s -`get_agents` doc comment). It's authoritative: an agent exists in the -swarm if and only if it has an identity there, and `swarmctl agent -create` (or the swarm UI, via `POST /api/agents`) writes that identity -once, by calling `ensure_agent_identity`. +An agent holding no permission sees its own directories and nothing else. An +agent holding `manage_root_agent` gets every other agent's `state` directory on +this hive mounted read-write at `/agents//state`, never `config` or +`harness`. It's the only grant that reaches another agent's state. Ruth, the +root agent, holds it, and the swarm UI's setup assistant creates her with it. The +mounts follow the roster: an empty file means no mounts until the next rebuild. -This page is about a different, **hive-local** file: the hive-c0re-owned -`meta/` directory's `topology.json`, at -`/var/lib/hyperhive/meta/topology.json`: +## The manager is a normal agent -```json -["alice", "bob", "ruth"] -``` +`ruth` differs from other agents in her name and in that grant. You create her +like any other agent, from the swarm UI or `swarmctl agent create`; the hive +creates nothing at boot. Her permissions, prompt and tools work as for every +agent, and destroying her is as final as for any other. -One entry per agent _this hive_ has state/config for, in name order. -Unlike the swarm roster, it's a derived cache: the reconcile pass below -rebuilds it from what the hive observes locally (config repos cloned, containers spawned), and it -answers a narrower question than "does this agent exist": _which of -this hive's local agents the `ManageRootAgent` permission's bind-mounts -should cover._ `topology::all_agents` is the only reader that matters. - -That reader is a permission boundary: the set it returns is what an agent -holding the `ManageRootAgent` permission gets bind-mounted (each other -agent's `state` dir, read-write, at `/agents//state`; never `config` or -`harness`). An agent holding no permission sees its own dirs and nothing else: -`ManageRootAgent` is the only grant that reaches another agent's state -dir. See -[`persistence.md`](../../4-implementation/agent-state/persistence.md)'s _Cross-agent access to state._ - -### Reading the map-shaped format - -`topology.json` may also be a map of `name → parent | null`; the reader -accepts that shape and takes its keys as the roster. An empty -roster costs more than a cosmetic gap: every permission holder loses its -mounts until the next reconcile pass writes the array form. - -### Why meta, not per-agent `agent.nix` - -An agent shouldn't be able to add itself to a set that governs who may -reach its state dir. The roster IS a system-level fact; meta is where -system-level facts live. - -### How `topology.json` gets updated - -- **Read** — parsed into a set of names; a missing or unparsable file - degrades safely to "no agents" (covers a fresh install that hasn't - synced yet). -- **Reconcile** — runs alongside the periodic meta/flake regeneration. - Adds newly spawned agents, drops removed ones. The live set is the - hive's existing agent containers plus the agent that spawns now; reconcile - drops an agent whose config repo exists but whose container doesn't. - -No write API and no operator verb reach this file. Reconcile derives it -from which agents exist, so the next pass overwrites a hand edit that -parses. It refuses to write over a file it can't parse and fails -instead, so a broken hand edit stays until someone fixes it. - -See `hive-c0re/src/agent_config/topology.rs` and `hive-c0re/src/meta.rs`'s -module docs for the exact call chain. - -## Hard-coded manager behaviour - -The **manager (`ruth`)** differs from other agents in naming/bootstrap -and its socket flavour; its permissions, prompt, tool allow-list and -state dirs work as for every other agent. - - - -- **Naming/bootstrap** — the manager's broker recipient name, state-dir - key, and nixos-container name are all `ruth` (container `h-ruth`). - Like every agent it comes from the swarm dashboard or - `swarmctl agent create`; `hive-c0re` creates nothing at boot. - Roster-wise, `ruth` is just another entry. -- **Wire-protocol** — `hive-core-agent-sock`'s unified `Request` enum - is the same on both socket flavours. Container lifecycle - ops (kill/start/restart/rebuild) go through the separate host-admin - socket `hivectl` speaks. `Wake` (inject a `from: ` message straight - into the caller's own inbox) is on this socket too, unprivileged on - both flavours; matrix, bash and forge don't call it — their - notifications push a todo on the harness's in-agent socket (see - [`docs/4-implementation/turn-loop/mcp.md`](../../4-implementation/turn-loop/mcp.md#waking-the-agent-from-inside-the-container)). -- **Storage/mounts** — the `manage_root_agent` permission, which the swarm - UI's setup assistant creates ruth with, gets a container - each other agent's `state` dir bind-mounted RW at - `/agents//state` (so it can manage any agent's state dir; config - isn't mounted, since a real config change is a PR from a clone), plus an RO mount for - `/meta` (the hive's topology and limits files). Any agent holding it gets the same mounts. -- **Prompt** — identical for every container but one paragraph: an agent - built from an [archetype](../swarm/archetypes.md) is told it has no config - repo, every other agent is told how to propose changes to its own. -- **Tool allow-list** — the MCP tools claude sees come from the agent's - permissions alone, the same mechanism for every agent - ([`docs/4-implementation/turn-loop/mcp.md`](../../4-implementation/turn-loop/mcp.md)). Ruth's permissions - are the default plus `manage_root_agent`, stored for her like any grant. -- **State dirs** — _not_ treated as an exception: `HYPERHIVE_STATE_DIR` is -injected uniformly via `systemd.globalEnvironment` for every -container including the manager, so all token/state paths resolve -through it the same way everywhere. - - -None of the above is a stable interface — treat the module doc -comments as the source of truth for exactly which checks exist today. - -## Cross-references - -- Milestone: "Agent privileges and sub-agents" (tracked internally) -- Audit table source: milestone comment (tracked internally) -- Operator/agent trust boundary (orthogonal axis): [`boundary.md`](../hives/boundary.md) +How the hive reads and rebuilds the file, the wire protocol and the prompt blocks: +[`roster.md`](../../4-implementation/agent-state/roster.md). The operator/agent +trust boundary is a separate axis: [`boundary.md`](../hives/boundary.md). diff --git a/docs/4-implementation/agent-state/roster.md b/docs/4-implementation/agent-state/roster.md new file mode 100644 index 000000000..ba7d05619 --- /dev/null +++ b/docs/4-implementation/agent-state/roster.md @@ -0,0 +1,126 @@ +# Agent roster: topology file and manager behaviour + +The hive-local `topology.json`, how it is derived, and what is special about the +manager (`ruth`). The operator's overview is +[the agent roster page](../../2-handbook/agents/agent-roster.md). + + + +Agents are a **flat set**: the `manage_root_agent` permission scopes +which agents can manage which others. + + + +This doc covers what the roster file holds, the map-shaped alternative +`topology::all_agents` accepts, and what's hard-coded for the manager. + +## Where the roster lives + +"The roster" means two different, non-overlapping things depending on +scope. The [README](../../../README.md)'s control-plane row ("`swarm-controller` +holds the hive directory, the agent roster and the job graph") is the +**swarm-wide** one: `GET /api/agents` on swarm-controller reads +straight from the swarm's authelia identity store over +`swarm-authelia-bridge` (`swarm-controller/src/auth.rs::list_agent_identities`) +— "every agent the swarm holds an identity for" (`swarm-controller/src/routes/agents.rs`'s +`get_agents` doc comment). It's authoritative: an agent exists in the +swarm if and only if it has an identity there, and `swarmctl agent +create` (or the swarm UI, via `POST /api/agents`) writes that identity +once, by calling `ensure_agent_identity`. + +This page is about a different, **hive-local** file: the hive-c0re-owned +`meta/` directory's `topology.json`, at +`/var/lib/hyperhive/meta/topology.json`: + +```json +["alice", "bob", "ruth"] +``` + +One entry per agent _this hive_ has state/config for, in name order. +Unlike the swarm roster, it's a derived cache: the reconcile pass below +rebuilds it from what the hive observes locally (config repos cloned, containers spawned), and it +answers a narrower question than "does this agent exist": _which of +this hive's local agents the `ManageRootAgent` permission's bind-mounts +should cover._ `topology::all_agents` is the only reader that matters. + +That reader is a permission boundary: the set it returns is what an agent +holding the `ManageRootAgent` permission gets bind-mounted (each other +agent's `state` dir, read-write, at `/agents//state`; never `config` or +`harness`). An agent holding no permission sees its own dirs and nothing else: +`ManageRootAgent` is the only grant that reaches another agent's state +dir. See +[`persistence.md`](../agent-state/persistence.md)'s _Cross-agent access to state._ + +### Reading the map-shaped format + +`topology.json` may also be a map of `name → parent | null`; the reader +accepts that shape and takes its keys as the roster. An empty +roster costs more than a cosmetic gap: every permission holder loses its +mounts until the next reconcile pass writes the array form. + +### Why meta, not per-agent `agent.nix` + +An agent shouldn't be able to add itself to a set that governs who may +reach its state dir. The roster IS a system-level fact; meta is where +system-level facts live. + +### How `topology.json` gets updated + +- **Read** — parsed into a set of names; a missing or unparsable file + degrades safely to "no agents" (covers a fresh install that hasn't + synced yet). +- **Reconcile** — runs alongside the periodic meta/flake regeneration. + Adds newly spawned agents, drops removed ones. The live set is the + hive's existing agent containers plus the agent that spawns now; reconcile + drops an agent whose config repo exists but whose container doesn't. + +No write API and no operator verb reach this file. Reconcile derives it +from which agents exist, so the next pass overwrites a hand edit that +parses. It refuses to write over a file it can't parse and fails +instead, so a broken hand edit stays until someone fixes it. + +See `hive-c0re/src/agent_config/topology.rs` and `hive-c0re/src/meta.rs`'s +module docs for the exact call chain. + +## Hard-coded manager behaviour + +The **manager (`ruth`)** differs from other agents in naming/bootstrap +and its socket flavour; its permissions, prompt, tool allow-list and +state dirs work as for every other agent. + + + +- **Naming/bootstrap** — the manager's broker recipient name, state-dir + key, and nixos-container name are all `ruth` (container `h-ruth`). + Like every agent it comes from the swarm dashboard or + `swarmctl agent create`; `hive-c0re` creates nothing at boot. + Roster-wise, `ruth` is just another entry. +- **Wire-protocol** — `hive-core-agent-sock`'s unified `Request` enum + is the same on both socket flavours. Container lifecycle + ops (kill/start/restart/rebuild) go through the separate host-admin + socket `hivectl` speaks. `Wake` (inject a `from: ` message straight + into the caller's own inbox) is on this socket too, unprivileged on + both flavours; matrix, bash and forge don't call it — their + notifications push a todo on the harness's in-agent socket (see + [`docs/4-implementation/turn-loop/mcp.md`](../turn-loop/mcp.md#waking-the-agent-from-inside-the-container)). +- **Storage/mounts** — the `manage_root_agent` permission, which the swarm + UI's setup assistant creates ruth with, gets a container + each other agent's `state` dir bind-mounted RW at + `/agents//state` (so it can manage any agent's state dir; config + isn't mounted, since a real config change is a PR from a clone), plus an RO mount for + `/meta` (the hive's topology and limits files). Any agent holding it gets the same mounts. +- **Prompt** — identical for every container but one paragraph: an agent + built from an [archetype](../../2-handbook/swarm/archetypes.md) is told it has no config + repo, every other agent is told how to propose changes to its own. +- **Tool allow-list** — the MCP tools claude sees come from the agent's + permissions alone, the same mechanism for every agent + ([`docs/4-implementation/turn-loop/mcp.md`](../turn-loop/mcp.md)). Ruth's permissions + are the default plus `manage_root_agent`, stored for her like any grant. +- **State dirs** — _not_ treated as an exception: `HYPERHIVE_STATE_DIR` is +injected uniformly via `systemd.globalEnvironment` for every +container including the manager, so all token/state paths resolve +through it the same way everywhere. + + +None of the above is a stable interface — treat the module doc +comments as the source of truth for exactly which checks exist today. diff --git a/docs/4-implementation/scheduler/coordinator.md b/docs/4-implementation/scheduler/coordinator.md index d44363edd..423c39d48 100644 --- a/docs/4-implementation/scheduler/coordinator.md +++ b/docs/4-implementation/scheduler/coordinator.md @@ -555,4 +555,4 @@ config — all nspawn containers live in `machine.slice`. - `docs/4-implementation/agent-state/config-changes.md` — config PRs, meta flake, system messages - `docs/4-implementation/agent-state/persistence.md` — SQLite schema, state-dir layout - `docs/5-project/process/conventions.md` — wire protocol, recipient sentinels -- `docs/2-handbook/agents/agent-roster.md` — the agent roster and privilege +- `docs/4-implementation/agent-state/roster.md` — the agent roster and privilege diff --git a/docs/README.md b/docs/README.md index 4e00978d5..cbaf1e8d9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -98,7 +98,9 @@ One agent: its roster entry, state, config and page. Start at tables and directory layout: [`4-implementation/agent-state/persistence.md`](4-implementation/agent-state/persistence.md). - **Who can do what to whom — the agent roster and privilege?** → - [`2-handbook/agents/agent-roster.md`](2-handbook/agents/agent-roster.md). + [`2-handbook/agents/agent-roster.md`](2-handbook/agents/agent-roster.md); how + `topology.json` is rebuilt and what is special about the manager: + [`4-implementation/agent-state/roster.md`](4-implementation/agent-state/roster.md). - **What does an agent's own page show?** → [`2-handbook/agents/web-page.md`](2-handbook/agents/web-page.md); the full reference is [`agent-page`](4-implementation/web-ui/agent-page.md).