hyperhive/hive-c0re/src/workers/mcp_sockets.rs

60 lines
2.3 KiB
Rust

//! MCP socket listener reconcile loop.
//!
//! Periodically checks that every running agent container has a bound
//! MCP listener registered in the `Coordinator`. Any agent that is running
//! but whose listener has gone (e.g. after a hive-c0re restart that cleared
//! `/run/hyperhive/agents/`) gets re-registered automatically.
//!
//! Same shape as `agent_sockets::spawn_poll` — a simple 10 s tick loop that
//! converges "agent container running ⇒ MCP listener bound". The self-healing
//! guarantee means no callsite needs to call `register_agent` directly;
//! `lifecycle::ensure_agent_runtime_dir` (called inside `lifecycle::spawn`
//! and `converge_start_preamble`) creates the bind-mount source, and the
//! reconcile loop picks up the listener binding on the next tick.
use std::sync::Arc;
use crate::coordinator::Coordinator;
/// Spawn the MCP socket listener reconcile loop.
///
/// Every 10 s the loop lists running agent containers and calls
/// `register_agent` for any that lack a bound listener. The first tick fires
/// immediately so hive-c0re restarts re-register all running agents without
/// waiting a full interval.
pub fn spawn_poll(coord: Arc<Coordinator>) {
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(10));
loop {
interval.tick().await;
reconcile_once(&coord).await;
}
});
}
async fn reconcile_once(coord: &Arc<Coordinator>) {
let running = match crate::lifecycle::list().await {
Ok(names) => names,
Err(e) => {
tracing::debug!(error = ?e, "mcp_sockets poll: failed to list agents");
return;
}
};
let registered = coord.list_agents();
for container in running {
let Some(name) = container.strip_prefix(crate::lifecycle::AGENT_PREFIX) else {
continue;
};
if !registered.contains(&name.to_owned()) {
if let Err(e) = coord.register_agent(name) {
tracing::warn!(
agent = %name,
error = ?e,
"mcp_sockets poll: register_agent failed"
);
} else {
tracing::debug!(agent = %name, "mcp_sockets poll: registered missing listener");
}
}
}
}