fix(backend): apply an Agent's sampling parameters wherever it runs - #417
Merged
Conversation
An Agent must generate with the parameters assigned to it, whether it is driving a Chat turn or running as someone else's sub-agent. Two places quietly disagreed. Sub-agent runs dropped all six. `createSubAgentTool` built its ToolLoopAgent from model, instructions, tools and stopWhen only, and `SubAgentToolOptions` had no field to carry the rest -- so an Agent's Temperature was inert the moment it was used as a sub-agent, on every Provider including ones that honour it. Nothing reported this: the warnings stream is silent because the parameter was never sent. `seed` never reached the model on an Agent turn. The other five resolved from `agent || data`, while `seed` was read straight off the request, so an Agent's stored Seed was ignored on every Agent-driven turn. Both now resolve through one shared `resolveSamplingSettings`, which keeps the null-means-unset rule (#263) in a single place rather than duplicating the list twice and letting one copy fall behind -- which is how `seed` came to be missing from one of them. Docs: the sub-agent section claimed a delegated run receives "exactly four things". Max steps was already a fifth, and sampling parameters are now a sixth. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An Agent must generate with the parameters assigned to it, whether it's driving a Chat turn or running as someone else's sub-agent. Two places quietly disagreed.
Sub-agent runs dropped all six
createSubAgentToolbuilt itsToolLoopAgentfrommodel,instructions,toolsandstopWhenonly, andSubAgentToolOptionshad no field to carry the rest. So an Agent's Temperature, Top-p, Top-k, Seed, Presence penalty and Frequency penalty were all inert the moment that Agent was used as a sub-agent — on every Provider, including ones that honour them perfectly.Nothing could have reported this. It looks like the provider-entitlement problem in #411, but it isn't: the warnings stream is silent because the parameter was never in the request. Surfacing provider warnings, however well, would never have caught it.
seednever reached the model on an Agent turnresolveGenerationConfigresolved the other five fromagent || data, but the returnedstream.seedwas read straight off the request. An Agent's stored Seed was therefore ignored on every Agent-driven turn — the field is on the Agent form, in the schema, and settable through the agent-management tools, and it went nowhere.Direct (no-Agent) turns are unaffected:
agent || datafalls through to the request, so a Direct turn's seed still comes from the request and is still persisted on the row.One resolver instead of two lists
Both paths now go through
resolveSamplingSettingsin a newsampling-settings.ts. It keeps the null-means-unset rule from #263 in a single place —nullandundefinedare both omitted so the Provider default applies, rather than an explicit null being sent.That's the actual root cause worth fixing here: the parameter list was written out twice, and one copy fell behind.
seedis what fell out. A shared resolver is why a seventh parameter can't land in one path and not the other.The module is separate rather than living in
chat-execution.tsbecausechat-executionandsub-agentalready import each other.Verification
Six new tests: sampling forwarded to the sub-agent's agent, unset parameters omitted rather than sent as
undefined, sampling unable to clobbermodel/instructions/tools, stored parameters forwarded withnulltreated as unset, the Agent's seed used on an Agent turn, and the request's seed still used on a Direct turn.pnpm lint,pnpm typecheckandpnpm testpass — 1396 backend, 63 frontend, 24 docs-contract.Docs
building-with-platypus/agents.mdxsaid a delegated run receives "exactly four things". Max steps was already a fifth (passed asstopWhen), and sampling parameters are now a sixth. Rewritten to say the rule rather than enumerate a list that keeps going stale: a sub-agent run gets everything belonging to the Agent itself, and an Agent generates the same way wherever it runs.Related
Context for this is on #411, which stays open — it's about Providers silently dropping parameters we do send, which is a separate problem with a separate fix.
🤖 Generated with Claude Code