Skip to content
93 changes: 33 additions & 60 deletions crates/goose-cli/src/session/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) {
Ok(call) => match call.name.to_string().as_str() {
"developer__text_editor" => render_text_editor_request(call, debug),
"developer__shell" => render_shell_request(call, debug),
"dynamic_task__create_task" => render_dynamic_task_request(call, debug),
"subagent" => render_subagent_request(call, debug),
"todo__write" => render_todo_request(call, debug),
_ => render_default_request(call, debug),
},
Expand Down Expand Up @@ -447,69 +447,42 @@ fn render_shell_request(call: &CallToolRequestParam, debug: bool) {
println!();
}

fn render_dynamic_task_request(call: &CallToolRequestParam, debug: bool) {
fn render_subagent_request(call: &CallToolRequestParam, debug: bool) {
print_tool_header(call);

// Print task_parameters array
if let Some(task_parameters) = call
.arguments
.as_ref()
.and_then(|args| args.get("task_parameters"))
.and_then(|v| match v {
Value::Array(arr) => Some(arr),
_ => None,
})
{
println!("{}:", style("task_parameters").dim());
for task_param in task_parameters.iter() {
println!(" -");

if let Some(param_obj) = task_param.as_object() {
for (key, value) in param_obj {
match value {
Value::String(s) => {
// For strings, print the full content without truncation
println!(" {}: {}", style(key).dim(), style(s).green());
}
Value::Array(arr) => {
// For arrays, print each item on its own line
println!(" {}:", style(key).dim());
for item in arr {
if let Value::String(s) = item {
println!(" - {}", style(s).green());
} else if let Value::Object(_) = item {
// For objects in arrays, print them with indentation
print!(" - ");
if let Value::Object(obj) = item {
print_params(&Some(obj.clone()), 3, debug);
}
} else {
println!(
" - {}",
style(format!("{}", item)).green()
);
}
}
}
Value::Object(_) => {
// For objects, print them with proper indentation
println!(" {}:", style(key).dim());
if let Value::Object(obj) = value {
print_params(&Some(obj.clone()), 2, debug);
}
}
_ => {
// For other types (numbers, booleans, null)
println!(
" {}: {}",
style(key).dim(),
style(format!("{}", value)).green()
);
}
}
}
if let Some(args) = &call.arguments {
if let Some(Value::String(subrecipe)) = args.get("subrecipe") {
println!("{}: {}", style("subrecipe").dim(), style(subrecipe).cyan());
}

if let Some(Value::String(instructions)) = args.get("instructions") {
let display = if instructions.len() > 100 && !debug {
safe_truncate(instructions, 100)
} else {
instructions.clone()
};
println!(
"{}: {}",
style("instructions").dim(),
style(display).green()
);
}

if let Some(Value::Object(params)) = args.get("parameters") {
println!("{}:", style("parameters").dim());
print_params(&Some(params.clone()), 1, debug);
}

let skip_keys = ["subrecipe", "instructions", "parameters"];
let mut other_args = serde_json::Map::new();
for (k, v) in args {
if !skip_keys.contains(&k.as_str()) {
other_args.insert(k.clone(), v.clone());
}
}
if !other_args.is_empty() {
print_params(&Some(other_args), 0, debug);
}
}

println!();
Expand Down
139 changes: 25 additions & 114 deletions crates/goose/src/agents/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,12 @@ use crate::agents::extension_manager_extension::MANAGE_EXTENSIONS_TOOL_NAME_COMP
use crate::agents::final_output_tool::{FINAL_OUTPUT_CONTINUATION_MESSAGE, FINAL_OUTPUT_TOOL_NAME};
use crate::agents::platform_tools::PLATFORM_MANAGE_SCHEDULE_TOOL_NAME;
use crate::agents::prompt_manager::PromptManager;
use crate::agents::recipe_tools::dynamic_task_tools::{
create_dynamic_task, create_dynamic_task_tool, DYNAMIC_TASK_TOOL_NAME_PREFIX,
};
use crate::agents::retry::{RetryManager, RetryResult};
use crate::agents::router_tools::ROUTER_LLM_SEARCH_TOOL_NAME;
use crate::agents::sub_recipe_manager::SubRecipeManager;
use crate::agents::subagent_execution_tool::lib::ExecutionMode;
use crate::agents::subagent_execution_tool::subagent_execute_task_tool::{
self, SUBAGENT_EXECUTE_TASK_TOOL_NAME,
};
use crate::agents::subagent_execution_tool::tasks_manager::TasksManager;
use crate::agents::subagent_task_config::TaskConfig;
use crate::agents::subagent_tool::{
create_subagent_tool, handle_subagent_tool, SUBAGENT_TOOL_NAME,
};
use crate::agents::tool_route_manager::ToolRouteManager;
use crate::agents::tool_router_index_manager::ToolRouterIndexManager;
use crate::agents::types::SessionConfig;
Expand Down Expand Up @@ -92,8 +86,7 @@ pub struct Agent {
pub(super) provider: SharedProvider,

pub extension_manager: Arc<ExtensionManager>,
pub(super) sub_recipe_manager: Mutex<SubRecipeManager>,
pub(super) tasks_manager: TasksManager,
pub(super) sub_recipes: Mutex<HashMap<String, SubRecipe>>,
pub(super) final_output_tool: Arc<Mutex<Option<FinalOutputTool>>>,
pub(super) frontend_tools: Mutex<HashMap<String, FrontendTool>>,
pub(super) frontend_instructions: Mutex<Option<String>>,
Expand Down Expand Up @@ -168,8 +161,7 @@ impl Agent {
Self {
provider: provider.clone(),
extension_manager: Arc::new(ExtensionManager::new(provider.clone())),
sub_recipe_manager: Mutex::new(SubRecipeManager::new()),
tasks_manager: TasksManager::new(),
sub_recipes: Mutex::new(HashMap::new()),
final_output_tool: Arc::new(Mutex::new(None)),
frontend_tools: Mutex::new(HashMap::new()),
frontend_instructions: Mutex::new(None),
Expand Down Expand Up @@ -407,9 +399,11 @@ impl Agent {
self.extend_system_prompt(final_output_system_prompt).await;
}

pub async fn add_sub_recipes(&self, sub_recipes: Vec<SubRecipe>) {
let mut sub_recipe_manager = self.sub_recipe_manager.lock().await;
sub_recipe_manager.add_sub_recipe_tools(sub_recipes);
pub async fn add_sub_recipes(&self, sub_recipes_to_add: Vec<SubRecipe>) {
let mut sub_recipes = self.sub_recipes.lock().await;
for sr in sub_recipes_to_add {
sub_recipes.insert(sr.name.clone(), sr);
}
}

pub async fn apply_recipe_components(
Expand Down Expand Up @@ -438,9 +432,9 @@ impl Agent {
cancellation_token: Option<CancellationToken>,
session: &Session,
) -> (String, Result<ToolCallResult, ErrorData>) {
// Prevent subagents from creating other subagents
if session.session_type == crate::session::SessionType::SubAgent
&& (tool_call.name == DYNAMIC_TASK_TOOL_NAME_PREFIX
|| tool_call.name == SUBAGENT_EXECUTE_TASK_TOOL_NAME)
&& tool_call.name == SUBAGENT_TOOL_NAME
{
return (
request_id,
Expand Down Expand Up @@ -486,27 +480,7 @@ impl Agent {
}

debug!("WAITING_TOOL_START: {}", tool_call.name);
let result: ToolCallResult = if self
.sub_recipe_manager
.lock()
.await
.is_sub_recipe_tool(&tool_call.name)
{
let sub_recipe_manager = self.sub_recipe_manager.lock().await;
let arguments = tool_call
.arguments
.clone()
.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,
&session.working_dir,
)
.await
} else if tool_call.name == SUBAGENT_EXECUTE_TASK_TOOL_NAME {
let result: ToolCallResult = if tool_call.name == SUBAGENT_TOOL_NAME {
let provider = match self.provider().await {
Ok(p) => p,
Err(_) => {
Expand All @@ -521,84 +495,24 @@ impl Agent {
}
};

// Get extensions from the agent's runtime state rather than global config
// This ensures subagents inherit extensions that were dynamically enabled by the parent
let extensions = self.get_extension_configs().await;

let task_config =
TaskConfig::new(provider, &session.id, &session.working_dir, extensions);
let sub_recipes = self.sub_recipes.lock().await.clone();

let arguments = match tool_call.arguments.clone() {
Some(args) => Value::Object(args),
None => {
return (
request_id,
Err(ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Tool call arguments are required".to_string(),
None,
)),
);
}
};
let task_ids: Vec<String> = match arguments.get("task_ids") {
Some(v) => match serde_json::from_value(v.clone()) {
Ok(ids) => ids,
Err(_) => {
return (
request_id,
Err(ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Invalid task_ids format".to_string(),
None,
)),
);
}
},
None => {
return (
request_id,
Err(ErrorData::new(
ErrorCode::INVALID_PARAMS,
"task_ids parameter is required".to_string(),
None,
)),
);
}
};

let execution_mode = arguments
.get("execution_mode")
.and_then(|v| serde_json::from_value::<ExecutionMode>(v.clone()).ok())
.unwrap_or(ExecutionMode::Sequential);

subagent_execute_task_tool::run_tasks(
task_ids,
execution_mode,
task_config,
&self.tasks_manager,
cancellation_token,
)
.await
} else if tool_call.name == DYNAMIC_TASK_TOOL_NAME_PREFIX {
// Get loaded extensions for shortname resolution
let loaded_extensions = self
.extension_manager
.list_extensions()
.await
.unwrap_or_default();
let arguments = tool_call
.arguments
.clone()
.map(Value::Object)
.unwrap_or(Value::Object(serde_json::Map::new()));
create_dynamic_task(

handle_subagent_tool(
arguments,
&self.tasks_manager,
loaded_extensions,
&session.working_dir,
task_config,
sub_recipes,
session.working_dir.clone(),
cancellation_token,
)
.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 Expand Up @@ -734,21 +648,18 @@ impl Agent {
.unwrap_or_default();

if extension_name.is_none() || extension_name.as_deref() == Some("platform") {
// Add platform tools
// TODO: migrate the manage schedule tool as well
prefixed_tools.extend([platform_tools::manage_schedule_tool()]);
// Dynamic task tool
prefixed_tools.push(create_dynamic_task_tool());
}

if extension_name.is_none() {
let sub_recipe_manager = self.sub_recipe_manager.lock().await;
prefixed_tools.extend(sub_recipe_manager.sub_recipe_tools.values().cloned());

if let Some(final_output_tool) = self.final_output_tool.lock().await.as_ref() {
prefixed_tools.push(final_output_tool.tool());
}
prefixed_tools.push(subagent_execute_task_tool::create_subagent_execute_task_tool());

// Add the unified subagent tool
let sub_recipes = self.sub_recipes.lock().await;
let sub_recipes_vec: Vec<_> = sub_recipes.values().cloned().collect();
prefixed_tools.push(create_subagent_tool(&sub_recipes_vec));
}

prefixed_tools
Expand Down
3 changes: 1 addition & 2 deletions crates/goose/src/agents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,16 @@ pub mod mcp_client;
pub mod moim;
pub mod platform_tools;
pub mod prompt_manager;
pub mod recipe_tools;
mod reply_parts;
pub mod retry;
mod router_tool_selector;
mod router_tools;
mod schedule_tool;
pub(crate) mod skills_extension;
pub mod sub_recipe_manager;
pub mod subagent_execution_tool;
pub mod subagent_handler;
mod subagent_task_config;
pub mod subagent_tool;
pub(crate) mod todo_extension;
mod tool_execution;
mod tool_route_manager;
Expand Down
4 changes: 2 additions & 2 deletions crates/goose/src/agents/prompt_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use serde_json::Value;
use std::collections::HashMap;

use crate::agents::extension::ExtensionInfo;
use crate::agents::recipe_tools::dynamic_task_tools::should_enabled_subagents;
use crate::agents::router_tools::llm_search_tool_prompt;
use crate::agents::subagent_tool::should_enable_subagents;
use crate::hints::load_hints::{load_hint_files, AGENTS_MD_FILENAME, GOOSE_HINTS_FILENAME};
use crate::{
config::{Config, GooseMode},
Expand Down Expand Up @@ -152,7 +152,7 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> {
extension_tool_limits,
goose_mode,
is_autonomous: goose_mode == GooseMode::Auto,
enable_subagents: should_enabled_subagents(self.model_name.as_str()),
enable_subagents: should_enable_subagents(self.model_name.as_str()),
max_extensions: MAX_EXTENSIONS,
max_tools: MAX_TOOLS,
};
Expand Down
Loading
Loading