Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4b69401
feat: Add summon extension replacing subagent tool and skills extension
tlongwell-block Feb 3, 2026
c071170
fix(summon): add meaningful prompts for skill/agent delegation
tlongwell-block Feb 4, 2026
e9b2d62
feat: add unprefixed tools support for platform extensions
tlongwell-block Feb 4, 2026
db919e7
chore: remove non-doc comments from summon extension
tlongwell-block Feb 4, 2026
2a28615
refactor(summon): restore builtin skills as bundled files
tlongwell-block Feb 4, 2026
994b902
fix: resolve tool prefix lookup and code duplication issues
tlongwell-block Feb 4, 2026
17d064d
Update goose-self-test.yaml for new load/delegate tools
tlongwell-block Feb 4, 2026
3312957
feat(summon): persist async delegate results for retrieval via load()
tlongwell-block Feb 4, 2026
44ee9c1
refactor: remove self-obvious comments and consolidate source discovery
tlongwell-block Feb 4, 2026
d526616
refactor: remove self-obvious comments from extension_manager and sum…
tlongwell-block Feb 4, 2026
314a687
docs: clarify delegate parallel execution requires async flag
tlongwell-block Feb 4, 2026
05b8639
refactor: remove model shorthand translation from summon extension
tlongwell-block Feb 4, 2026
0a934c3
refactor: use session ID as async task identifier
tlongwell-block Feb 4, 2026
0e8f656
Merge origin/main into summon_extension
tlongwell-block Feb 4, 2026
bbfa545
chore: restore McpApps files formatting from origin/main
tlongwell-block Feb 4, 2026
9b92835
feat(summon): add cancellation support for async delegates
tlongwell-block Feb 5, 2026
88f8ef4
revert to 25 default subagent turns
tlongwell-block Feb 5, 2026
2bbfed0
fix(summon): address PR review feedback
tlongwell-block Feb 5, 2026
418a2fa
Merge remote-tracking branch 'origin/main' into summon_extension
tlongwell-block Feb 5, 2026
382f2ec
fix: add write mutex to SessionStorage to prevent SQLite lock contention
tlongwell-block Feb 5, 2026
e2dcc02
tool description to include subrecipes
tlongwell-block Feb 5, 2026
2a640bb
fix: support unprefixed tools in code_execution extension
tlongwell-block Feb 5, 2026
1062d04
fix: enable subrecipes discovery in CLI and show parameters in descri…
tlongwell-block Feb 5, 2026
f1483ef
Auto-inject summon extension for recipes with sub_recipes
tlongwell-block Feb 5, 2026
ed43363
feat(summon): improve delegate tool description with effective delega…
tlongwell-block Feb 6, 2026
dc9b0aa
fix(summon): prioritize locality over type in source discovery
tlongwell-block Feb 6, 2026
dc6efcb
fix(tests): strip tool_meta from replay hash computation
tlongwell-block Feb 9, 2026
0332ddc
Merge remote-tracking branch 'origin/main' into summon_extension
tlongwell-block Feb 9, 2026
1325117
fix: add missing disable_session_naming arg to AgentConfig::new calls
tlongwell-block Feb 9, 2026
da160a3
ci: add gpt-3.5-turbo to allowed failures in smoke tests
tlongwell-block Feb 9, 2026
e2c51bd
changes per review. first class extension check. change client_name -…
tlongwell-block Feb 9, 2026
01cad15
Merge remote-tracking branch 'origin/main' into summon_extension
tlongwell-block Feb 9, 2026
4ce7037
merge main, add unprefixed_tools to tom extension
tlongwell-block Feb 9, 2026
47349ca
allow google:gemini-2.5-flash to fail (flaky in code-exec mode)
tlongwell-block Feb 9, 2026
e688e47
fix(session): raise SQLite busy_timeout to 30s, remove application-le…
tlongwell-block Feb 10, 2026
42f548a
Merge remote-tracking branch 'origin/main' into summon_extension
tlongwell-block Feb 10, 2026
ed76afb
refactor: extract goose_extension meta key into constant
tlongwell-block Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ result

