diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 38d8fca390e2..0c687345803c 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -454,7 +454,12 @@ impl Agent { .map(Value::Object) .unwrap_or(Value::Object(serde_json::Map::new())); sub_recipe_manager - .dispatch_sub_recipe_tool_call(&tool_call.name, arguments, &self.tasks_manager) + .dispatch_sub_recipe_tool_call( + &tool_call.name, + arguments, + &self.tasks_manager, + &session.working_dir, + ) .await } else if tool_call.name == SUBAGENT_EXECUTE_TASK_TOOL_NAME { let provider = match self.provider().await { @@ -542,7 +547,13 @@ impl Agent { .clone() .map(Value::Object) .unwrap_or(Value::Object(serde_json::Map::new())); - create_dynamic_task(arguments, &self.tasks_manager, loaded_extensions).await + create_dynamic_task( + arguments, + &self.tasks_manager, + loaded_extensions, + &session.working_dir, + ) + .await } else if self.is_frontend_tool(&tool_call.name).await { // For frontend tools, return an error indicating we need frontend execution ToolCallResult::from(Err(ErrorData::new( diff --git a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs index bc61926ac38c..d0dd14a6e974 100644 --- a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs +++ b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs @@ -11,6 +11,7 @@ use crate::agents::subagent_execution_tool::{ use crate::agents::tool_execution::ToolCallResult; use crate::config::GooseMode; use crate::recipe::{Recipe, RecipeBuilder}; +use crate::session::SessionManager; use anyhow::{anyhow, Result}; use rmcp::model::{Content, ErrorCode, ErrorData, Tool, ToolAnnotations}; use rmcp::schemars::{schema_for, JsonSchema}; @@ -276,6 +277,7 @@ pub async fn create_dynamic_task( params: Value, tasks_manager: &TasksManager, loaded_extensions: Vec, + parent_working_dir: &std::path::Path, ) -> ToolCallResult { let task_params_array = extract_task_parameters(¶ms); @@ -299,8 +301,26 @@ pub async fn create_dynamic_task( .and_then(|v| v.as_bool()) .unwrap_or(false); + // Create a session for this task - use its ID as the task ID + let session = match SessionManager::create_session( + parent_working_dir.to_path_buf(), + "Subagent task".to_string(), + crate::session::session_manager::SessionType::SubAgent, + ) + .await + { + Ok(s) => s, + Err(e) => { + return ToolCallResult::from(Err(ErrorData { + code: ErrorCode::INTERNAL_ERROR, + message: Cow::from(format!("Failed to create session: {}", e)), + data: None, + })); + } + }; + let task = Task { - id: uuid::Uuid::new_v4().to_string(), + id: session.id, payload: TaskPayload { recipe, return_last_only, diff --git a/crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs b/crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs index b3f02ff9b400..c84d1e385dd7 100644 --- a/crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs +++ b/crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs @@ -12,6 +12,7 @@ use crate::agents::subagent_execution_tool::tasks_manager::TasksManager; use crate::recipe::build_recipe::build_recipe_from_template; use crate::recipe::local_recipes::load_local_recipe_file; use crate::recipe::{Recipe, RecipeParameter, RecipeParameterRequirement, SubRecipe}; +use crate::session::SessionManager; use super::param_utils::prepare_command_params; @@ -53,15 +54,23 @@ fn extract_task_parameters(params: &Value) -> Vec { .unwrap_or_default() } -fn create_tasks_from_params( +async fn create_tasks_from_params( sub_recipe: &SubRecipe, command_params: &[std::collections::HashMap], + parent_working_dir: &std::path::Path, ) -> Result> { let recipe_file = load_local_recipe_file(&sub_recipe.path) .map_err(|e| anyhow::anyhow!("Failed to load recipe {}: {}", sub_recipe.path, e))?; let mut tasks = Vec::new(); for task_command_param in command_params { + let session = SessionManager::create_session( + parent_working_dir.to_path_buf(), + format!("Subagent: {}", sub_recipe.name), + crate::session::session_manager::SessionType::SubAgent, + ) + .await?; + let recipe = build_recipe_from_template( recipe_file.content.clone(), &recipe_file.parent_dir, @@ -74,7 +83,7 @@ fn create_tasks_from_params( .map_err(|e| anyhow::anyhow!("Failed to build recipe: {}", e))?; let task = Task { - id: uuid::Uuid::new_v4().to_string(), + id: session.id, payload: TaskPayload { recipe, return_last_only: false, @@ -106,10 +115,11 @@ pub async fn create_sub_recipe_task( sub_recipe: &SubRecipe, params: Value, tasks_manager: &TasksManager, + parent_working_dir: &std::path::Path, ) -> Result { let task_params_array = extract_task_parameters(¶ms); let command_params = prepare_command_params(sub_recipe, task_params_array.clone())?; - let tasks = create_tasks_from_params(sub_recipe, &command_params)?; + let tasks = create_tasks_from_params(sub_recipe, &command_params, parent_working_dir).await?; let task_execution_payload = create_task_execution_payload(&tasks, sub_recipe); let tasks_json = serde_json::to_string(&task_execution_payload) diff --git a/crates/goose/src/agents/sub_recipe_manager.rs b/crates/goose/src/agents/sub_recipe_manager.rs index 26cf4eb44183..c8603a009025 100644 --- a/crates/goose/src/agents/sub_recipe_manager.rs +++ b/crates/goose/src/agents/sub_recipe_manager.rs @@ -57,9 +57,10 @@ impl SubRecipeManager { tool_name: &str, params: Value, tasks_manager: &TasksManager, + parent_working_dir: &std::path::Path, ) -> ToolCallResult { let result = self - .call_sub_recipe_tool(tool_name, params, tasks_manager) + .call_sub_recipe_tool(tool_name, params, tasks_manager, parent_working_dir) .await; match result { Ok(call_result) => ToolCallResult::from(Ok(call_result)), @@ -76,6 +77,7 @@ impl SubRecipeManager { tool_name: &str, params: Value, tasks_manager: &TasksManager, + parent_working_dir: &std::path::Path, ) -> Result, ErrorData> { let sub_recipe = self.sub_recipes.get(tool_name).ok_or_else(|| { let sub_recipe_name = tool_name @@ -97,7 +99,7 @@ impl SubRecipeManager { data: None, } })?; - let output = create_sub_recipe_task(sub_recipe, params, tasks_manager) + let output = create_sub_recipe_task(sub_recipe, params, tasks_manager, parent_working_dir) .await .map_err(|e| ErrorData { code: ErrorCode::INTERNAL_ERROR, diff --git a/crates/goose/src/agents/subagent_execution_tool/tasks.rs b/crates/goose/src/agents/subagent_execution_tool/tasks.rs index ceec8f474f77..81269b80dcec 100644 --- a/crates/goose/src/agents/subagent_execution_tool/tasks.rs +++ b/crates/goose/src/agents/subagent_execution_tool/tasks.rs @@ -83,7 +83,7 @@ async fn handle_recipe_task( } tokio::select! { - result = run_complete_subagent_task(recipe, task_config, return_last_only) => { + result = run_complete_subagent_task(recipe, task_config, return_last_only, task.id.clone()) => { result.map(|text| serde_json::json!({"result": text})) .map_err(|e| format!("Recipe execution failed: {}", e)) } diff --git a/crates/goose/src/agents/subagent_handler.rs b/crates/goose/src/agents/subagent_handler.rs index 561ba7962722..0ab0310a7837 100644 --- a/crates/goose/src/agents/subagent_handler.rs +++ b/crates/goose/src/agents/subagent_handler.rs @@ -1,10 +1,8 @@ -use crate::session::session_manager::SessionType; use crate::{ agents::{subagent_task_config::TaskConfig, AgentEvent, SessionConfig}, conversation::{message::Message, Conversation}, execution::manager::AgentManager, recipe::Recipe, - session::SessionManager, }; use anyhow::{anyhow, Result}; use futures::StreamExt; @@ -21,14 +19,17 @@ pub async fn run_complete_subagent_task( recipe: Recipe, task_config: TaskConfig, return_last_only: bool, + session_id: String, ) -> Result { - let (messages, final_output) = get_agent_messages(recipe, task_config).await.map_err(|e| { - ErrorData::new( - ErrorCode::INTERNAL_ERROR, - format!("Failed to execute task: {}", e), - None, - ) - })?; + let (messages, final_output) = get_agent_messages(recipe, task_config, session_id) + .await + .map_err(|e| { + ErrorData::new( + ErrorCode::INTERNAL_ERROR, + format!("Failed to execute task: {}", e), + None, + ) + })?; if let Some(output) = final_output { return Ok(output); @@ -94,7 +95,11 @@ pub async fn run_complete_subagent_task( Ok(response_text) } -fn get_agent_messages(recipe: Recipe, task_config: TaskConfig) -> AgentMessagesFuture { +fn get_agent_messages( + recipe: Recipe, + task_config: TaskConfig, + session_id: String, +) -> AgentMessagesFuture { Box::pin(async move { let text_instruction = recipe .instructions @@ -105,18 +110,9 @@ fn get_agent_messages(recipe: Recipe, task_config: TaskConfig) -> AgentMessagesF let agent_manager = AgentManager::instance() .await .map_err(|e| anyhow!("Failed to create AgentManager: {}", e))?; - let parent_session_id = task_config.parent_session_id; - let working_dir = task_config.parent_working_dir; - let session = SessionManager::create_session( - working_dir.clone(), - format!("Subagent task for: {}", parent_session_id), - SessionType::SubAgent, - ) - .await - .map_err(|e| anyhow!("Failed to create a session for sub agent: {}", e))?; let agent = agent_manager - .get_or_create_agent(session.id.clone()) + .get_or_create_agent(session_id.clone()) .await .map_err(|e| anyhow!("Failed to get sub agent session file path: {}", e))?; @@ -149,13 +145,13 @@ fn get_agent_messages(recipe: Recipe, task_config: TaskConfig) -> AgentMessagesF } } let session_config = SessionConfig { - id: session.id.clone(), + id: session_id.clone(), schedule_id: None, max_turns: task_config.max_turns.map(|v| v as u32), retry_config: recipe.retry, }; - let mut stream = crate::session_context::with_session_id(Some(session.id.clone()), async { + let mut stream = crate::session_context::with_session_id(Some(session_id.clone()), async { agent.reply(user_message, session_config, None).await }) .await diff --git a/crates/goose/tests/dynamic_task_tools_tests.rs b/crates/goose/tests/dynamic_task_tools_tests.rs index 14e349b010ee..f195d8b16662 100644 --- a/crates/goose/tests/dynamic_task_tools_tests.rs +++ b/crates/goose/tests/dynamic_task_tools_tests.rs @@ -105,7 +105,14 @@ mod tests { ] }); - let result = create_dynamic_task(params, &tasks_manager, test_loaded_extensions()).await; + let working_dir = std::path::Path::new("/tmp"); + let result = create_dynamic_task( + params, + &tasks_manager, + test_loaded_extensions(), + working_dir, + ) + .await; // Check that the result is successful by awaiting the future let tool_result = result.result.await; @@ -156,7 +163,14 @@ mod tests { ] }); - let result = create_dynamic_task(params, &tasks_manager, test_loaded_extensions()).await; + let working_dir = std::path::Path::new("/tmp"); + let result = create_dynamic_task( + params, + &tasks_manager, + test_loaded_extensions(), + working_dir, + ) + .await; // Check that the result fails since text_instruction is no longer supported let tool_result = result.result.await; @@ -354,7 +368,14 @@ mod tests { ] }); - let result = create_dynamic_task(params, &tasks_manager, test_loaded_extensions()).await; + let working_dir = std::path::Path::new("/tmp"); + let result = create_dynamic_task( + params, + &tasks_manager, + test_loaded_extensions(), + working_dir, + ) + .await; // Should fail on the invalid task let tool_result = result.result.await;