From 4f3f9f1a4ac483432cceacc34add9d61d2a911c8 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Sun, 28 Jun 2026 00:58:11 +0200 Subject: [PATCH] fix(daemon): register autospawned agent in store for task routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After autospawn spawns zot, the agent was never registered in the local SQLite store. The scheduler only routes tasks to registered agents — so every task stayed stuck in 'queued' forever. This commit adds a store.register_agent() call right after the spawn succeeds, before the bootstrap RPC prompt. The agent is registered with [shell, freebsd, code] capabilities and the current hostname. Uses try_lock() to avoid blocking the daemon on store contention. --- crates/colibri-daemon/src/socket.rs | 48 ++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/colibri-daemon/src/socket.rs b/crates/colibri-daemon/src/socket.rs index 7ad3d79..a28c4f0 100644 --- a/crates/colibri-daemon/src/socket.rs +++ b/crates/colibri-daemon/src/socket.rs @@ -630,6 +630,46 @@ pub async fn autospawn_agent_if_configured(state: &SharedState) { if resp.ok { info!("autospawn: agent spawned"); + // Resolve agent_id from the spawn response for both registration and RPC. + let agent_id = resp + .data + .as_ref() + .and_then(|d| d.get("agent_id")) + .and_then(|v| v.as_str()) + .map(str::to_string); + + // Register the autospawned agent in the local store so the scheduler + // can route queued tasks to it. Without this, tasks stay stuck in + // "queued" — the scheduler only assigns work to registered agents. + if let Some(ref aid) = agent_id { + let hostname = std::env::var("HOSTNAME").unwrap_or_else(|_| "unknown".to_string()); + match state.store.try_lock() { + Ok(store) => { + if let Err(e) = store.register_agent( + aid, + serde_json::json!(["shell", "freebsd", "code"]), + Some(&hostname), + ) { + warn!(agent_id = %aid, error = %e, "autospawn: failed to register agent in store"); + } else { + info!(agent_id = %aid, hostname = %hostname, "autospawn: agent registered for task work"); + } + } + Err(std::sync::TryLockError::WouldBlock) => { + warn!("autospawn: store locked; agent registration deferred"); + } + Err(std::sync::TryLockError::Poisoned(e)) => { + if let Err(e) = e.into_inner().register_agent( + aid, + serde_json::json!(["shell", "freebsd", "code"]), + Some(&hostname), + ) { + warn!(agent_id = %aid, error = %e, "autospawn: failed to register agent (poisoned store)"); + } + } + } + } + // An RPC agent (zot rpc) idles until it gets a prompt on stdin. Send a // one-time bootstrap so it comes online and emits events — opt-in via // COLIBRI_AUTOSPAWN_RPC_PROMPT so the default boot spends no tokens. @@ -637,14 +677,6 @@ pub async fn autospawn_agent_if_configured(state: &SharedState) { .ok() .filter(|s| !s.trim().is_empty()) { - // Resolve the agent id from the spawn response, then clone the - // RpcSender out and drop the registry guard before the async write. - let agent_id = resp - .data - .as_ref() - .and_then(|d| d.get("agent_id")) - .and_then(|v| v.as_str()) - .map(str::to_string); let sender = agent_id .as_ref() .and_then(|id| state.agents.get(id).and_then(|e| e.value().rpc_sender())); -- 2.45.3