hive-c0re: let request_init_config spawn a brand-new sub-agent under its requester

This commit is contained in:
damocles 2026-06-21 22:15:41 +02:00 committed by mara
commit b3d002e4a7
8 changed files with 254 additions and 23 deletions

View file

@ -456,6 +456,40 @@ fn require_child(agent: &str, target: &str, action: &str) -> Option<AgentRespons
}
}
/// Topology guard for `request_init_config` / `request_apply_commit`,
/// which may legitimately target a child that does not exist *yet*
/// (spawning a brand-new sub-agent). The caller may act on a
/// `target` that is EITHER already its direct child (re-init / config
/// update of an existing child) OR brand-new (absent from the topology
/// tree — the requester becomes its parent). A name that already
/// belongs to a *different* parent (or is a root agent) is refused so
/// one agent can't hijack another's sub-tree.
///
/// Also re-runs the agent-name format check that `require_child`
/// implicitly provided (a traversal / malformed name could never be a
/// child): a brand-new name now flows straight to `submit_init_config`,
/// which builds filesystem paths from it, so validate before that.
fn require_new_child(agent: &str, target: &str, action: &str) -> Option<AgentResponse> {
if let Some(reason) = crate::dashboard::validate_agent_name(target) {
return Some(AgentResponse::Err {
message: format!("agent `{agent}` cannot {action} `{target}`: {reason}"),
});
}
match crate::topology::read().get(target) {
// brand-new name — requester becomes the parent on approval.
None => None,
// already our direct child — re-init / config update path.
Some(Some(p)) if p == agent => None,
// owned by someone else, or a root agent — refuse.
Some(_) => Some(AgentResponse::Err {
message: format!(
"agent `{agent}` cannot {action} `{target}`: it already exists \
under a different parent in the topology tree"
),
}),
}
}
/// `GetLooseEnds` — resolve the (optionally cross-agent) target then
/// read its loose ends.
fn handle_get_loose_ends(
@ -696,11 +730,11 @@ fn handle_request_init_config(
name: &str,
description: Option<String>,
) -> AgentResponse {
if let Some(err) = require_child(agent, name, "request_init_config for") {
if let Some(err) = require_new_child(agent, name, "request_init_config for") {
return err;
}
tracing::info!(%agent, %name, "agent: request_init_config for child");
match crate::manager_server::submit_init_config(coord, name, description) {
match crate::manager_server::submit_init_config(coord, name, Some(agent), description) {
Ok(_id) => AgentResponse::Ok,
Err(e) => AgentResponse::Err {
message: format!("{e:#}"),
@ -717,7 +751,7 @@ async fn handle_request_apply_commit(
commit_ref: &str,
description: Option<&str>,
) -> AgentResponse {
if let Some(err) = require_child(agent, target_agent, "request_apply_commit for") {
if let Some(err) = require_new_child(agent, target_agent, "request_apply_commit for") {
return err;
}
tracing::info!(%agent, %target_agent, %commit_ref, "agent: request_apply_commit for child");