Auto-inject summon extension for recipes with sub_recipes

When a recipe defines sub_recipes, the summon extension is now automatically
added to the extensions list. This ensures the delegate tool is available
without requiring users to manually specify the summon extension.

Changes:
- Add ensure_summon_for_subrecipes() helper in Recipe that auto-injects
  the summon platform extension when sub_recipes are present
- Update test assertions to expect summon in extension list
- Update test script to look for 'delegate' instead of 'subagent'
This commit is contained in:
Tyler Longwell
2026-02-05 15:01:32 -05:00
parent 1062d047d2
commit f1483efa82
4 changed files with 46 additions and 16 deletions
@@ -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();
@@ -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();
+28 -5
View File
@@ -226,6 +226,24 @@ pub struct RecipeBuilder {
}
impl Recipe {
fn ensure_summon_for_subrecipes(&mut self) {
if self.sub_recipes.is_none() {
return;
}
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]),
_ => {}
}
}
/// Returns true if harmful content is detected in instructions, prompt, or activities fields
pub fn check_for_security_warnings(&self) -> bool {
if [self.instructions.as_deref(), self.prompt.as_deref()]
@@ -277,7 +295,7 @@ impl Recipe {
}
pub fn from_content(content: &str) -> Result<Self> {
let recipe: Recipe = match serde_yaml::from_str::<serde_yaml::Value>(content) {
let mut recipe: Recipe = match serde_yaml::from_str::<serde_yaml::Value>(content) {
Ok(yaml_value) => {
if let Some(nested_recipe) = yaml_value.get("recipe") {
serde_yaml::from_value(nested_recipe.clone())
@@ -291,6 +309,7 @@ impl Recipe {
.map_err(|e| anyhow::anyhow!("{}", strip_error_location(&e.to_string())))?,
};
recipe.ensure_summon_for_subrecipes();
Ok(recipe)
}
}
@@ -450,8 +469,10 @@ mod tests {
assert_eq!(recipe.prompt, Some("Test prompt".to_string()));
assert!(recipe.extensions.is_some());
let extensions = recipe.extensions.unwrap();
assert_eq!(extensions.len(), 1);
let extensions = recipe.extensions.as_ref().unwrap();
assert_eq!(extensions.len(), 2);
assert!(extensions.iter().any(|e| e.name() == "test_extension"));
assert!(extensions.iter().any(|e| e.name() == "summon"));
assert!(recipe.parameters.is_some());
let parameters = recipe.parameters.unwrap();
@@ -533,8 +554,10 @@ sub_recipes:
assert_eq!(recipe.prompt, Some("Test prompt".to_string()));
assert!(recipe.extensions.is_some());
let extensions = recipe.extensions.unwrap();
assert_eq!(extensions.len(), 1);
let extensions = recipe.extensions.as_ref().unwrap();
assert_eq!(extensions.len(), 2);
assert!(extensions.iter().any(|e| e.name() == "test_extension"));
assert!(extensions.iter().any(|e| e.name() == "summon"));
assert!(recipe.parameters.is_some());
let parameters = recipe.parameters.unwrap();
@@ -17,6 +17,7 @@ prompt: |
Run two subrecipes in parallel:
- use file_stats subrecipe to gather file statistics for {{ target_directory }}
- use code_patterns subrecipe to analyze code patterns in {{ target_directory }}
Iteratively `sleep 10` until the delegates complete, then load their output and confirm success
extensions:
- type: builtin
name: developer
+9 -9
View File
@@ -22,7 +22,7 @@ export PATH="$SCRIPT_DIR/target/release:$PATH"
# Set default provider and model if not already set
# Use fast model for CI to speed up tests
export GOOSE_PROVIDER="${GOOSE_PROVIDER:-anthropic}"
export GOOSE_MODEL="${GOOSE_MODEL:-claude-3-5-haiku-20241022}"
export GOOSE_MODEL="${GOOSE_MODEL:-claude-haiku-4-5}"
echo "Using provider: $GOOSE_PROVIDER"
echo "Using model: $GOOSE_MODEL"
@@ -77,17 +77,17 @@ check_recipe_output() {
local tmpfile=$1
local mode=$2
# Check for unified subagent tool invocation (new format: "─── subagent |")
if grep -q "─── subagent" "$tmpfile"; then
echo "✓ SUCCESS: Subagent tool invoked"
RESULTS+=("✓ Subagent tool invocation ($mode)")
# Check for delegate tool invocation (new format: "─── delegate |")
if grep -q "─── delegate" "$tmpfile"; then
echo "✓ SUCCESS: Delegate tool invoked"
RESULTS+=("✓ Delegate tool invocation ($mode)")
else
echo "✗ FAILED: No evidence of subagent tool invocation"
RESULTS+=("✗ Subagent tool invocation ($mode)")
echo "✗ FAILED: No evidence of delegate tool invocation"
RESULTS+=("✗ Delegate tool invocation ($mode)")
fi
# Check that both subrecipes were called (shown as "subrecipe: <name>" in output)
if grep -q "subrecipe:.*file_stats\|file_stats.*subrecipe" "$tmpfile" && grep -q "subrecipe:.*code_patterns\|code_patterns.*subrecipe" "$tmpfile"; then
# Check that both subrecipes were called (shown as "source: <name>" in delegate output)
if grep -q "source:.*file_stats\|source.*file_stats" "$tmpfile" && grep -q "source:.*code_patterns\|source.*code_patterns" "$tmpfile"; then
echo "✓ SUCCESS: Both subrecipes (file_stats, code_patterns) found in output"
RESULTS+=("✓ Both subrecipes present ($mode)")
else