diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 2e9aedfb1423..f4099437a484 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -534,8 +534,16 @@ impl Agent { }; let extensions = self.get_extension_configs().await; + + let max_turns_from_recipe = session + .recipe + .as_ref() + .and_then(|r| r.settings.as_ref()) + .and_then(|s| s.max_turns); + let task_config = - TaskConfig::new(provider, &session.id, &session.working_dir, extensions); + TaskConfig::new(provider, &session.id, &session.working_dir, extensions) + .with_max_turns(max_turns_from_recipe); let sub_recipes = self.sub_recipes.lock().await.clone(); let arguments = tool_call @@ -1833,6 +1841,7 @@ impl Agent { goose_provider: Some(provider_name.clone()), goose_model: Some(model_name.clone()), temperature: Some(model_config.temperature.unwrap_or(0.0)), + max_turns: None, }; tracing::debug!( diff --git a/crates/goose/src/agents/subagent_task_config.rs b/crates/goose/src/agents/subagent_task_config.rs index 01c955d0c01c..f25c0ef104ca 100644 --- a/crates/goose/src/agents/subagent_task_config.rs +++ b/crates/goose/src/agents/subagent_task_config.rs @@ -53,4 +53,11 @@ impl TaskConfig { ), } } + + pub fn with_max_turns(mut self, max_turns: Option) -> Self { + if let Some(turns) = max_turns { + self.max_turns = Some(turns); + } + self + } } diff --git a/crates/goose/src/agents/subagent_tool.rs b/crates/goose/src/agents/subagent_tool.rs index 8df8b9a457e2..a04373620779 100644 --- a/crates/goose/src/agents/subagent_tool.rs +++ b/crates/goose/src/agents/subagent_tool.rs @@ -51,6 +51,7 @@ pub struct SubagentSettings { pub provider: Option, pub model: Option, pub temperature: Option, + pub max_turns: Option, } pub fn create_subagent_tool(sub_recipes: &[SubRecipe]) -> Tool { @@ -82,9 +83,10 @@ pub fn create_subagent_tool(sub_recipes: &[SubRecipe]) -> Tool { "properties": { "provider": {"type": "string", "description": "Override LLM provider"}, "model": {"type": "string", "description": "Override model"}, - "temperature": {"type": "number", "description": "Override temperature"} + "temperature": {"type": "number", "description": "Override temperature"}, + "max_turns": {"type": "number", "description": "Override max turns"} }, - "description": "Override model/provider settings." + "description": "Override model/provider/settings." }, "summary": { "type": "boolean", @@ -392,6 +394,10 @@ async fn apply_settings_overrides( params: &SubagentParams, ) -> Result { if let Some(settings) = ¶ms.settings { + if let Some(max_turns) = settings.max_turns { + task_config.max_turns = Some(max_turns); + } + if settings.provider.is_some() || settings.model.is_some() || settings.temperature.is_some() { let provider_name = settings diff --git a/crates/goose/src/recipe/mod.rs b/crates/goose/src/recipe/mod.rs index 57684e92eab8..1dd3cb12fba1 100644 --- a/crates/goose/src/recipe/mod.rs +++ b/crates/goose/src/recipe/mod.rs @@ -104,6 +104,9 @@ pub struct Settings { #[serde(skip_serializing_if = "Option::is_none")] pub temperature: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub max_turns: Option, } #[derive(Serialize, Deserialize, Debug, Clone, ToSchema)] diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 23216e15b234..0490aa744009 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -6087,6 +6087,11 @@ "type": "string", "nullable": true }, + "max_turns": { + "type": "integer", + "nullable": true, + "minimum": 0 + }, "temperature": { "type": "number", "format": "float", diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index f9507b27cec3..cdbf11f3d149 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -987,6 +987,7 @@ export type SetSlashCommandRequest = { export type Settings = { goose_model?: string | null; goose_provider?: string | null; + max_turns?: number | null; temperature?: number | null; };