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#<name>` resolves before
nixos-container create runs. flake ref switches from
applied/<n>#default to meta#<name>; 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.
This commit is contained in:
müde 2026-05-16 00:27:12 +02:00
parent c42ad1330c
commit 8f94e4379a

View file

@ -139,16 +139,13 @@ async fn port_collision(self_name: &str) -> Option<String> {
#[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#<name>`
// 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<Vec<crate::meta::AgentSpec>> {
let containers = list().await.unwrap_or_default();
let mut out: Vec<crate::meta::AgentSpec> = 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<Vec<crate::meta::AgentSpec>> {
agents_for_meta(Some(name)).await
}
pub async fn kill(name: &str) -> Result<()> {
validate(name)?;
let container = container_name(name);