Skip to content

fix(backend): apply an Agent's sampling parameters wherever it runs - #417

Merged
willdady merged 1 commit into
mainfrom
fix/sub-agent-sampling-parameters
Aug 6, 2026
Merged

fix(backend): apply an Agent's sampling parameters wherever it runs#417
willdady merged 1 commit into
mainfrom
fix/sub-agent-sampling-parameters

Conversation

@willdady

@willdady willdady commented Aug 6, 2026

Copy link
Copy Markdown
Owner

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

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, 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.

seed never reached the model on an Agent turn

resolveGenerationConfig resolved the other five from agent || data, but the returned stream.seed was 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 || data falls 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 resolveSamplingSettings in a new sampling-settings.ts. It keeps the null-means-unset rule from #263 in a single place — null and undefined are 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. seed is 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.ts because chat-execution and sub-agent already 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 clobber model/instructions/tools, stored parameters forwarded with null treated 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 typecheck and pnpm test pass — 1396 backend, 63 frontend, 24 docs-contract.

Docs

building-with-platypus/agents.mdx said a delegated run receives "exactly four things". Max steps was already a fifth (passed as stopWhen), 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

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>
@willdady
willdady merged commit f23acc8 into main Aug 6, 2026
4 checks passed
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.

1 participant