From 8f94e4379aa22160a778896222b55543e1ea29c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Sat, 16 May 2026 00:27:12 +0200 Subject: [PATCH] lifecycle::spawn through meta after setup_proposed + setup_applied, spawn now syncs the meta flake (one input + one nixosConfiguration per agent) so `--flake /var/lib/hyperhive/meta#` resolves before nixos-container create runs. flake ref switches from applied/#default to meta#; the wrapper modules (identity, HIVE_PORT, HIVE_LABEL, HIVE_DASHBOARD_PORT) now live in the meta flake's mkAgent. new helper agents_for_meta builds the AgentSpec list by enumerating containers + optionally appending a not-yet-present name for the spawn case. spawn keeps its caller signature; rebuild + auto_update get wired up in follow-up commits. --- hive-c0re/src/lifecycle.rs | 55 +++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 06afc27..594bc88 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -139,16 +139,13 @@ async fn port_collision(self_name: &str) -> Option { #[allow(clippy::too_many_arguments)] pub async fn spawn( name: &str, - // hyperhive_flake + dashboard_port are unused now that the meta - // flake owns the wrapper; left here as the caller surface settles - // — meta-module landing will remove them in a follow-up. - _hyperhive_flake: &str, + hyperhive_flake: &str, agent_dir: &Path, proposed_dir: &Path, applied_dir: &Path, claude_dir: &Path, notes_dir: &Path, - _dashboard_port: u16, + dashboard_port: u16, ) -> Result<()> { validate(name)?; if let Some(other) = port_collision(name).await { @@ -161,8 +158,13 @@ pub async fn spawn( setup_applied(applied_dir, Some(proposed_dir), name).await?; ensure_claude_dir(claude_dir)?; ensure_state_dir(notes_dir)?; + // Meta flake gets the new agent's input + nixosConfiguration + // before `nixos-container create` so the `--flake meta#` + // ref resolves. + let agents = agents_after_spawn(name).await?; + crate::meta::sync_agents(hyperhive_flake, dashboard_port, &agents).await?; let container = container_name(name); - let flake_ref = format!("{}#default", applied_dir.display()); + let flake_ref = format!("{}#{name}", crate::meta::meta_dir().display()); run(&["create", &container, "--flake", &flake_ref]).await?; set_nspawn_flags(&container, agent_dir, claude_dir, notes_dir)?; set_resource_limits(&container)?; @@ -170,6 +172,47 @@ pub async fn spawn( run(&["start", &container]).await } +/// Build the `AgentSpec` list for the meta flake from `nixos-container +/// list` + a hypothetical extra name not yet in the list (for spawn +/// where the new agent's container doesn't exist yet). Pass empty +/// `name_to_add` from rebuild paths where the agent is already in the +/// container list. +async fn agents_for_meta(name_to_add: Option<&str>) -> Result> { + let containers = list().await.unwrap_or_default(); + let mut out: Vec = containers + .into_iter() + .filter_map(|c| { + let (name, is_manager) = if c == MANAGER_NAME { + (MANAGER_NAME.to_owned(), true) + } else if let Some(n) = c.strip_prefix(AGENT_PREFIX) { + (n.to_owned(), false) + } else { + return None; + }; + Some(crate::meta::AgentSpec { + port: agent_web_port(&name), + name, + is_manager, + }) + }) + .collect(); + if let Some(extra) = name_to_add + && !out.iter().any(|a| a.name == extra) + { + out.push(crate::meta::AgentSpec { + name: extra.to_owned(), + is_manager: is_manager(extra), + port: agent_web_port(extra), + }); + } + out.sort_by(|a, b| a.name.cmp(&b.name)); + Ok(out) +} + +async fn agents_after_spawn(name: &str) -> Result> { + agents_for_meta(Some(name)).await +} + pub async fn kill(name: &str) -> Result<()> { validate(name)?; let container = container_name(name);