Skip to content

feat: add per-delegate max_turns override#8066

Merged
DOsinga merged 4 commits intoblock:mainfrom
Yagi-Michael:feat/max-turns-per-delegate
Mar 26, 2026
Merged

feat: add per-delegate max_turns override#8066
DOsinga merged 4 commits intoblock:mainfrom
Yagi-Michael:feat/max-turns-per-delegate

Conversation

@Yagi-Michael
Copy link
Copy Markdown
Contributor

@Yagi-Michael Yagi-Michael commented Mar 23, 2026

Summary

  • Adds a max_turns parameter to the delegate tool, allowing individual delegations to override the max turns limit
  • Fixes priority order so delegated recipe settings.max_turns and session recipe are both respected
  • Full priority chain: per-delegate max_turns > delegated recipe settings > session recipe > env var > config > default

Context

The original max_turns implementation landed in subagent_tool.rs, but the summon refactor replaced that file with the unified summon extension (platform_extensions/summon.rs). The per-delegate override was never carried over, so it needs to be wired up in the new location.

Closes #6198

Changes

  • DelegateParams: new optional max_turns: Option<usize> field
  • JSON schema: exposes max_turns as integer with minimum: 1
  • Validation: rejects max_turns: 0
  • build_task_config: checks params.max_turns, then delegated recipe.settings.max_turns, then falls back to resolve_max_turns
  • resolve_max_turns: reordered to session recipe > env var (was env var > recipe)

Test plan

  • Deserialization tests for max_turns presence and absence
  • Validation tests rejecting zero and accepting positive values
  • Resolution tests verifying recipe overrides env var
  • Resolution tests verifying env var fallback when no recipe
  • Resolution tests verifying default fallback
  • Env var tests use #[serial] for thread safety

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0dfa76a437

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1545 to +1547
let max_turns = params
.max_turns
.unwrap_or_else(|| self.resolve_max_turns(session));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Read delegated recipe max_turns before parent-session fallback

For delegate(source: ...) calls, build_delegate_recipe has already loaded the target recipe and passed it into this function, but the new max-turn logic still ignores that recipe argument and only consults session.recipe. In a normal chat session session.recipe is None, so a source recipe/subrecipe with its own settings.max_turns will silently fall back to the env/default limit instead of honoring the delegated recipe's explicit bound; if the parent session has a different recipe limit, that value wins instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Helpful feedback. Think this is real and not just fluff. Taking a look (using goose to help 🪿 )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — this was a real bug. resolve_max_turns only checked session.recipe (the parent session's recipe), so a delegated source recipe's settings.max_turns was silently ignored.

Fixed by inserting an .or_else() check on the delegated recipe.settings.max_turns between the per-delegate params.max_turns override and the resolve_max_turns(session) fallback. The priority chain is now:

  1. params.max_turns (per-delegate override)
  2. Delegated recipe's settings.max_turns
  3. Session recipe / env var / config / default

Adds max_turns: Option<usize> to DelegateParams so each delegate() call
can cap its own turn budget independently of the recipe-level setting.

The original max_turns implementation landed in subagent_tool.rs, but
the summon refactor replaced that file with the unified summon extension
(platform_extensions/summon.rs). The per-delegate override was never
carried over, so it needs to be wired up in the new location.

Priority chain (highest to lowest):
1. delegate tool max_turns argument
2. delegated recipe settings.max_turns
3. session recipe settings.max_turns
4. GOOSE_SUBAGENT_MAX_TURNS env var / config
5. Default 25

Rejects max_turns: 0 since it would immediately stop the delegate.

Relates to block#6198

