diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 79ee8df1a..94dd89ce8 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -953,7 +953,16 @@ pub fn start_managed_agent_process( // Scalar PIDs are migration-only and never establish pair liveness. record.runtime_pid = None; - let mut process = spawn_agent_child(app, record, &key.relay_url, false, owner_hex)?; + // Lazy, matching manual start, restart, reconcile, and restore (see the + // F1 note in restore.rs). This was the last eager call site. Eager init + // ran the full serial pool spawn BEFORE the harness connected to the + // relay, so create/start of a slow-starting agent (e.g. a gateway-backed + // harness at several seconds per worker) took minutes — and a mention + // sent in that window predated the startup watermark and could be missed + // permanently. Lazy connects/subscribes first, queues accepted work, and + // initializes the pool in the cancellable wake task on first flushable + // work. + let mut process = spawn_agent_child(app, record, &key.relay_url, true, owner_hex)?; let now = now_iso(); let receipt = super::ManagedAgentRuntimeReceipt { key: key.clone(), diff --git a/desktop/src-tauri/src/managed_agents/runtime/tests.rs b/desktop/src-tauri/src/managed_agents/runtime/tests.rs index 55fa8efba..1f2c9de8d 100644 --- a/desktop/src-tauri/src/managed_agents/runtime/tests.rs +++ b/desktop/src-tauri/src/managed_agents/runtime/tests.rs @@ -1052,3 +1052,73 @@ fn restart_eligible_false_when_orphan_has_no_drift() { fn restart_eligible_false_when_non_orphan_has_no_drift() { assert!(!super::restart_eligible(false, false, false)); } + +// ── every spawn_agent_child call site is lazy ──────────────────────── +// +// The eager path (`lazy=false` → BUZZ_ACP_LAZY_POOL=false) makes buzz-acp +// serially initialize the FULL worker pool before connecting to the relay. +// For a slow-starting harness that is minutes of startup, and any mention +// sent in that window predates the startup watermark and can be missed +// permanently. Every Desktop flow (create/start, manual start, restart, +// reconcile, restore) must therefore spawn lazy: relay connects first, +// accepted work queues, and the pool initializes in the cancellable wake +// task. +// +// `spawn_agent_child` spawns a real OS process, so this contract is pinned +// at the source level: no call site may pass a literal `false` for the +// `lazy` parameter. If a new call site legitimately needs eager init, +// it must carry a documented exemption here. +#[test] +fn no_spawn_agent_child_call_site_is_eager() { + let sources = [ + ( + "runtime.rs", + include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/managed_agents/runtime.rs" + )), + ), + ( + "runtime_commands.rs", + include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/managed_agents/runtime_commands.rs" + )), + ), + ( + "restore.rs", + include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/managed_agents/restore.rs" + )), + ), + ]; + for (name, source) in sources { + for (idx, line) in source.lines().enumerate() { + let trimmed = line.trim(); + // Skip comments; match only argument-position literal `false` + // in a spawn_agent_child call. The call spans multiple lines in + // restore.rs, so scan a small window after the call token. + if trimmed.starts_with("//") { + continue; + } + if trimmed.contains("spawn_agent_child(") { + let window: String = source + .lines() + .skip(idx) + .take(8) + .collect::>() + .join("\n"); + // The lazy flag is the 4th argument; a literal `false` in the + // window is the eager smell this test exists to catch. + assert!( + !window.contains("false"), + "{name}:{}: spawn_agent_child call site appears to pass \ + lazy=false (eager pool init). All Desktop spawns must be \ + lazy — see this test's doc comment. Call window:\n{window}", + idx + 1 + ); + } + } + } +}