Skip to content

fix(recipe): auto-inject summon when instructions use delegate()#7360

Closed
clouatre wants to merge 1 commit intoblock:mainfrom
clouatre:feat/delegate-summon-injection
Closed

fix(recipe): auto-inject summon when instructions use delegate()#7360
clouatre wants to merge 1 commit intoblock:mainfrom
clouatre:feat/delegate-summon-injection

Conversation

@clouatre
Copy link
Copy Markdown
Contributor

Summary

Recipes with an explicit extensions: block that omits summon lose access to the delegate tool. This was a regression introduced in #6964, which moved delegate from an unconditional builtin into the summon platform extension.

ensure_summon_for_subrecipes only checked for sub_recipes presence. It now also scans recipe.instructions for the string delegate( and injects the summon platform extension when found but absent. A tracing::warn is emitted when auto-injection occurs.

Changes

  • crates/goose/src/recipe/mod.rs: extend ensure_summon_for_subrecipes to detect delegate( in instructions and auto-inject summon

Tests

Three unit tests added:

  • test_ensure_summon_injected_when_delegate_in_instructions: recipe with explicit empty extensions and delegate( in instructions gets summon injected
  • test_ensure_summon_not_duplicated_when_already_present: summon already listed + delegate in instructions stays at exactly one summon entry
  • test_ensure_summon_not_injected_without_delegate: recipe without delegate and empty extensions stays empty

Reproduction

Before this fix, a recipe like:

extensions:
  - type: builtin
    name: developer
instructions: |
  Use delegate() to spawn subagents.

would fail with delegate tool not found at runtime.

After this fix, summon is auto-injected and the recipe works as expected.

Fixes #7355

Copilot AI review requested due to automatic review settings February 19, 2026 17:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a regression from #6964 where recipes with explicit extensions: blocks that omit summon lose access to the delegate() tool. The fix extends the ensure_summon_for_subrecipes function to detect delegate( usage in recipe instructions and auto-inject the summon extension when needed.

Changes:

  • Extended ensure_summon_for_subrecipes to detect delegate( in instructions and auto-inject summon platform extension
  • Added warning when auto-injection occurs due to delegate usage
  • Added three unit tests for delegate detection and summon injection scenarios

@clouatre clouatre force-pushed the feat/delegate-summon-injection branch 2 times, most recently from ecd4c76 to 2ebd70e Compare February 19, 2026 17:13
Copilot AI review requested due to automatic review settings February 19, 2026 17:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

crates/goose/src/recipe/mod.rs:267

  • ensure_summon_for_subrecipes currently injects extensions = Some(vec![summon]) when uses_delegate and extensions is None, which turns an implicit "use globally-enabled extensions" recipe into an explicit/exclusive extensions list (see resolve_extensions_for_new_session), potentially dropping other enabled extensions and changing runtime behavior; consider only auto-injecting when the recipe had an explicit extensions: block (i.e., self.extensions.is_some()), or merging summon into the resolved enabled extensions instead of replacing them.
        let summon_present = self
            .extensions
            .as_ref()
            .map(|exts| exts.iter().any(|e| e.name() == "summon"))
            .unwrap_or(false);

        if uses_delegate && !summon_present {
            warn!("recipe instructions use 'delegate' but 'summon' extension is not listed; auto-injecting summon");
        }

        let summon = ExtensionConfig::Platform {
            name: "summon".to_string(),
            description: String::new(),
            display_name: None,
            bundled: None,
            available_tools: vec![],
        };
        match &mut self.extensions {
            Some(exts) if !exts.iter().any(|e| e.name() == "summon") => exts.push(summon),
            None => self.extensions = Some(vec![summon]),
            _ => {}

@clouatre
Copy link
Copy Markdown
Contributor Author

Hey @lifeizhou-ap, would you have a chance to look at this? It fixes a regression from #6964 where recipes with an explicit extensions: block silently lose delegate() unless they manually add summon.

@tlongwell-block
Copy link
Copy Markdown
Collaborator

/goose can you take a look at this?

Hi, @clouatre 👋

Recipes that depend on subagents/delegates should probably explicitly include the summon extension going forward.

Otherwise, we would probably want to just include all default-enabled platform extensions in recipes instead of making a callout for summon.

The exception would be recipes that use subrecipes, in which case we should automatically include the summon extension so that subrecipes work. I figure that's an implicit include of the extension

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ goose PR review could not be completed. Check the workflow run for details.

Extend ensure_summon (renamed from ensure_summon_for_subrecipes) to
detect delegate() calls in recipe instructions via regex and auto-inject
the summon extension with a warning log.

Fix the None extensions arm: when extensions is None it means 'use all
global extensions' which already includes summon. The previous code from
block#6964 incorrectly converted None to Some([summon]), dropping all other
global extensions.

Addresses regression introduced in block#6964 (v1.24.0) where recipes with
explicit extensions blocks silently lost delegate() capability.

Signed-off-by: Hugues Clouâtre <[email protected]>
@clouatre clouatre force-pushed the feat/delegate-summon-injection branch from 2ebd70e to 5fdc6ed Compare February 25, 2026 00:24
@clouatre
Copy link
Copy Markdown
Contributor Author

@tlongwell-block Thanks for the feedback. Agreed that explicit is better, and sub_recipes is the cleanest case for auto-injection.

The context here is a silent regression from #6964 (v1.24.0). Recipes with explicit extensions: blocks that use delegate() just stopped working, no warning, no migration path. Took me a while to track down in my own recipes.

Revised approach:

The warning nudges authors toward explicit declaration while not breaking existing recipes in the meantime.

@clouatre
Copy link
Copy Markdown
Contributor Author

Closing this PR. The maintainer's position is that recipes using delegate must explicitly declare the summon extension, and auto-injection should be limited to the sub_recipes case.

PR #6964 (v1.24.0) moved delegate from an unconditional builtin into the summon platform extension. This was a breaking change for every recipe with an explicit extensions: block that relies on delegate. There was no deprecation period, no migration note, and no runtime warning. The failure mode is silent: delegate disappears from the tool list without any error.

This cost us several hours of debugging. We initially misattributed the regression to an unrelated change (#7353) because nothing pointed to the summon extension as the cause. PR #7457 fixed a separate regression from the same refactor.

Opening a documentation issue so other users do not have to repeat this effort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recipe sessions lose delegate tool when extensions block omits summon

3 participants