Signed-off-by: Michael Yagi <[email protected]>
@Yagi-Michael Yagi-Michael force-pushed the feat/max-turns-per-delegate branch from 0dfa76a to 03aa967 Compare March 23, 2026 06:49
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03aa967d7a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1545 to +1549
let max_turns = params
.max_turns
.or_else(|| {
recipe
.settings
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bound delegated max_turns to SessionConfig's u32 range

build_task_config now forwards either params.max_turns or recipe.settings.max_turns as a raw usize, but run_subagent_task later narrows task_config.max_turns to u32 in subagent_handler.rs:175-179. That means delegated values above u32::MAX wrap (for example, 4294967296 becomes 0 and the first turn check in agent.rs stops the subagent immediately), and settings.max_turns: 0 from a delegated recipe now slips through even though validate_delegate_params rejects 0 for tool-call overrides. Since this feature is what starts honoring delegated values, it needs a 1..=u32::MAX guard before with_max_turns.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — added .clamp(1, u32::MAX as usize) to the resolved max_turns before passing to with_max_turns. This handles both issues: rejects 0 from delegated recipe settings, and prevents wrapping when narrowed to u32 in subagent_handler.rs:178.

- Collapse .or_else block to single line (cargo fmt)
- Use struct initializer instead of field assignment for Session (clippy)
- Clamp resolved max_turns to 1..=u32::MAX before passing to TaskConfig,
  preventing u32 wrapping in SessionConfig and rejecting 0 from recipes

Signed-off-by: Michael Yagi <[email protected]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 500d503356

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1547 to +1549
.or_else(|| recipe.settings.as_ref().and_then(|s| s.max_turns))
.unwrap_or_else(|| self.resolve_max_turns(session))
.clamp(1, u32::MAX as usize);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject invalid max_turns instead of saturating it

The new .clamp(1, u32::MAX as usize) avoids the wraparound in subagent_handler.rs:175-179, but it still rewrites malformed limits into different budgets. If a delegate call, recipe, or GOOSE_SUBAGENT_MAX_TURNS is set to 0 or above u32::MAX, this now runs the subagent with 1 or 4_294_967_295 turns instead of surfacing the configuration error. Because max_turns is the runaway-safety bound, silently saturating typos here can turn a bad limit into an effectively unbounded delegate.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay codex, one more time :)

@Yagi-Michael
Copy link
Copy Markdown
Contributor Author

🪿 please let me know how this looks or if would like any changes 🪿

Yagi-Michael and others added 2 commits March 23, 2026 01:25
Error on max_turns of 0 or above u32::MAX from recipe settings or env
var, which bypass validate_delegate_params.

Signed-off-by: Michael Yagi <[email protected]>
Copy link
Copy Markdown
Collaborator

@DOsinga DOsinga left a comment

Choose a reason for hiding this comment

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

Good fix — addresses a real bug (delegated recipe max_turns was silently ignored) and a real edge case (u32 overflow). Nice job engaging with codex feedback and iterating. I cleaned up a few restatement comments and dropped two trivial deserialization tests (they only assert struct layout, not behaviour) in a follow-up commit.

@Yagi-Michael
Copy link
Copy Markdown
Contributor Author

Woo, thanks! Thank you for the cleanup, will be better at catching those bloating going forward

@Yagi-Michael
Copy link
Copy Markdown
Contributor Author

Good fix — addresses a real bug (delegated recipe max_turns was silently ignored) and a real edge case (u32 overflow). Nice job engaging with codex feedback and iterating. I cleaned up a few restatement comments and dropped two trivial deserialization tests (they only assert struct layout, not behaviour) in a follow-up commit.

Hi @DOsinga, just curious what the merge cadence is and roughly when can this might get merged in

@DOsinga DOsinga added this pull request to the merge queue Mar 26, 2026
Merged via the queue into block:main with commit 838d99e Mar 26, 2026
21 checks passed
hydrosquall pushed a commit to hydrosquall/goose that referenced this pull request Mar 31, 2026
Signed-off-by: Michael Yagi <[email protected]>
Signed-off-by: Douwe Osinga <[email protected]>
Co-authored-by: Douwe Osinga <[email protected]>
Signed-off-by: Cameron Yick <[email protected]>
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.

feat: Add max_turns to recipe and subagent settings

2 participants