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

@ -120,7 +120,6 @@ pub(super) async fn run_node(
// The payload rides the node and is destructured here, so the executor
// takes it directly instead of re-matching the kind behind a `bail!`
// that could never fire.
NodeKind::WritePermFile { payload, .. } => run_write_perm_file(coord, agent, payload).await,
NodeKind::EmitRebuilt { ok, .. } => {
run_emit_rebuilt(coord, agent, coord.job_queue.root_of(id), *ok).await;
Ok(())
@ -790,30 +789,6 @@ async fn run_write_dropin(coord: &Arc<Coordinator>, name: &str) -> Result<()> {
Ok(())
}
/// Write + commit the perm file(s) (fused under `META_LOCK` so the
/// working tree is never left dirty), then emit the P3RM1SS10NS-tab
/// snapshots so the dashboard reflects the new assignment.
async fn run_write_perm_file(
coord: &Arc<Coordinator>,
name: &str,
payload: &super::model::PermPayload,
) -> Result<()> {
use super::model::PermPayload;
// Runs under the deploy window (it declares `Resource::MetaWindow`): a
// perm commit landing inside another node's staged prepare→finalize
// window would sweep the staged deploy lock into its commit (the
// commits are also path-limited in meta.rs — belt and braces).
match payload {
PermPayload::ToolGroups { groups } => {
crate::meta::commit_tool_groups(name, groups)
.await
.with_context(|| format!("commit tool-groups for {name}"))?;
coord.emit_tool_groups_snapshot();
}
}
Ok(())
}
/// Compute which agents a `nix flake update <inputs>` on the meta
/// flake affects — the fan-out set for `MetaUpdate` DAGs. Empty
/// `inputs` or any input under `hyperhive` → every container;

View file

@ -43,7 +43,7 @@ use hive_jobq::{Graph, NodeId};
use hive_jobq_wire::{GraphNode, GraphWire};
use tokio::sync::Notify;
pub use model::{NodeKind, PermPayload, State};
pub use model::{NodeKind, State};
use resource::Resource;
/// A job under construction: `hive_jobq`'s builder over this queue's payload

View file

@ -1,7 +1,7 @@
//! Data model for the generic job-DAG queue: the node kinds — the primitive
//! operations — and what each one carries. The `State` / `PermPayload` wire
//! enums live in `hive_host_sock::jobs` (they travel on the host admin
//! socket) and are re-exported here for the queue's internal use. The graph
//! operations — and what each one carries. The `State` wire
//! enum lives in `hive_host_sock::jobs` (it travels on the host admin
//! socket) and is re-exported here for the queue's internal use. The graph
//! itself is served through `hive_jobq_wire`'s generic projection — there is
//! no second, typed view of it any more.
//!
@ -12,7 +12,7 @@
//! job is exactly the nodes it declared. See `docs/scheduler/coordinator.md::Job queue`
//! for the full design.
pub use hive_host_sock::jobs::{PermPayload, State};
pub use hive_host_sock::jobs::State;
use serde::Serialize;
/// The primitive operations — each kind maps to one executor fn in
@ -98,12 +98,10 @@ pub enum NodeKind {
PauseDrain { agent: String },
/// `set_nspawn_flags` + `set_resource_limits` + daemon-reload.
WriteDropin { agent: String },
/// Commit `tool-groups.json` per its `payload`.
WritePermFile { agent: String, payload: PermPayload },
/// Group root of a rebuild subtree — the **brace**, scoped to one
/// agent. Why braces exist and what they cost: _Braces_.
AgentWindow { agent: String },
/// Tail node of a rebuild / perm-change: emit this agent's `Rebuilt`
/// Tail node of a rebuild: emit this agent's `Rebuilt`
/// manager event. One node per agent *and* per outcome; no cancel
/// variant (a cancelled DAG never ran, so there is no rebuild to
/// report).
@ -185,7 +183,6 @@ impl NodeKind {
| NodeKind::PauseSignal { agent }
| NodeKind::PauseDrain { agent }
| NodeKind::WriteDropin { agent }
| NodeKind::WritePermFile { agent, .. }
| NodeKind::AgentWindow { agent }
| NodeKind::EmitRebuilt { agent, .. }
| NodeKind::SetWanted { agent, .. } => agent,

View file

@ -20,7 +20,7 @@
//! `lifecycle::is_running` read), so [`super::power`] assembles them out of
//! the primitives this module exports ([`rebuild_nodes`]).
use super::model::{NodeKind, PermPayload};
use super::model::NodeKind;
use super::resource::Resource;
use super::{Handle, JobBuilder};
@ -403,26 +403,6 @@ pub fn destroy(builder: &JobBuilder, agent: &str, purge: bool) {
.after_ok(last);
}
/// Perm change: commit the JSON file, then the rebuild subgraph so
/// the updated `HIVE_TOOL_GROUPS` env var takes
/// effect in the container. Group-roots are `WritePermFile` plus the rebuild
/// subgraph's `MetaSync` / `Prebuild` / `Reconcile`, so the `EmitRebuilt` tail
/// edges all four.
pub fn perm_change(builder: &JobBuilder, agent: &str, payload: PermPayload) {
let write = builder
.node(NodeKind::WritePermFile {
agent: agent.to_owned(),
payload,
})
.needs(Resource::MetaWindow);
let roots = rebuild_nodes(builder, agent, true, Some(write));
emit_rebuilt_tails(
builder,
agent,
&[write, roots.meta_sync, roots.agent_window, roots.reconcile],
);
}
/// Meta-input lock bump. The `MetaLock` executor grows one rebuild subgraph
/// per affected agent into *this same* DAG on completion (declared onto the
/// builder it was handed) — appended *after* the bump lands so their prebuilds

View file

@ -1515,38 +1515,6 @@ fn destroy_container_must_not_suppress_crash_watch() {
);
}
#[test]
fn perm_change_shape_prefixes_rebuild_chain() {
let q = JobQueue::new(1);
insert(&q, |builder| {
templates::perm_change(
builder,
"agent-a",
PermPayload::ToolGroups { groups: vec![] },
);
});
assert_eq!(
declared_shape(&q)
.iter()
.map(|d| d.kind)
.collect::<Vec<_>>(),
vec![
"write_perm_file",
"meta_sync",
"agent_window",
"prebuild",
"stop_for_update",
"swap",
"rebuild_bookkeeping",
"reconcile",
// the ok / !ok tail pair
"emit_rebuilt",
"emit_rebuilt",
],
"the perm write prefixes an otherwise ordinary rebuild chain"
);
}
// ---- standalone sweeps ----
#[test]