Watch
0
0
Fork
You've already forked hyperhive
0

agent capabilities live in a swarm KV record, set from swarmctl

An agent's capabilities move from the hive's meta repo
(`meta/capabilities.json`, injected as `HIVE_CAPABILITIES`) into the
`capabilities` field of its key in a new `agent-permissions` KV bucket,
which swarm-controller alone writes.

- swarm-queue-client: `agent_permissions` (bucket, key, `AgentPermissions`,
  direct-get read, the import subject and its wire types).
- swarm-controller: creates the bucket at start; `GET|PUT|DELETE
  /api/agents/{name}/permissions` (deny_unknown_fields and unknown
  capability ⇒ 422, duplicate ⇒ 400, agent must be declared by a hive);
  answers each hive's one-shot import, writing a key only where there is
  none and only for agents that hive's wanted state declares.
- swarmctl: `agent permissions get|set|clear`.
- swarm-nats-auth: a hive reads every key of the bucket and sends its
  import on its own subject; the controller subscribes to the imports and
  may answer them (`responses`).
- hive-c0re: reads each of its agents' keys every 30s into
  /var/lib/hyperhive/swarm-agent-permissions.json and restarts an agent,
  gracefully, when its capabilities change. Unset is the compiled-in
  default (ruth: manage_root_agent; everyone else: none). A failed read
  keeps the stored value. On first boot it seeds that file from
  meta/capabilities.json, sends the grants to the controller, and deletes
  the file once the controller answers.

Deleted: the capabilities.json reader/writer, `HIVE_CAPABILITIES` in the
rendered meta flake, ruth's capability seed, the capabilities half of the
permissions rebuild job (`PermPayload::Capabilities`/`Combined`), the
dashboard's capabilities table and `capabilities_changed` event, and the
now-unused `Capability::description`.

Part of #5170.
This commit is contained in:
atlas 2026-10-15 00:24:41 +02:00 • committed by mara
commit f862354e43
47 changed files with 1943 additions and 1146 deletions

View file

@ -1,8 +1,9 @@
//! Per-agent authorization: named MCP-tool groups (`ToolGroup`) and
//! system-level capability grants (`Capability`). Both are declared in
//! per-agent config (`tool-groups.json` / `capabilities.json`), injected
//! into the container as env vars, and read by the harness to decide
//! which MCP tools claude actually sees.
//! system-level capability grants (`Capability`). Tool groups are declared
//! in the hive's `tool-groups.json`, injected into the container as an env
//! var, and read by the harness to decide which MCP tools claude actually
//! sees. Capabilities are granted in the swarm's `agent-permissions` bucket
//! and enforced by hive-c0re.
//!
//! It also resolves the *built-in* claude tool list ([`builtin_tools_arg`]):
//! that set is group-gated too (`web_tools`), so it belongs next to the
@ -154,11 +155,11 @@ pub enum ToolGroup {
Inbox,
/// Gates no tool today — `list_containers`, its only member, was
/// removed with no replacement. Kept so existing
/// `meta/capabilities.json` grants still parse; `tools()` returns
/// `meta/tool-groups.json` grants still parse; `tools()` returns
/// `&[]`.
Lifecycle,
/// Gates no tool today — `create_repo`, its only member, was removed
/// with no replacement. Kept so existing `meta/capabilities.json`
/// with no replacement. Kept so existing `meta/tool-groups.json`
/// grants still parse; `tools()` returns `&[]`.
Forge,
/// Gates whether the `bash` MCP server (`mcp__bash__run`/`status`/
@ -323,16 +324,12 @@ impl ToolGroup {
}
}
/// Per-agent capability grants. Stored in `meta/capabilities.json`
/// (same shape as `tool-groups.json`: `{ "alice": ["manage_root_agent"] }`).
/// Capabilities control system-level access hive-c0re enforces at
/// dispatch time; they are orthogonal to tool groups (which control
/// which MCP tools the harness exposes to claude).
///
/// Injected into containers as `HIVE_CAPABILITIES` (comma-separated
/// `snake_case`) via `meta::render_flake`. The harness reads this to
/// conditionally register capability-gated MCP tools so claude only
/// sees tools it can actually invoke. See `docs/process/conventions.md::Capabilities`.
/// Per-agent capability grants: the `capabilities` field of the swarm's
/// `agent-permissions` record (`swarm_queue_client::agent_permissions`), set
/// with `swarmctl agent permissions set`. Capabilities control system-level
/// access hive-c0re enforces, through the container's bind mounts; they are
/// orthogonal to tool groups (which control which MCP tools the harness
/// exposes to claude). See `docs/process/conventions.md::Capabilities`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, strum::IntoStaticStr)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
@ -348,26 +345,14 @@ pub enum Capability {
/// there was. With the hierarchy removed there is no privileged root
/// to single out, so the same grant now means "manage any agent".
/// Kept under the old spelling deliberately — renaming it would turn
/// every `manage_root_agent` already stored in `capabilities.json`
/// into an unrecognised name that `capabilities::prune_unknown`
/// silently drops.
/// every `manage_root_agent` already stored in a swarm record into a
/// name the hive can no longer decode.
ManageRootAgent,
}
impl Capability {
/// Every known capability in a stable order. Use this to enumerate
/// columns in the permissions UI or validate incoming capability strings.
/// Every known capability in a stable order.
pub const ALL: &'static [Self] = &[Self::ManageRootAgent];
/// Short human-readable description suitable for a tooltip or help text.
#[must_use]
pub fn description(self) -> &'static str {
match self {
Self::ManageRootAgent => {
"manage any agent: every agent's state (rw) and config (ro) mounted for recovery"
}
}
}
}
#[cfg(test)]