mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
Fix: Subagent tool can execute existing tasks by ID
The `subagent` tool now accepts an optional `task_id` parameter. If a `task_id` is provided, the tool will execute the existing task from the `TasksManager` instead of creating a new one. This resolves a regression where tasks created by file-based sub-recipes were orphaned and could not be executed. - Added `task_id: Option<String>` to `SubagentParams`. - Updated `create_subagent_tool` documentation. - Modified `impl TryFrom<SubagentParams> for Recipe` to allow partial recipe creation if `task_id` is present, providing a dummy instruction if needed to satisfy the builder. - Updated `run_subagent_tool` to prioritize execution of existing tasks by `task_id` if provided, otherwise proceeds with new task creation. - Added `subagent_tool_test.rs` to verify new functionality.
This commit is contained in:
@@ -3,6 +3,8 @@ pub mod lib;
|
||||
pub mod notification_events;
|
||||
|
||||
pub mod subagent_tool;
|
||||
#[cfg(test)]
|
||||
mod subagent_tool_test;
|
||||
pub mod task_execution_tracker;
|
||||
pub mod task_types;
|
||||
pub mod tasks;
|
||||
|
||||
@@ -21,6 +21,10 @@ pub const SUBAGENT_TOOL_NAME: &str = "subagent";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct SubagentParams {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub task_id: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub instructions: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -106,7 +110,8 @@ pub fn create_subagent_tool() -> Tool {
|
||||
"Delegate a task to an autonomous subagent. Returns the result immediately.
|
||||
Use this tool to offload complex work (research, coding, critique) to a specialized agent.
|
||||
Defaults to 'return_last_only=true' for concise results.
|
||||
Accepts standard recipe parameters like 'extensions', 'settings', 'retry', etc."
|
||||
Accepts standard recipe parameters like 'extensions', 'settings', 'retry', etc.
|
||||
Can also be used to execute an existing task by providing 'task_id' (e.g. from a sub-recipe)."
|
||||
.to_string(),
|
||||
input_schema,
|
||||
)
|
||||
@@ -194,8 +199,10 @@ impl TryFrom<SubagentParams> for Recipe {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(params: SubagentParams) -> Result<Self, Self::Error> {
|
||||
if params.instructions.is_none() && params.prompt.is_none() {
|
||||
return Err(anyhow!("Either 'instructions' or 'prompt' is required"));
|
||||
if params.task_id.is_none() && params.instructions.is_none() && params.prompt.is_none() {
|
||||
return Err(anyhow!(
|
||||
"Either 'instructions' or 'prompt' is required unless 'task_id' is provided"
|
||||
));
|
||||
}
|
||||
|
||||
let mut builder = Recipe::builder()
|
||||
@@ -207,6 +214,11 @@ impl TryFrom<SubagentParams> for Recipe {
|
||||
.unwrap_or_else(|| "Inline recipe task".to_string()),
|
||||
);
|
||||
|
||||
// Allow building partial recipe if we are just executing a task ID
|
||||
if params.task_id.is_some() && params.instructions.is_none() && params.prompt.is_none() {
|
||||
builder = builder.instructions("Executing existing task".to_string());
|
||||
}
|
||||
|
||||
if let Some(inst) = params.instructions {
|
||||
builder = builder.instructions(inst);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
use crate::agents::subagent_execution_tool::subagent_tool::SubagentParams;
|
||||
use crate::recipe::Recipe;
|
||||
|
||||
#[test]
|
||||
fn test_subagent_params_schema() {
|
||||
let schema = rmcp::schemars::schema_for!(SubagentParams);
|
||||
let json = serde_json::to_value(&schema).unwrap();
|
||||
let properties = json.get("properties").unwrap().as_object().unwrap();
|
||||
|
||||
// Check for task_id
|
||||
assert!(properties.contains_key("task_id"));
|
||||
|
||||
// Check for instructions
|
||||
assert!(properties.contains_key("instructions"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from_params_with_task_id() {
|
||||
let params = SubagentParams {
|
||||
task_id: Some("task-123".to_string()),
|
||||
instructions: None,
|
||||
prompt: None,
|
||||
subagent_type: None,
|
||||
title: None,
|
||||
description: None,
|
||||
version: None,
|
||||
extensions: None,
|
||||
settings: None,
|
||||
activities: None,
|
||||
author: None,
|
||||
parameters: None,
|
||||
response: None,
|
||||
sub_recipes: None,
|
||||
retry: None,
|
||||
return_last_only: true,
|
||||
};
|
||||
|
||||
// Should succeed because task_id is present, even without instructions
|
||||
let recipe = Recipe::try_from(params).unwrap();
|
||||
assert_eq!(recipe.instructions, Some("Executing existing task".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from_params_missing_instructions() {
|
||||
let params = SubagentParams {
|
||||
task_id: None,
|
||||
instructions: None,
|
||||
prompt: None,
|
||||
subagent_type: None,
|
||||
title: None,
|
||||
description: None,
|
||||
version: None,
|
||||
extensions: None,
|
||||
settings: None,
|
||||
activities: None,
|
||||
author: None,
|
||||
parameters: None,
|
||||
response: None,
|
||||
sub_recipes: None,
|
||||
retry: None,
|
||||
return_last_only: true,
|
||||
};
|
||||
|
||||
// Should fail because neither task_id nor instructions are present
|
||||
assert!(Recipe::try_from(params).is_err());
|
||||
}
|
||||
Reference in New Issue
Block a user