From f849c36630c6f0b6a12049c2cea84bc1e21354eb Mon Sep 17 00:00:00 2001 From: rabi Date: Fri, 20 Feb 2026 10:34:31 +0530 Subject: [PATCH] fix(summon): stop MOIM from telling models to sleep while waiting for tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MOIM hint for running background tasks injected "sleep N to wait" into the conversation, causing some models to call `developer__shell: sleep N` which times out or wastes the entire turn. Replace with guidance to use load() which waits for the task result. Also stop load() from killing async delegate tasks after 300s — return the task to the background and let the model decide whether to wait again or cancel. Change-Id: Idc3e9100d5f487798c10991ec8672dc4b0a75a10 Signed-off-by: rabi --- .../src/agents/platform_extensions/summon.rs | 84 ++++++++----------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/crates/goose/src/agents/platform_extensions/summon.rs b/crates/goose/src/agents/platform_extensions/summon.rs index 91a0b4a3ad55..9ec4f73b6d0b 100644 --- a/crates/goose/src/agents/platform_extensions/summon.rs +++ b/crates/goose/src/agents/platform_extensions/summon.rs @@ -330,10 +330,12 @@ impl SummonClient { "Load knowledge into your current context or discover available sources.\n\n\ Call with no arguments to list all available sources (subrecipes, recipes, skills, agents).\n\ Call with a source name to load its content into your context.\n\ - For background tasks: load(source: \"task_id\", cancel: true) stops and returns output.\n\n\ + For background tasks: load(source: \"task_id\") waits for the task and returns the result.\n\ + To cancel a running task: load(source: \"task_id\", cancel: true) stops and returns output.\n\n\ Examples:\n\ - load() → Lists available sources\n\ - - load(source: \"rust-patterns\") → Loads the rust-patterns skill" + - load(source: \"rust-patterns\") → Loads the rust-patterns skill\n\ + - load(source: \"20260219_1\") → Waits for background task, then returns result" .to_string(), schema.as_object().unwrap().clone(), ) @@ -880,7 +882,7 @@ impl SummonClient { // Wait for the running task to complete, keeping the tool call // alive so notifications (subagent tool calls) stream in real time. - let task = running.remove(task_id).unwrap(); + let mut task = running.remove(task_id).unwrap(); drop(running); let buffered = { @@ -896,45 +898,37 @@ impl SummonClient { } } - let description = task.description.clone(); - let mut handle = task.handle; - - let (output, timed_out) = tokio::select! { - result = &mut handle => { - let s = match result { + tokio::select! { + result = &mut task.handle => { + let output = match result { Ok(Ok(s)) => s, Ok(Err(e)) => format!("Error: {}", e), Err(e) => format!("Task panicked: {}", e), }; - (s, false) + + return Ok(vec![Content::text(format!( + "# Background Task Result: {}\n\n\ + **Task:** {}\n\ + **Status:** ✓ Completed\n\ + **Duration:** {} ({} turns)\n\n\ + ## Output\n\n{}", + task_id, + task.description, + round_duration(task.started_at.elapsed()), + task.turns.load(Ordering::Relaxed), + output + ))]); } _ = tokio::time::sleep(Duration::from_secs(300)) => { - handle.abort(); - ("Task timed out waiting for completion (aborted after 5 min)".to_string(), true) - } - }; + self.background_tasks.lock().await.insert(task_id.to_string(), task); - let duration = task.started_at.elapsed(); - let turns_taken = task.turns.load(Ordering::Relaxed); - let status = if timed_out { - "⏱ Timed out" - } else { - "✓ Completed" - }; - - return Ok(vec![Content::text(format!( - "# Background Task Result: {}\n\n\ - **Task:** {}\n\ - **Status:** {}\n\ - **Duration:** {} ({} turns)\n\n\ - ## Output\n\n{}", - task_id, - description, - status, - round_duration(duration), - turns_taken, - output - ))]); + return Err(format!( + "Task '{task_id}' is still running after waiting 5 min. \ + Use load(source: \"{task_id}\") to wait again, or \ + load(source: \"{task_id}\", cancel: true) to stop." + )); + } + } } Err(format!("Task '{}' not found.", task_id)) @@ -1628,7 +1622,8 @@ impl SummonClient { .insert(task_id.clone(), task); Ok(vec![Content::text(format!( - "Task {} started in background: \"{}\"\nUse load(source: \"{}\") to wait for the result (it will block until complete).", + "Task {} started in background: \"{}\"\n\ + Continue with other work. When you need the result, use load(source: \"{}\").", task_id, description, task_id ))]) } @@ -1714,8 +1709,6 @@ impl McpClientTrait for SummonClient { let mut lines = vec!["Background tasks:".to_string()]; let now = current_epoch_millis(); - let mut shortest_elapsed_secs: Option = None; - let mut sorted_running: Vec<_> = running.values().collect(); sorted_running.sort_by_key(|t| &t.id); @@ -1723,10 +1716,6 @@ impl McpClientTrait for SummonClient { let elapsed = task.started_at.elapsed(); let idle_ms = now.saturating_sub(task.last_activity.load(Ordering::Relaxed)); - let elapsed_secs = elapsed.as_secs(); - shortest_elapsed_secs = - Some(shortest_elapsed_secs.map_or(elapsed_secs, |s| s.min(elapsed_secs))); - lines.push(format!( "• {}: \"{}\" - running {}, {} turns, idle {}", task.id, @@ -1757,12 +1746,11 @@ impl McpClientTrait for SummonClient { )); } - if let Some(shortest) = shortest_elapsed_secs { - let sleep_secs = 300u64.saturating_sub(shortest).max(10); - lines.push(format!( - "\n→ sleep {} to wait, or load(source: \"id\", cancel: true) to stop", - sleep_secs - )); + if !running.is_empty() { + lines.push( + "\n→ Use load(source: \"\") to wait for a task, or load(source: \"\", cancel: true) to stop it" + .to_string(), + ); } Some(lines.join("\n"))