diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index 7f52cdc6..87341af7 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -66,7 +66,7 @@ pub fn agent_config_pending(name: &str, deployed_sha: Option<&str>) -> bool { /// `queue_entry_id` is `Some(id)` when the rebuild was dispatched from /// the `rebuild_queue` worker (lets the function annotate its phase via /// `coord.set_queue_step`) and `None` when called directly (e.g. the -/// root-agent migration nudge in `ensure_root_agent`). +/// manager-migration nudge in `ensure_manager`). pub async fn rebuild_agent( coord: &Arc, name: &str, @@ -156,10 +156,10 @@ pub async fn rebuild_agent( } /// Auto-create the manager container on startup if it isn't already there. -/// hive-c0re manages the manager end-to-end: operators no longer declare -/// `containers.h-ruth` in their host NixOS config. Bypasses the approval -/// queue — manager is required infrastructure. Idempotent. -pub async fn ensure_root_agent(coord: &Arc) -> Result<()> { +/// hive-c0re manages `ruth` end-to-end: operators no +/// longer declare `containers.h-ruth` in their host NixOS config. Bypasses +/// the approval queue — manager is required infrastructure. Idempotent. +pub async fn ensure_manager(coord: &Arc) -> Result<()> { let existing = lifecycle::list().await.unwrap_or_default(); let current_rev = current_flake_rev(&coord.hyperhive_flake); if existing diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index bb9bd98a..d07e38dc 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -23,6 +23,7 @@ use crate::operator_questions::OperatorQuestions; const DASHBOARD_CHANNEL: usize = 256; const AGENT_RUNTIME_ROOT: &str = "/run/hyperhive/agents"; +const MANAGER_RUNTIME_ROOT: &str = "/run/hyperhive/manager"; /// Manager-editable per-agent config repos. Bind-mounted RW into the manager /// container as `/agents//`. Hive-c0re only writes to these on first /// spawn (initial commit); after that it's manager-only. @@ -856,15 +857,12 @@ impl Coordinator { Self::agent_dir(name).join("mcp.sock") } - /// Runtime dir for the manager. Uses the same per-agent subdir layout as - /// sub-agents — the manager is just another agent under - /// `AGENT_RUNTIME_ROOT` with its own subdirectory. pub fn manager_dir() -> PathBuf { - Self::agent_dir(crate::lifecycle::MANAGER_NAME) + PathBuf::from(MANAGER_RUNTIME_ROOT) } pub fn manager_socket_path() -> PathBuf { - Self::socket_path(crate::lifecycle::MANAGER_NAME) + Self::manager_dir().join("mcp.sock") } /// Ensure a runtime dir + (for sub-agents) per-agent socket exists. For @@ -874,7 +872,7 @@ impl Coordinator { /// `/run/hive/mcp.sock` bind that ends up in `set_nspawn_flags`. pub fn ensure_runtime(self: &Arc, name: &str) -> Result { if name == crate::lifecycle::MANAGER_NAME { - let dir = Self::agent_dir(name); + let dir = Self::manager_dir(); std::fs::create_dir_all(&dir) .with_context(|| format!("create manager dir {}", dir.display()))?; return Ok(dir); diff --git a/hive-c0re/src/crash_watch.rs b/hive-c0re/src/crash_watch.rs index 6a7d89dc..ae3ce18e 100644 --- a/hive-c0re/src/crash_watch.rs +++ b/hive-c0re/src/crash_watch.rs @@ -33,10 +33,13 @@ pub fn spawn(coord: Arc) { let mut current_logged_in = HashSet::new(); let mut sub_agents: Vec = Vec::new(); for c in &raw { - let Some(logical) = c.strip_prefix(AGENT_PREFIX) else { + let logical = if c == MANAGER_NAME { + MANAGER_NAME.to_owned() + } else if let Some(n) = c.strip_prefix(AGENT_PREFIX) { + n.to_owned() + } else { continue; }; - let logical = logical.to_owned(); if logical != MANAGER_NAME { sub_agents.push(logical.clone()); } diff --git a/hive-c0re/src/main.rs b/hive-c0re/src/main.rs index 8bbf0850..a41cb326 100644 --- a/hive-c0re/src/main.rs +++ b/hive-c0re/src/main.rs @@ -222,12 +222,12 @@ async fn cmd_serve( if let Err(e) = migrate::run(&coord).await { tracing::warn!(error = ?e, "startup migration failed"); } - // Auto-create the root agent container if it isn't there yet. Block + // Auto-create the manager container if it isn't there yet. Block // on this — without root the system has no manager harness. // Failures are logged but allowed: a broken auto-spawn shouldn't // make the dashboard unreachable for debugging. - if let Err(e) = auto_update::ensure_root_agent(&coord).await { - tracing::warn!(error = ?e, "auto-spawn root agent failed"); + if let Err(e) = auto_update::ensure_manager(&coord).await { + tracing::warn!(error = ?e, "auto-spawn manager failed"); } // Auto-update in the background — don't block service start. // Sub-agent rebuilds can take tens of seconds; we want the admin diff --git a/hive-c0re/src/manager_server.rs b/hive-c0re/src/manager_server.rs index 39bba5d0..8a4ccfc1 100644 --- a/hive-c0re/src/manager_server.rs +++ b/hive-c0re/src/manager_server.rs @@ -189,7 +189,7 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp let id = match coord .approvals .submit_kind( - MANAGER_AGENT, + hive_sh4re::MANAGER_AGENT, hive_sh4re::ApprovalKind::UpdateMetaInputs, &commit_ref, description.as_deref(), @@ -206,7 +206,7 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp tracing::info!(%id, %label, "update_meta_inputs approval queued"); coord.emit_approval_added( id, - MANAGER_AGENT, + hive_sh4re::MANAGER_AGENT, "update_meta_inputs", None, None, @@ -215,10 +215,10 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp ManagerResponse::Ok } ManagerRequest::RequestSchedulePrompt(payload) => { - handle_request_schedule_prompt(coord, MANAGER_AGENT, payload) + handle_request_schedule_prompt(coord, hive_sh4re::MANAGER_AGENT, payload) } ManagerRequest::CancelSchedule { id, targets } => { - handle_cancel_schedule(coord, MANAGER_AGENT, *id, targets.as_deref()) + handle_cancel_schedule(coord, hive_sh4re::MANAGER_AGENT, *id, targets.as_deref()) } ManagerRequest::EditSchedule { id, @@ -230,7 +230,7 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp targets_remove, } => handle_edit_schedule( coord, - MANAGER_AGENT, + hive_sh4re::MANAGER_AGENT, *id, body.clone(), description.clone(), @@ -248,7 +248,7 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp }, }, ManagerRequest::FireScheduleNow { id } => { - handle_fire_schedule_now(coord, MANAGER_AGENT, *id).await + handle_fire_schedule_now(coord, hive_sh4re::MANAGER_AGENT, *id).await } ManagerRequest::GetLogs { agent, lines } => { let n = lines.unwrap_or(50); diff --git a/hive-c0re/src/rebuild_queue.rs b/hive-c0re/src/rebuild_queue.rs index a5238454..0dd306de 100644 --- a/hive-c0re/src/rebuild_queue.rs +++ b/hive-c0re/src/rebuild_queue.rs @@ -781,8 +781,12 @@ pub async fn meta_update_cascade_agents(inputs: &[String]) -> Vec { .unwrap_or_default() .into_iter() .filter_map(|c| { - c.strip_prefix(crate::lifecycle::AGENT_PREFIX) - .map(str::to_owned) + if c == crate::lifecycle::MANAGER_NAME { + Some(crate::lifecycle::MANAGER_NAME.to_owned()) + } else { + c.strip_prefix(crate::lifecycle::AGENT_PREFIX) + .map(str::to_owned) + } }) .collect() } else { diff --git a/hive-c0re/src/reminder_scheduler.rs b/hive-c0re/src/reminder_scheduler.rs index c213882b..58a02d25 100644 --- a/hive-c0re/src/reminder_scheduler.rs +++ b/hive-c0re/src/reminder_scheduler.rs @@ -168,12 +168,17 @@ pub fn write_payload(agent: &str, host_path: &Path, message: &str) -> Result<(), } /// Container-visible state prefix the caller's `file_path` must live -/// under. Every agent sees its state at `/agents//state/` +/// under. Every agent sees its state at `/agents//state/` /// (see `lifecycle::set_nspawn_flags`). Auto-file paths use the same -/// prefix so the round-trip is symmetric. +/// prefix so the round-trip is symmetric. The manager logical name +/// maps to its container name (`root`) per `lifecycle::MANAGER_NAME`. #[must_use] pub fn container_state_prefix(agent: &str) -> String { - format!("/agents/{agent}/state/") + if agent == hive_sh4re::MANAGER_AGENT { + format!("/agents/{}/state/", crate::lifecycle::MANAGER_NAME) + } else { + format!("/agents/{agent}/state/") + } } /// Map an agent-visible container path to the matching host path, diff --git a/hive-c0re/src/scheduled_prompts_worker.rs b/hive-c0re/src/scheduled_prompts_worker.rs index ec0d431b..03440cef 100644 --- a/hive-c0re/src/scheduled_prompts_worker.rs +++ b/hive-c0re/src/scheduled_prompts_worker.rs @@ -190,8 +190,6 @@ fn known_agents(_coord: &Coordinator) -> std::collections::HashSet { // safe (we're not in a `current_thread` runtime). use std::collections::HashSet; let mut out: HashSet = HashSet::new(); - // Manager is always a scheduled-prompt target (fail-safe: include - // it even if `list()` fails so prompts to the manager never silently drop). out.insert(hive_sh4re::MANAGER_AGENT.to_owned()); let containers = tokio::task::block_in_place(|| { tokio::runtime::Handle::current().block_on(crate::lifecycle::list()) @@ -201,8 +199,9 @@ fn known_agents(_coord: &Coordinator) -> std::collections::HashSet { for raw in list { if let Some(name) = raw.strip_prefix(crate::lifecycle::AGENT_PREFIX) { out.insert(name.to_owned()); + } else if raw == crate::lifecycle::MANAGER_NAME { + out.insert(hive_sh4re::MANAGER_AGENT.to_owned()); } - } } Err(e) => { @@ -377,8 +376,9 @@ async fn known_agents_async() -> std::collections::HashSet { for raw in list { if let Some(name) = raw.strip_prefix(crate::lifecycle::AGENT_PREFIX) { out.insert(name.to_owned()); + } else if raw == crate::lifecycle::MANAGER_NAME { + out.insert(hive_sh4re::MANAGER_AGENT.to_owned()); } - } } Err(e) => {