feat: add per-delegate max_turns override#8066
Conversation
There was a problem hiding this comment.
💡 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".
| let max_turns = params | ||
| .max_turns | ||
| .unwrap_or_else(|| self.resolve_max_turns(session)); |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
Helpful feedback. Think this is real and not just fluff. Taking a look (using goose to help 🪿 )
There was a problem hiding this comment.
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:
params.max_turns(per-delegate override)- Delegated recipe's
settings.max_turns - 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]>
0dfa76a to
03aa967
Compare
There was a problem hiding this comment.
💡 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".
| let max_turns = params | ||
| .max_turns | ||
| .or_else(|| { | ||
| recipe | ||
| .settings |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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]>
There was a problem hiding this comment.
💡 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".
| .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); |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
Okay codex, one more time :)
|
🪿 please let me know how this looks or if would like any changes 🪿 |
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]>
Signed-off-by: Douwe Osinga <[email protected]>
DOsinga
left a comment
There was a problem hiding this comment.
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.
|
Woo, thanks! Thank you for the cleanup, will be better at catching those bloating going forward |
Hi @DOsinga, just curious what the merge cadence is and roughly when can this might get merged in |
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]>
Summary
max_turnsparameter to the delegate tool, allowing individual delegations to override the max turns limitsettings.max_turnsand session recipe are both respectedmax_turns> delegated recipe settings > session recipe > env var > config > defaultContext
The original
max_turnsimplementation landed insubagent_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 optionalmax_turns: Option<usize>fieldmax_turnsas integer withminimum: 1max_turns: 0build_task_config: checksparams.max_turns, then delegatedrecipe.settings.max_turns, then falls back toresolve_max_turnsresolve_max_turns: reordered to session recipe > env var (was env var > recipe)Test plan
max_turnspresence and absence#[serial]for thread safety