# Goose self-test artifacts
gooseselftest/
.tasks/
10 changes: 8 additions & 2 deletions crates/goose-cli/src/recipes/extract_from_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ mod tests {
input_config.additional_system_prompt,
Some("test_instructions my_value".to_string())
);
assert!(recipe.extensions.is_none());
assert!(recipe
.extensions
.as_ref()
.is_none_or(|e| e.iter().all(|ext| ext.name() == "summon")));

assert!(settings.is_some());
let settings = settings.unwrap();
Expand Down Expand Up @@ -162,7 +165,10 @@ mod tests {
input_config.additional_system_prompt,
Some("test_instructions my_value".to_string())
);
assert!(recipe.extensions.is_none());
assert!(recipe
.extensions
.as_ref()
.is_none_or(|e| e.iter().all(|ext| ext.name() == "summon")));

assert!(settings.is_some());
let settings = settings.unwrap();
Expand Down
17 changes: 12 additions & 5 deletions crates/goose-cli/src/session/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,11 +600,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
let recipe = session_config.recipe.as_ref();

agent
.apply_recipe_components(
recipe.and_then(|r| r.sub_recipes.clone()),
recipe.and_then(|r| r.response.clone()),
true,
)
.apply_recipe_components(recipe.and_then(|r| r.response.clone()), true)
.await;

let new_provider = match create(&resolved.provider_name, resolved.model_config).await {
Expand Down Expand Up @@ -643,6 +639,17 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
process::exit(1);
});

if let Some(recipe) = session_config.recipe.clone() {
if let Err(e) = session_manager
.update(&session_id)
.recipe(Some(recipe))
.apply()
.await
{
tracing::warn!("Failed to store recipe on session: {}", e);
}
}

if session_config.resume {
handle_resumed_session_workdir(&agent, &session_id, session_config.interactive).await;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ use tokio_util::task::AbortOnDropHandle;
pub use self::export::message_to_markdown;
pub use builder::{build_session, SessionBuilderConfig};
use console::Color;
use goose::agents::subagent_handler::SUBAGENT_TOOL_REQUEST_TYPE;
use goose::agents::AgentEvent;
use goose::agents::SUBAGENT_TOOL_REQUEST_TYPE;
use goose::permission::permission_confirmation::PrincipalType;
use goose::permission::Permission;
use goose::permission::PermissionConfirmation;
Expand Down
13 changes: 7 additions & 6 deletions crates/goose-cli/src/session/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,9 @@ 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),
"code_execution__execute" => render_execute_code_request(call, debug),
"subagent" => render_subagent_request(call, debug),
"execute" | "execute_code" => render_execute_code_request(call, debug),
"delegate" => render_delegate_request(call, debug),
"subagent" => render_delegate_request(call, debug),
"todo__write" => render_todo_request(call, debug),
_ => render_default_request(call, debug),
},
Expand Down Expand Up @@ -555,12 +556,12 @@ fn render_execute_code_request(call: &CallToolRequestParams, debug: bool) {
println!();
}

