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

@ -91,10 +91,12 @@ and end the turn.";
/// prompt (`--system-prompt-file`). Static settings are no longer
/// passed here — they live at `/etc/claude-code/managed-settings.json`
/// and claude auto-discovers them.
/// Materialised once at harness startup; shared between the turn loop
/// and the operator-driven `/compact` path so both invocations look
/// identical to claude (same MCP surface, same allowed tools, same
/// role prompt — only the stdin payload differs).
/// Materialised at harness startup; shared between the turn loop and the
/// operator-driven `/compact` path so both invocations look identical to
/// claude (same MCP surface, same allowed tools, same role prompt — only the
/// stdin payload differs). The MCP config is rewritten before each
/// invocation ([`TurnFiles::refresh_mcp_config`]), since which servers it
/// holds follows the agent's current permissions.
#[derive(Clone)]
pub struct TurnFiles {
pub mcp_config: PathBuf,
@ -114,6 +116,16 @@ impl TurnFiles {
system_prompt: write_system_prompt(socket, label).await?,
})
}
/// Rewrite the MCP config from the agent's current permissions, so a
/// change to `execution` reaches this invocation's `bash` server. A
/// failed write is logged and the previous file stays.
pub async fn refresh_mcp_config(&self) {
if let Err(e) = tokio::fs::write(&self.mcp_config, mcp_config::render_claude_config()).await
{
tracing::warn!(path = %self.mcp_config.display(), error = %e, "rewriting the claude MCP config failed");
}
}
}
/// Drop the MCP config blob claude reads from `--mcp-config <path>`.
@ -335,22 +347,25 @@ pub fn make_session(bus: &Bus) -> Result<AgentSession> {
})
}
/// The answer to an ACP agent's permission requests, see [`acp_permits`].
/// The answer to an ACP agent's permission requests, see [`acp_permits`],
/// against the agent's permissions at the moment it asks.
fn acp_permission_policy() -> PermissionPolicy {
let web =
mcp_config::effective_tool_groups().contains(&hive_sh4re::permissions::ToolGroup::WebTools);
std::sync::Arc::new(move |ask: &PermissionAsk<'_>| acp_permits(ask, web))
std::sync::Arc::new(|ask: &PermissionAsk<'_>| {
let web = crate::swarm_permissions::current()
.contains(&hive_sh4re::permissions::Permission::WebTools);
acp_permits(ask, web)
})
}
/// Whether an ACP agent may run the tool call it asks about. Default-deny,
/// allowing what a claude agent has:
///
/// - `other`-kind calls to tools of the MCP servers the session was handed
/// — which of those an agent gets is already decided by its tool groups
/// — which of those an agent gets is already decided by its permissions
/// (`mcp_config`);
/// - the file tools, mirroring the claude built-ins
/// (`hive_sh4re::permissions`): `read`, `edit`, `search`;
/// - `fetch` with the `web_tools` group (`web`).
/// - `fetch` with the `web_tools` permission (`web`).
///
/// Everything else is refused, including a built-in shell (`execute`) —
/// shell goes through `mcp__bash__run` — and any kind this list doesn't name.
@ -395,6 +410,7 @@ pub async fn drive_turn(
// Heuristic: context large AND prompt cache gone cold.
maybe_auto_reset(bus, session);
}
files.refresh_mcp_config().await;
let config = claude_config(bus, files);
let sink = BusSink::new(bus);
let mut result = session.run(&config, prompt, &sink).await;
@ -621,6 +637,7 @@ pub async fn run_pending_compact(files: &TurnFiles, bus: &Bus, session: &AgentSe
text: "operator: /compact — running on idle session".into(),
});
bus.set_state(crate::events::TurnState::Compacting);
files.refresh_mcp_config().await;
let config = claude_config(bus, files);
let sink = BusSink::new(bus);
report_compact(bus, session.compact(&config, &sink).await);