Watch
0
0
Fork
You've already forked hyperhive
0

agent permissions: one set, tool groups included, set at agent creation

mara on #5170: "dont split caps and perms. its all perms. / dont include
the now empty tool groups / nothing set -> default / also add perms to
agent creation screen".

- hive-sh4re: one `Permission` enum (messaging, meta, inbox, execution,
  web_tools, manage_root_agent) replaces `ToolGroup` and `Capability`;
  `lifecycle`/`forge` are gone. `Permission::default_for` is the default
  for an agent the swarm sets nothing for.
- The record is `{ "permissions": [...] }`. Missing key, unset field or
  no successful read yet = default; `[]` = none; a failed read after a
  good one keeps the last value.
- hive-agent reads its own key every 10s and builds each turn's --tools,
  --allowedTools and MCP config (rewritten before every turn) from it;
  the subagent daemon reads it on each start/continue. Agents get a
  read-only grant on their own key.
- hive-c0re restarts an agent only when its manage_root_agent changes.
  The import now merges meta/capabilities.json and meta/tool-groups.json
  into one list per agent and deletes both; the "import pending, don't
  read" decision is `ImportOutcome::read_allowed`, table-tested.
- swarm-controller: `GET /api/permissions` (names + default) and an
  optional `permissions` on `POST /api/agents`, stored before the job is
  queued. swarm-ui's create-agent form gets a permissions field.
- Deleted: HIVE_TOOL_GROUPS and its forward to the subagent daemon,
  tool_groups.rs, ruth's tool-group seed, the permissions rebuild job
  (PermPayload/WritePermFile/perm_change), the dashboard permissions tab
  and its stale-entries panel.

Part of #5170.
This commit is contained in:
atlas 2026-10-15 04:09:53 +02:00 • committed by mara
commit f0844f6da3
73 changed files with 1578 additions and 2742 deletions

View file

