Skip to content
15 changes: 13 additions & 2 deletions crates/goose/src/agents/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
22 changes: 21 additions & 1 deletion crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -276,6 +277,7 @@ pub async fn create_dynamic_task(
params: Value,
tasks_manager: &TasksManager,
loaded_extensions: Vec<String>,
parent_working_dir: &std::path::Path,
) -> ToolCallResult {
let task_params_array = extract_task_parameters(&params);

Expand All @@ -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,
Expand Down
16 changes: 13 additions & 3 deletions crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -53,15 +54,23 @@ fn extract_task_parameters(params: &Value) -> Vec<Value> {
.unwrap_or_default()
}

fn create_tasks_from_params(
async fn create_tasks_from_params(
sub_recipe: &SubRecipe,
command_params: &[std::collections::HashMap<String, String>],
parent_working_dir: &std::path::Path,
) -> Result<Vec<Task>> {
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,
Expand All @@ -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,
Expand Down Expand Up @@ -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<String> {
let task_params_array = extract_task_parameters(&params);
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)
Expand Down
6 changes: 4 additions & 2 deletions crates/goose/src/agents/sub_recipe_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand All @@ -76,6 +77,7 @@ impl SubRecipeManager {
tool_name: &str,
params: Value,
tasks_manager: &TasksManager,
parent_working_dir: &std::path::Path,
) -> Result<Vec<Content>, ErrorData> {
let sub_recipe = self.sub_recipes.get(tool_name).ok_or_else(|| {
let sub_recipe_name = tool_name
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/src/agents/subagent_execution_tool/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
40 changes: 18 additions & 22 deletions crates/goose/src/agents/subagent_handler.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,14 +19,17 @@ pub async fn run_complete_subagent_task(
recipe: Recipe,
task_config: TaskConfig,
return_last_only: bool,
session_id: String,
) -> Result<String, anyhow::Error> {
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);
Expand Down Expand Up @@ -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
Expand All @@ -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))?;

Expand Down Expand Up @@ -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
Expand Down
27 changes: 24 additions & 3 deletions crates/goose/tests/dynamic_task_tools_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down