fn render_subagent_request(call: &CallToolRequestParams, debug: bool) {
fn render_delegate_request(call: &CallToolRequestParams, debug: bool) {
print_tool_header(call);

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(source)) = args.get("source") {
println!("{}: {}", style("source").dim(), style(source).cyan());
}

if let Some(Value::String(instructions)) = args.get("instructions") {
Expand All @@ -581,7 +582,7 @@ fn render_subagent_request(call: &CallToolRequestParams, debug: bool) {
print_params(&Some(params.clone()), 1, debug);
}

let skip_keys = ["subrecipe", "instructions", "parameters"];
let skip_keys = ["source", "instructions", "parameters"];
let mut other_args = serde_json::Map::new();
for (k, v) in args {
if !skip_keys.contains(&k.as_str()) {
Expand Down
6 changes: 1 addition & 5 deletions crates/goose-server/src/routes/recipe_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,7 @@ pub async fn apply_recipe_to_agent(
include_final_output_tool: bool,
) -> Option<String> {
agent
.apply_recipe_components(
recipe.sub_recipes.clone(),
recipe.response.clone(),
include_final_output_tool,
)
.apply_recipe_components(recipe.response.clone(), include_final_output_tool)
.await;

recipe.instructions.as_ref().map(|instructions| {
Expand Down
109 changes: 3 additions & 106 deletions crates/goose/src/agents/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ use crate::agents::final_output_tool::{FINAL_OUTPUT_CONTINUATION_MESSAGE, FINAL_
use crate::agents::platform_tools::PLATFORM_MANAGE_SCHEDULE_TOOL_NAME;
use crate::agents::prompt_manager::PromptManager;
use crate::agents::retry::{RetryManager, RetryResult};
use crate::agents::subagent_task_config::TaskConfig;
use crate::agents::subagent_tool::{
create_subagent_tool, handle_subagent_tool, SUBAGENT_TOOL_NAME,
};
use crate::agents::types::{FrontendTool, SessionConfig, SharedProvider, ToolResultReceiver};
use crate::config::permission::PermissionManager;
use crate::config::{get_enabled_extensions, Config, GooseMode};
Expand All @@ -42,11 +38,11 @@ use crate::permission::permission_judge::PermissionCheckResult;
use crate::permission::PermissionConfirmation;
use crate::providers::base::Provider;
use crate::providers::errors::ProviderError;
use crate::recipe::{Author, Recipe, Response, Settings, SubRecipe};
use crate::recipe::{Author, Recipe, Response, Settings};
use crate::scheduler_trait::SchedulerTrait;
use crate::security::security_inspector::SecurityInspector;
use crate::session::extension_data::{EnabledExtensionsState, ExtensionState};
use crate::session::{Session, SessionManager, SessionType};
use crate::session::{Session, SessionManager};
use crate::tool_inspection::ToolInspectionManager;
use crate::tool_monitor::RepetitionInspector;
use crate::utils::is_token_cancelled;
Expand Down Expand Up @@ -121,7 +117,6 @@ pub struct Agent {
pub config: AgentConfig,

pub extension_manager: Arc<ExtensionManager>,
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 @@ -210,7 +205,6 @@ impl Agent {
provider: provider.clone(),
config,
extension_manager: Arc::new(ExtensionManager::new(provider.clone(), session_manager)),
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 @@ -452,23 +446,11 @@ impl Agent {
self.extend_system_prompt(final_output_system_prompt).await;
}

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(
&self,
sub_recipes: Option<Vec<SubRecipe>>,
response: Option<Response>,
include_final_output: bool,
) {
if let Some(sub_recipes) = sub_recipes {
self.add_sub_recipes(sub_recipes).await;
}

if include_final_output {
if let Some(response) = response {
self.add_final_output_tool(response).await;
Expand All @@ -485,18 +467,6 @@ impl Agent {
cancellation_token: Option<CancellationToken>,
session: &Session,
) -> (String, Result<ToolCallResult, ErrorData>) {
// Prevent subagents from creating other subagents
if session.session_type == SessionType::SubAgent && tool_call.name == SUBAGENT_TOOL_NAME {
return (
request_id,
Err(ErrorData::new(
ErrorCode::INVALID_REQUEST,
"Subagents cannot create other subagents".to_string(),
None,
)),
);
}

if tool_call.name == PLATFORM_MANAGE_SCHEDULE_TOOL_NAME {
let arguments = tool_call
.arguments
Expand Down Expand Up @@ -531,49 +501,7 @@ impl Agent {
}

debug!("WAITING_TOOL_START: {}", tool_call.name);
let result: ToolCallResult = if tool_call.name == SUBAGENT_TOOL_NAME {
let provider = match self.provider().await {
Ok(p) => p,
Err(_) => {
return (
request_id,
Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Provider is required".to_string(),
None,
)),
);
}
};

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)
.with_max_turns(max_turns_from_recipe);
let sub_recipes = self.sub_recipes.lock().await.clone();

let arguments = tool_call
.arguments
.clone()
.map(Value::Object)
.unwrap_or(Value::Object(serde_json::Map::new()));

handle_subagent_tool(
&self.config,
arguments,
task_config,
sub_recipes,
session.working_dir.clone(),
cancellation_token,
)
} else if self.is_frontend_tool(&tool_call.name).await {
let result: ToolCallResult = 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(
ErrorCode::INTERNAL_ERROR,
Expand Down Expand Up @@ -807,38 +735,13 @@ impl Agent {
Ok(())
}

pub async fn subagents_enabled(&self, session_id: &str) -> bool {
if self.config.goose_mode != GooseMode::Auto {
return false;
}
let context = self.extension_manager.get_context();
if matches!(
context
.session_manager
.get_session(session_id, false)
.await
.ok()
.map(|session| session.session_type),
Some(SessionType::SubAgent)
) {
return false;
}
!self
.extension_manager
.list_extensions()
.await
.map(|ext| ext.is_empty())
.unwrap_or(true)
}

pub async fn list_tools(&self, session_id: &str, extension_name: Option<String>) -> Vec<Tool> {
let mut prefixed_tools = self
.extension_manager
.get_prefixed_tools(session_id, extension_name.clone())
.await
.unwrap_or_default();

let subagents_enabled = self.subagents_enabled(session_id).await;
if (extension_name.is_none() || extension_name.as_deref() == Some("platform"))
&& self.config.scheduler_service.is_some()
{
Expand All @@ -849,12 +752,6 @@ impl Agent {
if let Some(final_output_tool) = self.final_output_tool.lock().await.as_ref() {
prefixed_tools.push(final_output_tool.tool());
}

if subagents_enabled {
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
2 changes: 1 addition & 1 deletion crates/goose/src/agents/builtin_skills/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use include_dir::{include_dir, Dir};
static BUILTIN_SKILLS_DIR: Dir =
include_dir!("$CARGO_MANIFEST_DIR/src/agents/builtin_skills/skills");

pub fn get_all_builtin_skills() -> Vec<&'static str> {
pub fn get_all() -> Vec<&'static str> {
BUILTIN_SKILLS_DIR
.files()
.filter(|f| f.path().extension().is_some_and(|ext| ext == "md"))
Expand Down
13 changes: 10 additions & 3 deletions crates/goose/src/agents/code_execution_extension.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::agents::extension::PlatformExtensionContext;
use crate::agents::extension_manager::get_tool_owner;
use crate::agents::mcp_client::{Error, McpClientTrait};
use anyhow::Result;
use async_trait::async_trait;
Expand Down Expand Up @@ -111,10 +112,16 @@ impl CodeExecutionClient {
let mut cfgs = vec![];
for tool in tools {
let full_name = tool.name.to_string();
let (server_name, tool_name) = full_name.split_once("__")?;
let (namespace, name) = if let Some((server, tool_name)) = full_name.split_once("__") {
(server.to_string(), tool_name.to_string())
} else if let Some(owner) = get_tool_owner(&tool) {
(owner, full_name)
} else {
continue;
};
cfgs.push(CallbackConfig {
name: tool_name.into(),
namespace: server_name.into(),
name,
namespace,
description: tool.description.as_ref().map(|d| d.to_string()),
input_schema: Some(json!(tool.input_schema)),
output_schema: tool.output_schema.as_ref().map(|s| json!(s)),
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/src/agents/execute_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ impl Agent {
Err(e) => return Err(anyhow!("Failed to build recipe: {}", e)),
};

self.apply_recipe_components(recipe.sub_recipes.clone(), recipe.response.clone(), true)
self.apply_recipe_components(recipe.response.clone(), true)
.await;

let prompt = [recipe.instructions.as_deref(), recipe.prompt.as_deref()]
Expand Down
Loading
Loading