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
|
|
@ -1,11 +1,13 @@
|
|||
//! Reads the subagent settings the swarm sets for this agent.
|
||||
//! Reads the subagent settings and permissions the swarm sets for this agent.
|
||||
//!
|
||||
//! swarm-controller keeps the agent's settings under its key in the
|
||||
//! `agent-inference` KV bucket. Its `subagent_models`, when set, is the list
|
||||
//! a `start` or `continue` naming a `model` is checked against, in place of
|
||||
//! `HIVE_AVAILABLE_MODELS` (`crate::session::check_model`). Its
|
||||
//! `max_subagents`, when set, caps how many subagents run at once
|
||||
//! (`crate::session::State::reserve_under`).
|
||||
//! (`crate::session::State::reserve_under`). The agent's permissions, under
|
||||
//! its key in `agent-permissions`, decide a new subagent's tools
|
||||
//! ([`permissions_now`]).
|
||||
//!
|
||||
//! **Read on every `start` and `continue`**, never cached, with the one subject the
|
||||
//! agent's own queue credential is granted in that bucket
|
||||
|
|
@ -23,10 +25,27 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
|||
use std::sync::{Arc, OnceLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use hive_sh4re::permissions::Permission;
|
||||
use swarm_queue_client::agent_inference::{self, AgentInference, read_own};
|
||||
use swarm_queue_client::agent_permissions;
|
||||
|
||||
use crate::swarm_term::{Target, connect};
|
||||
|
||||
/// The last permission set [`Reader::refresh_permissions`] read; `None` until
|
||||
/// one succeeds.
|
||||
static LAST_PERMISSIONS: std::sync::RwLock<Option<Vec<Permission>>> = std::sync::RwLock::new(None);
|
||||
|
||||
/// The permissions this agent holds now, for the subagent being spawned: the
|
||||
/// last set read, or [`Permission::DEFAULT`] before any read succeeds.
|
||||
#[must_use]
|
||||
pub fn permissions_now() -> Vec<Permission> {
|
||||
LAST_PERMISSIONS
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone()
|
||||
.unwrap_or_else(|| Permission::DEFAULT.to_vec())
|
||||
}
|
||||
|
||||
/// How long after a failed connect the task tries again.
|
||||
const RETRY_AFTER: Duration = Duration::from_mins(1);
|
||||
|
||||
|
|
@ -126,6 +145,46 @@ impl Reader {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the permissions the swarm grants this agent and keep them for
|
||||
/// [`permissions_now`]: its list, or the default for an unset key. A read
|
||||
/// that fails keeps the last value.
|
||||
pub async fn refresh_permissions(&self) {
|
||||
let read = match &self.source {
|
||||
Source::Queue { agent, client } => read_permissions(agent, client).await,
|
||||
#[cfg(test)]
|
||||
Source::Fixed { .. } => None,
|
||||
};
|
||||
if let Some(perms) = read {
|
||||
*LAST_PERMISSIONS
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(perms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_permissions(
|
||||
agent: &str,
|
||||
client: &OnceLock<async_nats::Client>,
|
||||
) -> Option<Vec<Permission>> {
|
||||
let client = client.get()?;
|
||||
swarm_queue_client::ensure_connected(client).ok()?;
|
||||
match tokio::time::timeout(READ_TIMEOUT, agent_permissions::read(client, agent)).await {
|
||||
Ok(Ok(agent_permissions::Read::Current(record))) => Some(
|
||||
record
|
||||
.permissions
|
||||
.unwrap_or_else(|| Permission::default_for(agent)),
|
||||
),
|
||||
Ok(Ok(agent_permissions::Read::NoBucket)) => None,
|
||||
Ok(Err(e)) => {
|
||||
tracing::warn!(error = %e, "subagent permissions: reading this agent's key failed");
|
||||
None
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::warn!("subagent permissions: no answer within {READ_TIMEOUT:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_queue(agent: &str, client: &OnceLock<async_nats::Client>) -> Read {
|
||||
|
|
|
|||
Loading…
Reference in a new issue