//! `hive-subagent-daemon` binary — spawns nested claude sessions on //! request and serves the `start`/`continue`/`status`/`interrupt` MCP tool //! surface directly over streamable-http on `--http ` — no stdio //! bridge, no separate bin claude has to respawn every turn. The same //! address also carries the subagent-facing `goal_reached`/`need_help` //! route, which is the only place the daemon can learn its own URL from. use std::sync::Arc; use anyhow::Result; use clap::Parser; #[derive(Parser)] #[command( name = "hive-subagent-daemon", about = "claude-subagent runner + MCP daemon" )] struct Cli { /// Serve the MCP tools over streamable-http on this address (e.g. /// `127.0.0.1:8793`). Bind loopback only. #[arg(long)] http: std::net::SocketAddr, } #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_env("RUST_LOG") .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), ) // This is a systemd-managed daemon — stdout always goes to journald, // never a human terminal, and journald doesn't strip ANSI escapes: // they land in victorialogs as raw byte-array spam otherwise. .with_ansi(false) .init(); let cli = Cli::parse(); let todo_socket = hive_subagent_mcp::paths::agent_socket(); tracing::info!( http = %cli.http, todo = %todo_socket.display(), "hive-subagent-daemon starting" ); // The one place the subagent-facing signal URLs can come from: the // address this process was told to listen on. Anything else would be a // guess at the deployment's own port assignment. This is only their // common prefix — each session's actual URL is this plus a token minted // for that session alone, which is what makes a signal's identity a // property of the endpoint rather than of the payload. let signal_base = format!("http://{}{}", cli.http, hive_subagent_mcp::mcp::SIGNAL_PATH); let state = Arc::new(hive_subagent_mcp::session::State::new( todo_socket, signal_base, )); // Serve the MCP tools over streamable-http forever. No background poll // loop to start — unlike the bash daemon's task-file queue, `start`/ // `continue` spawn their subagent's background turn directly from the // tool call itself, nothing to scan for. hive_subagent_mcp::mcp::serve_http(cli.http, state).await }