@ -2,8 +2,8 @@
<!-- vale write-good.Passive = NO -->
Agents are a **flat set**: the capability store scopes which agents can
manage which others.
Agents are a **flat set**: the `manage_root_agent` permission scopes
which agents can manage which others.
<!-- vale write-good.Passive = YES -->
@ -79,7 +79,7 @@ module docs for the exact call chain.
## Hard-coded manager behaviour
The **manager (`ruth`)** differs from other agents in naming/bootstrap,
its socket flavour and its default capability grant; its prompt, tool
its socket flavour and its default permissions; its prompt, tool
allow-list and state dirs work as for every other agent. A hive with
`services.hyperhive.ruthless = true` runs no manager: `hive-c0re` skips
its root-agent create/start sweep
@ -107,7 +107,7 @@ apply.
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
`/applied` (diff against what's deployed) and `/meta` (system-wide
deploy log). That grant is the `ManageRootAgent` capability: ruth
deploy log). That grant is the `manage_root_agent` permission: ruth
holds it by default, and any agent holding it gets the same mounts.
The operator dashboard's `POST /api/meta-update` is the only path
that writes `flake.lock`.
@ -118,11 +118,10 @@ apply.
`hive-agent/src/prompt.rs`'s own module doc). The `role:manager`
blocks are dead in production, exercised only by a unit test
(`filter_role_blocks(SAMPLE, "manager")`).
- **Tool allow-list** — the MCP tools claude sees come from
`HIVE_TOOL_GROUPS` alone, the same mechanism for every agent
([`docs/turn-loop/mcp.md`](../turn-loop/mcp.md)). Ruth's wider default
surface is a wider default grant (`ToolGroup::MANAGER_DEFAULT`, seeded
by `auto_update.rs` whenever its groups aren't already set).
- **Tool allow-list** — the MCP tools claude sees come from the agent's
permissions alone, the same mechanism for every agent
([`docs/turn-loop/mcp.md`](../turn-loop/mcp.md)). Ruth's default
permissions add only `manage_root_agent` (`Permission::default_for`).
- **State dirs** — _not_ special-cased: `HYPERHIVE_STATE_DIR` is
injected uniformly via `systemd.globalEnvironment` for every
container including the manager, so all token/state paths resolve

View file

@ -244,7 +244,7 @@ bind-mounted **read-only** (`hive-c0re/src/lifecycle/host_config.rs`
calls `bind_child_agent_dirs` for each entry in
`topology::all_agents()`). it's a copy to *read* another agent's
current config — not an editing surface. An agent without the
capability sees no other agent's config at all.
permission sees no other agent's config at all.
An agent submits a change to another agent's config the same way it
makes any other change: **clone the target agent's config repo from the
@ -252,29 +252,29 @@ forge into its own state dir, commit on a branch, open a PR**, and let
the operator review and merge it. By design, no second, mount-shaped
path reaches the same file without the review.
Agents holding the `manage_root_agent` capability (granted per agent in
Agents holding the `manage_root_agent` permission (granted per agent in
the swarm's `agent-permissions` record; see
`docs/process/conventions.md#capabilities`) get additional host-side bind
`docs/process/conventions.md#permissions`) get additional host-side bind
mounts via `set_nspawn_flags`:
- `/var/lib/hyperhive/agents/` → `/agents/` (RW) — **every** agent's
proposed repo, not just direct children. The capability means "may
proposed repo, not just direct children. The permission means "may
manage any agent", so the mounted set is every agent.
- `/var/lib/hyperhive/applied/` → `/applied/` (RO) — every agent's
authoritative applied repo, including `.git`.
- `/var/lib/hyperhive/meta/` → `/meta/` (RO) — the swarm-wide
deploy flake.
An agent without the capability only has its direct children's config
An agent without the permission only has its direct children's config
dirs.
⚠️ nspawn bakes bind flags at container start, so hive-c0re restarts
an agent, after its current turn, when its capabilities change.
an agent, after its current turn, when its `manage_root_agent` changes.
The root agent holds the capability by default: an agent whose record
leaves `capabilities` unset gets the hive's default, which is
`manage_root_agent` for `ruth` and nothing for every other agent.
Setting `"capabilities": []` for her revokes it.
The root agent holds the permission by default: an agent whose record
leaves `permissions` unset gets the default, which includes
`manage_root_agent` for `ruth` and for no other agent. Setting her
permissions without it revokes it.
Each proposed repo (`/agents/<n>/config/`) is pre-configured
with `applied` as a git remote pointing at
@ -349,8 +349,8 @@ updates the root agent itself.
The system prompt (`hive-agent/prompts/system.md`, rendered by
`hive-agent/src/prompt.rs`) is the **same for every agent**; what
varies is which MCP tools it surfaces (gated by tool groups and
capabilities in `agent.nix`). No `role:manager` block renders only
varies is which MCP tools it surfaces (gated by the agent's permissions,
which the swarm sets). No `role:manager` block renders only
for the root agent. The root agent's role-specific
behaviour comes from its CLAUDE.md / agent-specific instructions, not
the system prompt template.

View file

@ -329,14 +329,14 @@ Under `/var/lib/hyperhive/agents/<name>/`:
### Cross-agent access to state
An agent holding the `ManageRootAgent` capability gets every other
An agent holding the `manage_root_agent` permission gets every other
agent's `state` dir bind-mounted **read-write** and its `config` dir
**read-only** (`bind_child_agent_dirs` in `lifecycle/host_config.rs`).
The RW on `state` is deliberate, not an oversight: the holder recovers
other agents, which includes writing into their state (for example
seeding notes, clearing a stuck sentinel) as well as reading it.
This is the **only** cross-agent mount. An agent holding no capability
This is the **only** cross-agent mount. An agent without that permission
sees its own dirs and nothing else.
**`harness` isn't mounted at all.** It holds that agent's own runtime
@ -364,7 +364,7 @@ mount should be writable when it shouldn't.
<!-- vale write-good.Passive = YES -->
Isolation still holds by default: a container has its *own* dirs and,
unless it holds the capability, nothing else.
unless it holds the permission, nothing else.
Under `/var/lib/hyperhive/applied/<name>/` — the hive-c0re-only
applied repo. Tracks `flake.nix` (module-only boilerplate; never
@ -384,13 +384,9 @@ Contents:
audit trail (one commit per successful deploy or hyperhive bump).
- `topology.json` — the agent roster (`["alice", "bob", "ruth"]`).
Written by `topology::reconcile` on every meta sync; read by
`topology::all_agents`, which is the set the `ManageRootAgent`
capability grants mounts over. The reader also accepts a map-shaped
`topology::all_agents`, which is the set the `manage_root_agent`
permission grants mounts over. The reader also accepts a map-shaped
file carrying a `parent` per agent, and keeps its keys.
- `tool-groups.json` — per-agent MCP tool group grants
(`{ "alice": ["messaging", "inbox", "execution"] }`). Written by
`tool_groups::set_groups`; injected as `HIVE_TOOL_GROUPS` env
var into each agent's container.
- `swarm-resource-limits.json` — the caps the swarm sets for this
hive's agents
(`{ "sock": { "cpu_quota": "400%", "memory_max": "8G" } }`).
@ -415,11 +411,11 @@ Contents:
The root agent has the meta dir RO-mounted at `/meta/`.
Outside the meta repo, `/var/lib/hyperhive/swarm-agent-permissions.json`
holds the capabilities the swarm grants this hive's agents, as
holds the permissions the swarm grants this hive's agents, as
`workers::swarm_permissions` last read them from the swarm's
`agent-permissions` bucket (every 30 seconds): a copy, not the
record, which `swarmctl agent permissions` sets. `set_nspawn_flags`
reads it at every container start.
reads it at every container start for `manage_root_agent`.
hive-c0re renders containers onto `meta#<n>` at creation.

View file

@ -176,7 +176,7 @@ it directly as a bulleted list, no follow-up fetch needed.
`GetLooseEnds` takes no target — it always returns the caller's own
rows, on both the agent and manager sockets alike (the manager, `ruth`,
is a normal agent here, just deployed automatically with different
default capabilities). `Reminder` rows always carry `owner == self`.
default permissions). `Reminder` rows always carry `owner == self`.
Per-variant fields:
@ -272,99 +272,76 @@ JetStream permission refusal, a TLS chain failure) rather than a summary.
Not in scope: the `hivectl` host-admin and in-agent unix sockets. Those are a
JSON-line protocol with their own result types; RFC 9457 is an HTTP format.
## Tool groups
## Permissions
A set of named `ToolGroup` values (`hive_sh4re::permissions::ToolGroup`)
determines the MCP tool surface an agent receives, not a hardcoded
binary flavor.
One set of named `Permission` values (`hive_sh4re::permissions::Permission`)
decides what an agent may do: which MCP and built-in tools it gets, and which
host access it has.
| Group | Tools |
| Permission | Grants |
|---|---|
| `messaging` | `send`, `recv`, `ack_until` |
| `meta` | `get_agent_meta` (`set_status` is always-on, see below) |
| `inbox` | `get_loose_ends`, `cancel_loose_end`, `remind` |
| `execution` | vestigial — `mcp__bash__run` / `mcp__bash__status` are always available unconditionally via `extraMcpServers`; this group's entries expand to non-existent `mcp__hyperhive__run` / `mcp__hyperhive__status` and have no effect. See `docs/tools/bash.md`. |
| `lifecycle` | none |
| `forge` | none |
| `web_tools` | none (gates the Claude built-ins `WebFetch`/`WebSearch`, not an MCP tool) |
| `execution` | the `bash` MCP server (`mcp__bash__run`/`status`/`kill`), rendered into the agent's MCP config only with it; a subagent gets Claude's own `Bash` instead. See `docs/tools/bash.md`. |
| `web_tools` | the Claude built-ins `WebFetch`/`WebSearch` |
| `manage_root_agent` | every agent's state (rw) + config (ro) bind-mounted into the holder's container, plus `/applied` and `/meta` (ro) |
**Always-on tools** — `ToolGroup::ALWAYS_ON_TOOLS` exposes `set_status`,
`compact`, and `mark_todos_done` to every agent regardless of which
groups it holds. The operator dashboard depends on every agent
being able to report its status chip, and the server-side `SetStatus` handler
has no tool-group check (only length validation), so gating it would only
desync the `--allowedTools` list from what the host actually accepts.
Revoking `meta` therefore drops `get_agent_meta` but never `set_status`.
`mark_todos_done` is here because producers push todos to an agent independent of
whether it holds `inbox` — an agent without that group still needs a way to
clear them.
**Always-on tools** — `Permission::ALWAYS_ON_TOOLS` exposes `set_status`,
`compact`, and `mark_todos_done` to every agent regardless of its
permissions. The operator dashboard depends on every agent being able to
report its status chip, and the server-side `SetStatus` handler has no
permission check (only length validation), so gating it would only desync
the `--allowedTools` list from what the host actually accepts. Revoking
`meta` therefore drops `get_agent_meta` but never `set_status`.
`mark_todos_done` is here because producers push todos to an agent whether
or not it holds `inbox`, and an agent without it still needs to clear them.
**Config storage** — per-agent tool groups live in
`/var/lib/hyperhive/meta/tool-groups.json` (hive-c0re-owned, committed to the
meta repo alongside `topology.json`). Format: `{ "alice": ["messaging", "meta",
"inbox", "lifecycle"], "bob": ["messaging", "meta", "inbox"] }`. An absent entry
means "use role default." Tool permissions are intentionally NOT configurable
from `agent.nix` — that file changes through config PRs the manager opens, so
letting it declare its own groups would let the manager grant itself any tool by
submitting a config commit, bypassing the operator gate.
**Setting groups** — the operator sets groups via the dashboard or
`hive-c0re::tool_groups::set_groups(name, groups)`. After a change
`meta::sync_agents` commits the updated file; the next agent rebuild picks up
the new `HIVE_TOOL_GROUPS` env var. Agents with no entry get no var.
**Runtime resolution** — at session start the harness reads `HIVE_TOOL_GROUPS`
(a comma-separated list of snake_case group names injected by the meta renderer
from `tool-groups.json`), logging and skipping unrecognised tokens. Falls back
to `ToolGroup::AGENT_DEFAULT` (`messaging`, `meta`, `inbox`, `execution`) when
the var is absent or empty.
**Updating the surface** — when you add a new `#[tool]` fn to `AgentServer`
in `hive-agent-mcp/src/mcp/mod.rs`, add its name to the matching `ToolGroup::tools()`
slice in `hive-sh4re/src/permissions.rs`. That's the single source of truth;
`mcp_config::allowed_mcp_tools` (in `hive-agent/src/mcp_config.rs`) reads it at
session start.
## Capabilities
Capabilities gate system-level access that goes beyond the MCP tool surface —
things an agent can *access*, not just *call*. Parallel to tool groups but
orthogonal: an agent can have a tool group that registers a tool AND a capability
that allows the underlying resource access.
| Capability | Effect |
|---|---|
| `manage_root_agent` | may manage *any* agent: bind-mounts every agent's state (rw) + config (ro) into the holder's container, plus `/applied` and `/meta` (ro). Takes effect when the holder's container restarts, which hive-c0re does itself on a change |
**Where they live** — in the swarm, not on the hive: the `capabilities` field
of the agent's key in the `agent-permissions` KV bucket
**Where they live** — in the swarm, not on the hive: the `permissions` field of
the agent's key in the `agent-permissions` KV bucket
(`swarm-queue-client/src/agent_permissions.rs`), which swarm-controller alone
writes. Set them with `swarmctl agent permissions get|set|clear <agent>`
(`/api/agents/{name}/permissions`). Unset means the hive's default, which is a
constant in hive-c0re (`capabilities::default_for`): none, except `ruth`, who
gets `manage_root_agent`. `"capabilities": []` grants none.
(`/api/agents/{name}/permissions`), or on the swarm UI's create-agent screen
(`permissions` in `POST /api/agents`). A missing key, an unset field, or no
successful read yet means the default (`Permission::default_for`):
`messaging`, `meta`, `inbox`, `execution`, plus `manage_root_agent` for
`ruth`. `"permissions": []` grants none.
**How a change applies** — every 30 seconds hive-c0re reads the key of each
agent it runs (`workers/swarm_permissions.rs`) and stores the result in
`/var/lib/hyperhive/swarm-agent-permissions.json`, outside the meta repo. nspawn
fixes the bind mounts at container start, so when an agent's capabilities
change hive-c0re restarts it after its current turn. A failed read keeps the
last stored value; it never falls back to the default.
**How a change applies** —
- The agent's harness reads its own key every 10 seconds and builds every
turn's `--tools`, `--allowedTools` and MCP config from the last value, so a
change reaches the next turn. An ACP agent's permission check reads it on
each request.
- The subagent daemon reads the key on each `start`/`continue`; a running
subagent keeps its tools.
- hive-c0re reads each of its agents' keys every 30 seconds
(`workers/swarm_permissions.rs`) into
`/var/lib/hyperhive/swarm-agent-permissions.json`, outside the meta repo.
nspawn fixes the bind mounts at container start, so when an agent's
`manage_root_agent` changes hive-c0re restarts it after its current turn.
**Moving off `meta/capabilities.json`** — on its first boot with this code a
hive copies the file's grants into the stored value, sends them to
swarm-controller once (written only for agents with no key, and only for
agents that hive's wanted state declares), and deletes the file from the meta
repo when the controller answers (`workers/permissions_import.rs`).
Every reader keeps its last good value on a failed read.
**Capability NOT configurable from `agent.nix`** — same reasoning as tool
groups: an agent that could grant its own capabilities via a config commit would
bypass the operator approval gate.
**Moving off the meta repo** — on its first boot with this code a hive copies
`meta/capabilities.json` and `meta/tool-groups.json` into the stored value,
sends them to swarm-controller once as one list per agent (without
`lifecycle` and `forge`, which gave no tools; the controller writes a key
only where there is none, and only for agents that hive's wanted state
declares), and deletes
both files from the meta repo when the controller answers
(`workers/permissions_import.rs`). Until then it reads no keys.
**Adding a new capability** — add a variant to `Capability` in
`hive-sh4re/src/permissions.rs` and to `Capability::ALL`. Implement the access
check where hive-c0re enforces it (today: `set_nspawn_flags` in
`hive-c0re/src/lifecycle/host_config.rs`).
**Not configurable from `agent.nix`** — that file changes through config PRs
an agent can open, so letting it declare its own permissions would let an
agent grant itself any tool by submitting a config commit, bypassing the
operator gate.
**Adding to the set** — add a variant to `Permission` in
`hive-sh4re/src/permissions.rs` and to `Permission::ALL`, and enforce it where
it applies. A new `#[tool]` fn on `AgentServer` (`hive-agent-mcp/src/mcp/mod.rs`)
goes into the matching `Permission::tools()` slice: that's the single source
of truth `mcp_config::allowed_mcp_tools` (`hive-agent/src/mcp_config.rs`)
reads each turn.
## Async forms

View file

@ -71,9 +71,8 @@ Cheap — no build slot:
| `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, drop the power intent, notify the manager, rescan, re-emit the tombstone. Same split rationale as `RebuildBookkeeping`/`Swap`. The destructive work is `PurgeState`'s |
| `SetWanted` | write the durable power intent (`wanted = Up`/`Offline`) as the head node of a power-op DAG. 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 |
| `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 — 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 |
| `WritePermFile` | commit `tool-groups.json` (single git commit under `META_LOCK`) + emit the P3RM1SS10NS snapshot |
| `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` | matrix user/space sweep (`matrix::ensure_all`): the boot-time instance, plus one every 30 min from a loop in `main.rs`. Holds `Resource::MatrixSweep` (capacity 1), so two passes never overlap; each tick queues its own pass, which waits for the resource if one is already live. Agentless |
| `KnowledgePull` | `/knowledge` pull (`knowledge::pull`): at boot (commits that landed while `hive-c0re` was down), on the swarm knowledge-changed event, and hourly as a fallback. Holds `Resource::KnowledgeTree` (capacity 1), so two pulls never overlap on the working tree; each trigger queues its own pass, which waits for the resource if one is already live. Agentless |
@ -90,7 +89,7 @@ Two further layers protect the meta repo across _windows_ that span multiple
- **The deploy window** (`Resource::MetaWindow`): a global, capacity-1 queue
resource declared by every node kind that mutates the meta repo — `MetaSync`,
`MetaLock`, `WritePermFile` and `Provision`'s agent registration. Two meta
`MetaLock` and `Provision`'s agent registration. Two meta
mutations can therefore never interleave, so no commit lands inside another
node's window. It's a queue resource rather than a runtime mutex
because a subtree root holds a resource across its whole subtree, which
@ -139,7 +138,6 @@ restart(a..): online a: [Signal→Drain→ if graceful] StopForUpdate(a) → Re
offline a: Reconcile(a) (nothing to stop; Reconcile converges to existing wanted)
start(a..): a: SetWanted(a,Up) → Reconcile(a) (down+stale ⇒ SetWanted(a,Up) → «rebuild subgraph»)
spawn(a): [wanted=Up at deploy] Provision(a) → Create(a) → WriteDropin(a) → Reconcile(a)
perm-change(a): WritePermFile(a) → «rebuild subgraph»
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;
plus Reconcile(a) for every drifted agent (all ONE DAG)
@ -215,7 +213,7 @@ resources are free. Resources:
(`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
clobber intent before either reconciles.) **Lease-exempt**: `MetaSync`,
`Prebuild`, `Provision`, `MetaLock`, `WritePermFile` — they
`Prebuild`, `Provision`, `MetaLock` — they
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
reason, are the rebuild subtree's own members (`StopForUpdate`, `Swap`,

View file

@ -7,18 +7,18 @@ domain, covers host-level detail for one hive.
## What it shows
| route | what |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `/` | the start page: the swarm's name over a digital-rain backdrop; the swarm name in the header links here |
| `/hives` | the hive directory, each hive with its last reported status |
| `/agents` | every agent: status, config PR, wanted state, inference settings, resource caps; create agents, link forge, matrix and GitHub accounts |
| `/agents/<name>/terminal` | one agent's live terminal |
| `/agents/<name>/subagents/<subagent>/terminal` | one of that agent's subagents' live terminal |
| `/alerts` | the alerts the swarm's Grafana holds now (firing, pending, no data, error), read through the controller with a Viewer service-account token |
| `/jobs` | the controller's job graph — where agent creation and credential mints show progress |
| `/schedules` | the scheduled prompts: add, edit, pause, resume and remove |
| `/issues` | a cross-repo issue report |
| `/review` | the review classifier: its recent runs, a try-out on a real PR, and its settings |
| route | what |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/` | the start page: the swarm's name over a digital-rain backdrop; the swarm name in the header links here |
| `/hives` | the hive directory, each hive with its last reported status |
| `/agents` | every agent: status, config PR, wanted state, inference settings, resource caps; create agents (with their permissions), link forge, matrix and GitHub accounts |
| `/agents/<name>/terminal` | one agent's live terminal |
| `/agents/<name>/subagents/<subagent>/terminal` | one of that agent's subagents' live terminal |
| `/alerts` | the alerts the swarm's Grafana holds now (firing, pending, no data, error), read through the controller with a Viewer service-account token |
| `/jobs` | the controller's job graph — where agent creation and credential mints show progress |
| `/schedules` | the scheduled prompts: add, edit, pause, resume and remove |
| `/issues` | a cross-repo issue report |
| `/review` | the review classifier: its recent runs, a try-out on a real PR, and its settings |
Everything it shows comes from [`swarm-controller`](../../swarm-controller/README.md).
An agent created here or with `swarmctl agent create` starts `paused`; set it

View file

@ -7,11 +7,12 @@ by default (`lib.mkDefault`, with `allowedTools = ["*"]`) — an `agent.nix`
can override or drop the entry, so this is a default, not an
unconditional grant. Even where the entry is present, the server is only
actually rendered into an agent's claude config when the agent also
holds the **`execution`** tool group: `extra_server_required_group` in
holds the **`execution`** permission: `extra_server_required_permission` in
`hive-agent/src/mcp_config.rs` suppresses it entirely without that
group, so an agent lacking `execution` gets no `mcp__bash__*` tools at
all, not just an unused grant. See "Relationship to the `execution` tool
group" below.
permission, so an agent lacking `execution` gets no `mcp__bash__*` tools at
all, not just an unused grant. The harness rewrites the config each turn,
so a change applies at the next one. See "Relationship to the
`execution` permission" below.
## Tools
@ -122,14 +123,14 @@ unread rooms. An inline `wait_seconds` / `status` observation that already
delivered the result instead clears the keyed todo, so no redundant
loose-end follows.
## Relationship to the `execution` tool group
## Relationship to the `execution` permission
`ToolGroup::Execution` (in `AGENT_DEFAULT`) is the real gate on bash:
`extra_server_required_group` (in `hive-agent/src/mcp_config.rs`)
`Permission::Execution` (in `Permission::DEFAULT`) is the real gate on bash:
`extra_server_required_permission` (in `hive-agent/src/mcp_config.rs`)
controls whether the `bash` MCP server reaches the agent's claude
config at all, suppressing every `mcp__bash__*` tool
when absent — not just leaving them unused. Its own `tools()` returns
`&[]`: `mcp__bash__run`/`status`/`kill` live on the separate `bash` MCP
server that `extra_server_required_group` gates, not on the
server that `extra_server_required_permission` gates, not on the
`mcp__hyperhive__*` allowlist `tools()` builds. Removing `execution`
from an agent's groups **does** remove bash availability.
from an agent's permissions **does** remove bash availability.

View file

@ -399,23 +399,24 @@ particular `--tools` does **not** filter `mcp__*` tools.
**A subagent gets exactly the built-ins its parent agent has, plus one
deliberate exception** — the base resolution
(`hive_sh4re::permissions::subagent_builtin_tools_arg`, wrapping the
harness's own `builtin_tools_for`) from the same `HIVE_TOOL_GROUPS`:
(`hive_sh4re::permissions::subagent_builtin_tools_for`, wrapping the
harness's own `builtin_tools_for`) over the agent's permissions, which the
daemon reads from the swarm on each `start`/`continue`:
`Edit`, `Glob`, `Grep`, `Read`, `Skill`, `Write`, plus `WebFetch`/
`WebSearch` for an agent granted the `web_tools` tool group and not
otherwise. See
`WebSearch` for an agent granted `web_tools` and not otherwise. A running
subagent keeps the tools it started with. See
[the harness's own allowlist](../turn-loop/mcp.md#tool-allowlist-hive_sh4repermissionsallowed_builtin_tools)
for what that list contains and why.
The exception: **a subagent gets Claude's built-in `Bash` iff its parent
holds the `execution` tool group** — the group that already grants the
holds the `execution` permission** — the permission that already grants the
parent shell execution, via the out-of-process `bash` MCP server
(`mcp__bash__run`/`status`/`kill`). A subagent has no MCP server of its
own by default (see [MCP servers](#mcp-servers---strict-mcp-config)
below), so there is no `mcp__bash__*` for it to inherit that capability
through; it gets Claude's own tool instead. The rule is the capability
transfers, not the mechanism — the parent itself never gains built-in
`Bash` no matter which groups it holds, only a subagent spawned by an
`Bash` no matter which permissions it holds, only a subagent spawned by an
`execution`-holding parent does.
Inheriting rather than listing is the point: a hardcoded subagent list

View file

@ -70,7 +70,7 @@ Manage agents across the swarm
* `create` — Queue creation of a new agent on a hive in this swarm
* `mint-forge-token` — Check one agent's forge token, and mint it if it's missing or stale
* `inference` — Show the model, effort, subagent models and subagent cap the swarm sets for an agent, or change the model, effort and subagent models
* `permissions` — Show or change the capabilities the swarm grants an agent
* `permissions` — Show or change the permissions the swarm grants an agent
@ -205,23 +205,23 @@ Clear every field for an agent, so it goes back to its own values
## `swarmctl agent permissions`
Show or change the capabilities the swarm grants an agent.
Show or change the permissions the swarm grants an agent.
The hive running the agent reads them every 30 seconds and, when they change, restarts the agent after its current turn. Unset leaves the agent on its hive's default: none, except `ruth`, who gets `manage_root_agent`.
The agent applies its tool permissions at its next turn; the hive running it restarts it, after its current turn, when `manage_root_agent` changes. Unset leaves the agent on the default: `messaging`, `meta`, `inbox`, `execution`, plus `manage_root_agent` for `ruth`.
**Usage:** `swarmctl agent permissions <COMMAND>`
###### **Subcommands:**
* `get` — Show the capabilities the swarm grants an agent, or that it sets none
* `set` — Replace the capabilities the swarm grants an agent
* `clear` — Clear the agent's permissions, so its hive's default applies again
* `get` — Show the permissions the swarm grants an agent, or that it sets none
* `set` — Replace the permissions the swarm grants an agent
* `clear` — Clear the agent's permissions, so the default applies again
## `swarmctl agent permissions get`
Show the capabilities the swarm grants an agent, or that it sets none
Show the permissions the swarm grants an agent, or that it sets none
**Usage:** `swarmctl agent permissions get [OPTIONS] <AGENT>`
@ -239,9 +239,9 @@ Show the capabilities the swarm grants an agent, or that it sets none
## `swarmctl agent permissions set`
Replace the capabilities the swarm grants an agent
Replace the permissions the swarm grants an agent
**Usage:** `swarmctl agent permissions set [OPTIONS] --capabilities <CAPABILITIES> <AGENT>`
**Usage:** `swarmctl agent permissions set [OPTIONS] --permissions <PERMISSIONS> <AGENT>`
###### **Arguments:**
@ -249,7 +249,7 @@ Replace the capabilities the swarm grants an agent
###### **Options:**
* `--capabilities <CAPABILITIES>` — The capabilities to grant, comma-separated (`manage_root_agent`). `--capabilities ''` grants none
* `--permissions <PERMISSIONS>` — The permissions to grant, comma-separated: any of `messaging`, `meta`, `inbox`, `execution`, `web_tools`, `manage_root_agent`. `--permissions ''` grants none
* `--controller-socket <PATH>` — swarm-controller's unix socket.
Supplied by the nix module that installs this binary, from the same `socketPath` option the daemon binds; falls back to `SWARM_CONTROLLER_SOCKET`.
@ -258,7 +258,7 @@ Replace the capabilities the swarm grants an agent
## `swarmctl agent permissions clear`
Clear the agent's permissions, so its hive's default applies again
Clear the agent's permissions, so the default applies again
**Usage:** `swarmctl agent permissions clear [OPTIONS] <AGENT>`

View file

@ -177,7 +177,7 @@ harness between turns.
One harness serve binary (`hive-agent`, with its `hive-agent-mcp`
sibling), one shared `nix/agent-modules/` tree, one service unit
(`systemd.services.hive-agent`) for all agents. Privilege
differences live server-side in the broker socket (which tool groups
differences live in the agent's swarm permissions and the broker socket (which tools
and manager-surface calls each agent receives).
`agent.nix` and `ruth.nix` both import the shared `nix/agent-modules/`.
@ -250,7 +250,7 @@ The rest lives alongside this page, in three topic files:
custom static files, connectivity overrides, claude plugins, cargo message
filtering).
- **[mcp.md](mcp.md)** — the MCP tool surface claude sees: core tools,
privileged tool groups, self-wake, authoritative state, the tool envelope,
permission-gated tools, self-wake, authoritative state, the tool envelope,
and the built-in tool allowlist.
Per-subsystem impl detail lives in each module's `//!` doc-comment; these pages

View file

@ -22,10 +22,11 @@ toggle (default `false`) — see
[`docs/tools/subagent.md`](../tools/subagent.md#mcp-servers-available-to-a-subagent)
for what reaches a subagent's own `--mcp-config` and what doesn't.
Tool groups (`HIVE_TOOL_GROUPS`) gate tool access. The default
preset (`AGENT_DEFAULT`) includes `messaging`, `meta`, `inbox`, and
`execution`. The privileged `lifecycle` group
is opt-in via the P3RM1SS10NS tab.
The agent's permissions gate tool access
([`docs/process/conventions.md#permissions`](../process/conventions.md)).
The default (`Permission::DEFAULT`) is `messaging`, `meta`, `inbox` and
`execution`; `swarmctl agent permissions set` changes it, and the next turn
runs on the change.
## Core tools (always available)
@ -106,7 +107,7 @@ hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a
`homeserver`); omitted for agents with no matrix provisioning. Omit
`name` to query self.
**Always-on, no tool group**: `set_status(text)`, `compact()`,
**Always-on, no permission needed**: `set_status(text)`, `compact()`,
`mark_todos_done(ids)`.
- `set_status` — set a free-text status string visible on the
@ -130,26 +131,23 @@ hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a
unknown or already-done ids are silently skipped. Dials the same
in-agent todo socket as `cancel_loose_end`'s `todo` kind.
## Privileged tools (by tool group)
## Privileged tools (by permission)
- **Bash execution** (`execution`) — background shell tasks. See
[`docs/tools/bash.md`](../tools/bash.md).
- **Subagent spawning** — nested sessions on the agent's own runtime
(claude or ACP, via `hive-runtime`) as background tasks, shipped
default-on like bash execution; no tool group gates it. See
default-on like bash execution; no permission gates it. See
[`docs/tools/subagent.md`](../tools/subagent.md).
- **Lifecycle + config** (`lifecycle`) — gates nothing.
Config changes go through a forge PR on `agent-configs/<name>` — see
- **Config changes** — no permission: they go through a forge PR on
`agent-configs/<name>` — see
[`docs/agent-lifecycle/config-changes.md`](../agent-lifecycle/config-changes.md).
- **Forge repos** (`forge`) — gates nothing.
- **Web egress** (`web_tools`) — enables Claude's built-in `WebFetch`
and `WebSearch` tools (not MCP tools; added directly to the
`--allowedTools` list). Off by default; add the group in the
P3RM1SS10NS tab and rebuild to enable.
- **Capabilities** — orthogonal to tool groups, set via the P3RM1SS10NS
tab. No capability registers an MCP tool today; the full list and their
effects are in
[`docs/process/conventions.md#capabilities`](../process/conventions.md).
`--allowedTools` list). Off by default.
- **Recovery mounts** (`manage_root_agent`) — no tool: every agent's state
and config mounted into this agent's container. See
[`docs/process/conventions.md#permissions`](../process/conventions.md).
- **Matrix MCP + extra servers** — `mcp__matrix__*` tools and
per-agent extra MCP config. See
[`docs/tools/matrix.md`](../tools/matrix.md).
@ -199,7 +197,7 @@ status hint lives in the wake prompt + UI header, not here.
## Tool allowlist (`hive_sh4re::permissions::ALLOWED_BUILTIN_TOOLS`)
The built-in list and the `--tools` value it resolves to live in
`hive-sh4re` alongside `ToolGroup`, not in the harness, because the
`hive-sh4re` alongside `Permission`, not in the harness, because the
subagent daemon spawns its own `claude` and must resolve the same set —
see [`docs/tools/subagent.md`](../tools/subagent.md).
@ -207,15 +205,15 @@ see [`docs/tools/subagent.md`](../tools/subagent.md).
`Skill` is what makes an installed plugin's `SKILL.md` invokable —
without it, a skill's `description` frontmatter never gets seen by
the model no matter how well it matches the task.
- Tool-group-gated built-ins: `WebFetch`, `WebSearch` (added once the
operator enables the `web_tools` tool group — see P3RM1SS10NS tab).
- Permission-gated built-ins: `WebFetch`, `WebSearch` (added once the
agent holds `web_tools`).
- Denied by omission (absent from the harness `--tools` /
`--allowedTools`, so they "literally don't exist" in a harness turn):
`Bash`, `Task`, `NotebookEdit`, `TodoWrite`.
- Additionally in the managed-settings deny list
(`/etc/claude-code/managed-settings.json`, un-overridable): `Task`,
`TodoWrite`. `Bash` **isn't** in the managed deny — see below.
- Allowed MCP tools: as listed above (by tool group).
- Allowed MCP tools: as listed above (by permission).
The autonomous harness disallows `Bash` — shell execution goes
through `mcp__bash__run` (background tasks with structured output +
@ -230,7 +228,7 @@ completion wake landing in the wrong session (the harness inbox) for a
choom-started task; `choom` is an operator action (root or `hive-admin`),
so built-in shell there stays within the existing trust boundary. The bash MCP server
(`run` / `status` / `kill`) uses `allowedTools = ["*"]` so all
`mcp__bash__*` tools are always available regardless of tool groups.
`mcp__bash__*` tools are available whenever the agent holds `execution`.
`WebFetch` / `WebSearch` are off by default; enable the `web_tools`
tool group in the P3RM1SS10NS tab and rebuild the agent to enable them.
`WebFetch` / `WebSearch` are off by default; grant `web_tools`
(`swarmctl agent permissions set`) to enable them from the next turn.

View file

@ -16,14 +16,13 @@ Settings, Core). Every page links back to H0M3, so you're
never more than one step from the hub.
The **dashboard** itself (`/dashboard.html`) is where you'll spend most
of your time. It's a single page with five tabs:
of your time. It's a single page with four tabs:
- **4G3NTS** — every agent, live. This is the default tab and the one
you'll check most.
- **R3BU1LD QU3U3**, **M3T4 1NPUTS** and **BUILD L0GS** — the build
lifecycle: the rebuild queue with a live log of whatever is building,
the meta flake inputs you can bump, and the build log history.
- **P3RM1SS10NS** — what tools and system-level access each agent has.
Everything else lives on its own page instead, all reachable from the
H0M3 hub: **Flow** (`/flow.html`, the raw live message stream across
@ -58,10 +57,10 @@ shows as "needs update."
streaming log of whatever's currently building. Useful right after
merging a config change or bumping a flake input.
**Grant or revoke a tool/capability.** P3RM1SS10NS is a checkbox matrix
— rows are agents, columns are tool groups or capabilities. Nothing
takes effect until you hit **save all** at the bottom of the tab; a
save queues a rebuild for whichever agents actually changed.
**Grant or revoke a permission.** Not on the hive dashboard: an agent's
permissions live in the swarm. Set them with `swarmctl agent permissions`,
or on the swarm UI's create-agent screen when creating the agent; see
[`process/conventions.md`](../process/conventions.md#permissions).
**Read an agent's logs.** Logs aren't in the hive dashboard. Every
hive ships its agents', daemons' and infra containers' journals to the

View file

@ -20,7 +20,7 @@ from the dashboard tab strip.
- **← home back-link**: top-left of the chrome, links to the H0M3 hub
at `/`. Every surface links back to the hub rather than to each other.
- **Tab strip**: `◆ 4G3NTS ◆`, `◆ R3BU1LD QU3U3 ◆`, `◆ M3T4 1NPUTS ◆`,
`◆ BUILD L0GS ◆`, `◆ P3RM1SS10NS ◆`. In-page tabs only —
`◆ BUILD L0GS ◆`. In-page tabs only —
the SYST3M panels live on the
standalone **C0R3** page (`/core.html`), and FL0W / ST4TS
live on their own pages too, all reachable from the
@ -77,19 +77,15 @@ H0M3 hub (served at
`/core.html`), with minimal chrome: a `← home` back-link and a single pane,
**K3PT ST4T3**. The page is its own esbuild bundle (`core.js`) that
cold-loads `/api/state` and subscribes to `/api/dashboard/stream` for
`tombstones_changed` and `tool_groups_changed`
(the latter re-renders the stale-perms sub-section when permission
data changes). (Rebuild queue and meta inputs are dashboard tabs — see
below.)
`tombstones_changed`. (Rebuild queue and meta inputs are dashboard
tabs — see below.)
Per-container CPU, memory and disk load isn't shown in the hive UI. The
same figures go out as OTEL metrics and appear in Grafana (see
[observability](../scheduler/observability.md)). An agent's CPU and memory
caps live per agent in the swarm UI.
**K3PT ST4T3** — two sub-sections on one pane:
*Tombstones*: agents with kept state and **no container** (size + age +
**K3PT ST4T3** — agents with kept state and **no container** (size + age +
claude-creds badge). One action: `PURG3` (wipes state + applied
dirs; `POST /api/purge-tombstone/{name}`).
@ -104,13 +100,6 @@ container is all hive-c0re can actually prove. Fixing it properly
needs a recorded destroy — **deferred** to the swarm-controller /
snapshot-storage rework where the problem changes shape.
*Stale permission entries*: agents with explicit tool-group JSON
entries but no live container — typically renamed or deleted agents
whose entries persisted in `tool-groups.json`. Loaded when the page opens;
autorefreshes on `tool_groups_changed` SSE
events. Each ghost agent gets a `✕ clear perms` button
(`DELETE /api/permissions/{name}`).
## Build tabs (`#rebuild`, `#meta`, `#buildlogs`)
The build lifecycle — rebuild queue, live build log, meta inputs, and
@ -227,74 +216,6 @@ badge with a ticking elapsed-time chip; expanding streams output via
(suspends on manual scroll-up). Deep-link: `?id=N#buildlogs` opens the
entry with that id pre-expanded.
## P3RM1SS10NS tab
Per-agent tool-group configuration, rendered as a column-driven
checkbox matrix: rows are agents, columns are the group names fetched
from hive-c0re. The column list is authoritative — adding a new
tool-group to hive-c0re requires no UI change; the new column appears
automatically. Capabilities aren't set here: they're the swarm's,
set with `swarmctl agent permissions` (see
`docs/process/conventions.md#capabilities`).
The snapshot carries `agents` (the full manageable roster — live
containers ∪ agents with an explicit entry) and `effective` (per-agent
explicit-or-role-default values) alongside the explicit `assignments`
map. Rows come from `agents` so **agents on defaults always appear**
(not just those with an explicit entry), and checkboxes reflect the
`effective` values so a default agent shows the groups it actually runs
with rather than blank — which also means saving it won't silently
strip those defaults. The `(default)` badge keys off absence from
`assignments` (no explicit entry).
Fetches fire on tab activation (not page-load) to avoid unnecessary
work when the operator never visits this tab. Live mutations from the
rebuild-queue worker are also pushed via the `tool_groups_changed`
SSE event (same payload shape as the GET endpoint), so an open P3RM1SS10NS tab reflects worker-applied changes
without requiring navigation. Tab-activation re-fetches remain as a
safety net for reconnect windows.
<!-- vale write-good.Passive = NO -->
**T00L GR0UPS** — per-agent tool-group permissions. Tool groups are
named buckets of MCP tools; each agent starts with a role default
(sub-agents: `messaging`, `meta`, `inbox`, `execution` —
`ToolGroup::AGENT_DEFAULT`; hive-c0re seeds the root agent to
`ToolGroup::MANAGER_DEFAULT` — `messaging`, `meta`, `inbox`,
`lifecycle`, `execution`,
that is, every group except `forge` and `web_tools`). 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`. hive-c0re queues a rebuild so `HIVE_TOOL_GROUPS`
takes effect.
<!-- vale write-good.Passive = YES -->
The current tool groups are: `messaging`, `meta`, `inbox`, `lifecycle`,
`forge`, `execution`, `web_tools`. All listed in `ToolGroup::ALL` in `hive-sh4re`. The
`web_tools` group is special: it carries no MCP tools; instead it adds
Claude's built-in `WebFetch` and `WebSearch` to `--tools` /
`--allowedTools` for that agent session.
The table's shape: a `.tg-table-wrap` outer scroll container, `thead`
with a label column (`.tg-agent-col`) + one column per group
(`.tg-group-col`). Each tbody row is one agent: a name cell
and its checkbox cells, where each checkbox carries `data-baseline` (its
render-time state) and the row carries `data-agent` for dirty-tracking.
**Saving — one button for the whole page.** No per-row save
buttons exist. A single page-level `.perm-save-bar` with a **save all (N
agents)** button sits at the bottom of the pane, enabled only when some
checkbox diverges from its baseline. Selecting it diffs every checkbox
and POSTs one batch to `POST /api/permissions` as
`{ changes: [ { agent, tool_groups } ] }` — the batch includes only the
agents whose groups changed (an included array fully replaces the
agent's groups).
The batch is atomic: it validates every change first and on any error
rejects the whole POST (`{error}`, nothing applied); a clean 200 (`ok`)
flips the bar to a queued→rebuilding state and re-fetches the table.
Live `tool_groups_changed` events re-render the
matrix unless it has unsaved edits, so an in-progress edit set
isn't clobbered.
## ST4TS page (`/stats.html`)
Hive-wide turn statistics, aggregated across every agent's
@ -733,31 +654,6 @@ below — some endpoints aren't in it yet.
- `POST /api/cancel-reminder/{id}` — hard-delete a pending reminder.
- `POST /api/retry-reminder/{id}` — re-arm a reminder whose delivery
failed (clears the failure state so the scheduler retries).
- `GET /api/tool-groups` — returns `{ groups: Vec<String>,
assignments: BTreeMap<String, Vec<String>>,
descriptions: BTreeMap<String, String> }`. `groups` is the
ordered list of all known tool-group names (drives the column
headers in the P3RM1SS10NS tab); `assignments` is the per-agent
override map (absent agents use the role default); `descriptions`
maps each group name to a short human-readable string surfaced as
a column-header tooltip on hover.
- `POST /api/tool-groups/{agent}` — body `{ groups: ["name", …] }`.
Writes the tool-group set for `{agent}` to
`/var/lib/hyperhive/meta/tool-groups.json` and queues a rebuild so
`HIVE_TOOL_GROUPS` takes effect. Agent name validated;
`guard_agent_name` applied.
- `POST /api/permissions` — batch perm apply for the save-all
permissions button. Body
`{ changes: [{ agent, tool_groups?: ["name", …] }] }`.
An omitted `tool_groups` leaves the agent untouched, an empty array
clears it, a populated array fully replaces it (same replace
semantics as the per-agent endpoint above). Each affected agent gets
one `PermChange` queue entry. **Atomic**: the endpoint validates every
change first (agent names via `guard_agent_name`, group names) and on
any validation error writes or enqueues nothing (non-2xx `{ error }`);
it skips rows with the field omitted rather than erroring on them.
Returns `200 "ok"` on
success.
- `POST /api/meta-update` — `nix flake update` the selected
`meta/flake.lock` inputs, then rebuild the affected agents.
- `GET /api/dashboard/stream` — unified live event channel:
@ -805,11 +701,6 @@ payload):
operator cancel / retry (`/api/system/reminders/*`), `cancel_loose_end`
with Reminder kind, and the scheduler tick after each delivery
batch (`reminder_scheduler`).
- `tool_groups_changed` (seq, groups: `Vec<str>`, descriptions: map,
assignments: `BTreeMap<String, Vec<String>>`) — full snapshot of
tool-group assignments. Emitted from the rebuild-queue worker after
a `PermChange` / ToolGroups entry commits the JSON file. Same
shape as `GET /api/tool-groups`; P3RM1SS10NS tab subscribes.
- `tombstones_changed` (seq, tombstones: `Vec<TombstoneView>`) —
full snapshot of destroyed-but-state-kept agents. Emitted on
destroy, purge, and revive. C0R3 K3PT ST4T3 tab subscribes.