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
|
|
@ -98,7 +98,7 @@ use hive_claude::{Attach, Claude, Config, SessionStore};
|
|||
use hive_runtime::{
|
||||
AcpCommand, AcpRuntime, PermissionAsk, PermissionPolicy, Runtime as _, RuntimeSpec, TurnStop,
|
||||
};
|
||||
use hive_sh4re::permissions::ToolGroup;
|
||||
use hive_sh4re::permissions::Permission;
|
||||
use swarm_queue_client::agent_inference::AgentInference;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
|
|
@ -552,12 +552,16 @@ impl State {
|
|||
command
|
||||
}
|
||||
|
||||
/// Read the swarm's key once, refuse a `model` outside the list in effect
|
||||
/// for this agent (see `check_model`), and return the subagent cap in
|
||||
/// effect (see `subagent_cap`).
|
||||
/// Read the swarm's keys once: refresh this agent's permissions (the
|
||||
/// tools the subagent about to run gets), refuse a `model` outside the list
|
||||
/// in effect for this agent (see `check_model`), and return the subagent
|
||||
/// cap in effect (see `subagent_cap`).
|
||||
async fn swarm_limits(&self, model: Option<&str>) -> anyhow::Result<Option<u32>> {
|
||||
let read = match &self.models {
|
||||
Some(reader) => Some(reader.read().await),
|
||||
Some(reader) => {
|
||||
reader.refresh_permissions().await;
|
||||
Some(reader.read().await)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let agent = self
|
||||
|
|
@ -1051,7 +1055,7 @@ fn validate_name(name: &str) -> anyhow::Result<()> {
|
|||
|
||||
/// Env var carrying the nix-configured `services.hyperhive.agent.availableModels` as a
|
||||
/// comma-separated list, forwarded onto this daemon's unit by
|
||||
/// `nix/agent-modules/mcp.nix` the same way `HIVE_TOOL_GROUPS` is.
|
||||
/// `nix/agent-modules/mcp.nix` from the harness unit.
|
||||
const AVAILABLE_MODELS_ENV: &str = "HIVE_AVAILABLE_MODELS";
|
||||
|
||||
/// The models this daemon may spawn a subagent on, from
|
||||
|
|
@ -1136,7 +1140,7 @@ fn check_model(model: Option<&str>, list: &ModelList) -> anyhow::Result<()> {
|
|||
"the swarm's subagent_models for this agent",
|
||||
),
|
||||
ModelList::Local(None) => return Ok(()),
|
||||
// Same split/trim/skip-empty rules as `ToolGroup::parse_list`.
|
||||
// Split, trim, skip empty tokens.
|
||||
ModelList::Local(Some(env)) => (
|
||||
env.split(',')
|
||||
.map(str::trim)
|
||||
|
|
@ -1186,7 +1190,8 @@ fn subagent_otel_attrs(name: &str) -> String {
|
|||
/// gates covering one half each, neither substituting for the other:
|
||||
/// `strict_mcp_config` over **MCP** tools (no ambient discovery, so exactly
|
||||
/// what [`crate::mcp_config::build`] renders), `--tools` over **built-in**
|
||||
/// ones, via [`hive_sh4re::permissions::subagent_builtin_tools_arg`], which
|
||||
/// ones, via [`hive_sh4re::permissions::subagent_builtin_tools_for`] over the
|
||||
/// agent's permissions ([`crate::swarm_models::permissions_now`]), which
|
||||
/// documents its one `Bash` exception. `--tools` does not filter `mcp__*`,
|
||||
/// so the signal tools are unnamed in it (`docs/tools/subagent.md`).
|
||||
///
|
||||
|
|
@ -1208,10 +1213,13 @@ fn build_config(
|
|||
dir: Option<&str>,
|
||||
signal_url: Option<&str>,
|
||||
) -> Config {
|
||||
let tools = hive_sh4re::permissions::subagent_builtin_tools_arg();
|
||||
let tools = hive_sh4re::permissions::subagent_builtin_tools_for(
|
||||
&crate::swarm_models::permissions_now(),
|
||||
)
|
||||
.join(",");
|
||||
// Refuse to spawn on an empty `--tools` rather than reason about what it
|
||||
// would do: what an empty value means is disputed and depends on the
|
||||
// installed claude release (see `builtin_tools_arg`). Nothing here wants
|
||||
// installed claude release (see `builtin_tools_for`). Nothing here wants
|
||||
// "a subagent with no built-ins" anyway, so an empty resolution is a bug
|
||||
// either way, and failing on it is safe under every reading.
|
||||
assert!(
|
||||
|
|
@ -2063,9 +2071,9 @@ fn acp_runtime(
|
|||
sessions: &Path,
|
||||
name: &str,
|
||||
) -> AcpRuntime<hive_claude::NeverCompact> {
|
||||
let groups = hive_sh4re::permissions::effective_tool_groups();
|
||||
let perms = crate::swarm_models::permissions_now();
|
||||
let permit: PermissionPolicy =
|
||||
Arc::new(move |ask: &PermissionAsk<'_>| acp_permits(ask, &groups));
|
||||
Arc::new(move |ask: &PermissionAsk<'_>| acp_permits(ask, &perms));
|
||||
AcpRuntime::new(
|
||||
command,
|
||||
acp_session_file(sessions, name),
|
||||
|
|
@ -2079,15 +2087,15 @@ fn acp_runtime(
|
|||
///
|
||||
/// - `other`-kind calls to tools of the MCP servers the session was handed;
|
||||
/// - the file tools: `read`, `edit`, `search`;
|
||||
/// - `fetch` with the `web_tools` group;
|
||||
/// - `execute` with the `execution` group, as a claude subagent gets `Bash`
|
||||
/// - `fetch` with the `web_tools` permission;
|
||||
/// - `execute` with the `execution` permission, as a claude subagent gets `Bash`
|
||||
/// ([`hive_sh4re::permissions::subagent_builtin_tools_for`]).
|
||||
fn acp_permits(ask: &PermissionAsk<'_>, groups: &[ToolGroup]) -> bool {
|
||||
fn acp_permits(ask: &PermissionAsk<'_>, perms: &[Permission]) -> bool {
|
||||
match ask.kind {
|
||||
"other" => ask.mcp_server.is_some(),
|
||||
"read" | "edit" | "search" => true,
|
||||
"fetch" => groups.contains(&ToolGroup::WebTools),
|
||||
"execute" => groups.contains(&ToolGroup::Execution),
|
||||
"fetch" => perms.contains(&Permission::WebTools),
|
||||
"execute" => perms.contains(&Permission::Execution),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
@ -2984,15 +2992,13 @@ mod tests {
|
|||
/// parent actually licenses rather than against a restatement of it that
|
||||
/// could drift.
|
||||
///
|
||||
/// ⚠️ **Scope: this catches a code divergence, not an environment one.**
|
||||
/// Both sides resolve in one process off one `HIVE_TOOL_GROUPS`, so it
|
||||
/// cannot see the two real units disagreeing about that var — which they
|
||||
/// did until `nix/agent-modules/mcp.nix` forwarded it onto this daemon.
|
||||
/// Nothing in a unit test reaches that; it is a deployment property.
|
||||
/// ⚠️ **Scope: this catches a code divergence, not a read one.** Both
|
||||
/// sides resolve off one permission set here; that the parent and this
|
||||
/// daemon read the same key is a deployment property.
|
||||
#[test]
|
||||
fn a_subagent_gets_no_builtin_its_parent_lacks() {
|
||||
let allowed = hive_sh4re::permissions::subagent_builtin_tools_for(
|
||||
&hive_sh4re::permissions::effective_tool_groups(),
|
||||
&crate::swarm_models::permissions_now(),
|
||||
);
|
||||
let config = build_config("n", None, None, None, None, None);
|
||||
for tool in spawned_tools(&config).split(',') {
|
||||
|
|
@ -5412,8 +5418,8 @@ echo '{"type":"result","subtype":"success","is_error":false}'
|
|||
kind,
|
||||
mcp_server: None,
|
||||
};
|
||||
let none: &[ToolGroup] = &[];
|
||||
let both = &[ToolGroup::WebTools, ToolGroup::Execution];
|
||||
let none: &[Permission] = &[];
|
||||
let both = &[Permission::WebTools, Permission::Execution];
|
||||
for kind in ["read", "edit", "search"] {
|
||||
assert!(acp_permits(&ask(kind), none), "{kind}");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue