From 8659c14021477f60a7eee433a0f892b25578969d Mon Sep 17 00:00:00 2001 From: jeffa-block <233853744+jeffa-block@users.noreply.github.com> Date: Wed, 17 Jun 2026 01:21:29 +1000 Subject: [PATCH] feat(summon): add context parameter for delegate tasks (#9518) Signed-off-by: Douwe M Osinga Co-authored-by: Douwe M Osinga --- .../src/agents/platform_extensions/summon.rs | 61 ++++++++++++++++++- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/crates/goose/src/agents/platform_extensions/summon.rs b/crates/goose/src/agents/platform_extensions/summon.rs index 4307f1200..6c44d64f0 100644 --- a/crates/goose/src/agents/platform_extensions/summon.rs +++ b/crates/goose/src/agents/platform_extensions/summon.rs @@ -58,6 +58,7 @@ pub struct DelegateParams { pub model: Option, pub temperature: Option, pub max_turns: Option, + pub context: Option, pub working_dir: Option, #[serde(default)] pub r#async: bool, @@ -296,6 +297,14 @@ pub fn discover_filesystem_sources(working_dir: &Path) -> Vec { sources } +fn build_instructions_with_context(context: &str, instructions: &str) -> String { + let mut result = format!("# Reference Context\n\n{}", context); + if !instructions.is_empty() { + result.push_str(&format!("\n\n# Task Instructions\n\n{}", instructions)); + } + result +} + fn build_subagent_instructions(session: Option<&crate::session::Session>) -> String { let Some(session) = session else { return String::new(); @@ -552,6 +561,10 @@ impl SummonClient { "minimum": 1, "description": "Maximum turns for this delegate. Overrides recipe settings.max_turns and GOOSE_SUBAGENT_MAX_TURNS." }, + "context": { + "type": "string", + "description": "Reference context to inject into the delegate's system prompt. Use for background information, file contents, or constraints the delegate needs but that aren't part of the task instructions." + }, "working_dir": { "type": "string", "description": "Working directory for the delegate. Must be within the parent session's working directory. Defaults to the parent's working directory." @@ -1232,12 +1245,19 @@ impl SummonClient { session_id: &str, working_dir: &Path, ) -> Result { - if let Some(source_name) = ¶ms.source { + let mut recipe = if let Some(source_name) = ¶ms.source { self.build_source_recipe(source_name, params, session_id, working_dir) - .await + .await? } else { - self.build_adhoc_recipe(params) + self.build_adhoc_recipe(params)? + }; + + if let Some(ref context) = params.context { + let existing = recipe.instructions.unwrap_or_default(); + recipe.instructions = Some(build_instructions_with_context(context, &existing)); } + + Ok(recipe) } fn build_adhoc_recipe(&self, params: &DelegateParams) -> Result { @@ -2255,6 +2275,41 @@ You review code."#; ); } + #[tokio::test] + async fn test_context_injected_into_adhoc_recipe() { + let temp_dir = TempDir::new().unwrap(); + let client = SummonClient::new(create_test_context()).unwrap(); + + let params = DelegateParams { + instructions: Some("do the task".to_string()), + context: Some("background info".to_string()), + ..Default::default() + }; + + let recipe = client + .build_delegate_recipe(¶ms, "test", temp_dir.path()) + .await + .unwrap(); + + assert_eq!( + recipe.instructions.as_deref(), + Some("# Reference Context\n\nbackground info") + ); + assert_eq!(recipe.prompt.as_deref(), Some("do the task")); + } + + #[test] + fn test_build_instructions_with_context_wraps_existing_instructions() { + assert_eq!( + build_instructions_with_context("background info", "Run deploy steps"), + "# Reference Context\n\nbackground info\n\n# Task Instructions\n\nRun deploy steps" + ); + assert_eq!( + build_instructions_with_context("background info", ""), + "# Reference Context\n\nbackground info" + ); + } + #[test] fn test_validate_delegate_params_rejects_zero_max_turns() { let context = create_test_context();