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:
parent
f862354e43
commit
f0844f6da3
73 changed files with 1578 additions and 2742 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue