feat(#513): inject HIVE_TOOL_GROUPS from meta tool-groups.json per agent

This commit is contained in:
damocles 2026-06-01 12:17:47 +02:00 committed by mara
commit 816523861c
4 changed files with 129 additions and 5 deletions

View file

@ -141,6 +141,13 @@ pub async fn sync_agents(
if crate::topology::topology_path().exists() {
git(&dir, &["add", "topology.json"]).await?;
}
// Stage tool-groups.json when it exists. Created on first
// `set_groups` call (operator-driven); absent = all agents on
// their role defaults, no file needed. git add is a no-op when
// the file is unchanged.
if crate::tool_groups::tool_groups_path().exists() {
git(&dir, &["add", "tool-groups.json"]).await?;
}
nix(&dir, &["flake", "lock"]).await?;
if std::path::Path::new(&dir).join("flake.lock").exists() {
git(&dir, &["add", "flake.lock"]).await?;
@ -436,7 +443,7 @@ where
let pronouns_escaped = operator_pronouns.replace('\\', "\\\\").replace('"', "\\\"");
let _ = writeln!(
out,
" dashboardPort = {dashboard_port};\n operatorPronouns = \"{pronouns_escaped}\";\n mkAgent = {{ name, isManager, port, parent ? null }}:"
" dashboardPort = {dashboard_port};\n operatorPronouns = \"{pronouns_escaped}\";\n mkAgent = {{ name, isManager, port, parent ? null, toolGroups ? null }}:"
);
out.push_str(
r#" let
@ -446,6 +453,7 @@ where
input = inputs."agent-${name}";
service = if isManager then "hive-m1nd" else "hive-ag3nt";
parentEnv = if parent == null then {} else { HIVE_PARENT = parent; };
toolGroupsEnv = if toolGroups == null then {} else { HIVE_TOOL_GROUPS = toolGroups; };
in
base.extendModules {
modules = [
@ -477,7 +485,7 @@ where
HIVE_LABEL = name;
HYPERHIVE_STATE_DIR = "/agents/${name}/state";
};
systemd.services.${service}.environment = parentEnv // {
systemd.services.${service}.environment = parentEnv // toolGroupsEnv // {
HIVE_PORT = toString port;
HIVE_LABEL = name;
HIVE_DASHBOARD_PORT = toString dashboardPort;
@ -527,19 +535,32 @@ where
// (every container at root). `meta::sync_agents` seeds the file
// on first run with manager as root + everyone else under manager.
let topology = crate::topology::read();
let tool_groups_map = crate::tool_groups::read();
for spec in agents {
let parent_attr = topology
.get(&spec.name)
.and_then(|p| p.as_ref())
.map_or_else(|| "null".to_owned(), |p| format!("\"{p}\""));
// Emit `toolGroups = "group1,group2"` when the operator has
// explicitly configured groups for this agent. Absent entry = null
// = harness falls back to its role default (no env var emitted,
// no rebuild cascade for agents whose groups haven't changed).
let groups = tool_groups_map.get(&spec.name).cloned().unwrap_or_default();
let tool_groups_attr = if groups.is_empty() {
"null".to_owned()
} else {
let joined = groups.join(",");
format!("\"{joined}\"")
};
let _ = writeln!(
out,
" {} = mkAgent {{ name = \"{}\"; isManager = {}; port = {}; parent = {}; }};",
" {} = mkAgent {{ name = \"{}\"; isManager = {}; port = {}; parent = {}; toolGroups = {}; }};",
spec.name,
spec.name,
if spec.is_manager { "true" } else { "false" },
spec.port,
parent_attr,
tool_groups_attr,
);
}
out.push_str(" };\n };\n}\n");