Skip to content

fix(agent): omit sampling params the agent spec never set - #97

Merged
wolfy-j merged 2 commits into
masterfrom
fix/agent-omit-unset-sampling-params
Jul 28, 2026
Merged

fix(agent): omit sampling params the agent spec never set#97
wolfy-j merged 2 commits into
masterfrom
fix/agent-omit-unset-sampling-params

Conversation

@wolfy-j

@wolfy-j wolfy-j commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Problem

AgentRunner defaulted temperature to 0 whenever a compiled spec omitted it:

temperature = compiled_spec.temperature or AGENT_CONFIG.defaults.temperature,  -- defaults.temperature = 0

CompiledAgentSpec already declared temperature: number?, but the AgentRunner type declared temperature: number, so a caller had no way to express "send no temperature at all".

Claude 4.7+ and Opus/Sonnet 5 reject temperature, top_p and top_k outright. Verified against the live API:

model temperature top_p top_k
claude-opus-5 400 400 400
claude-sonnet-5 400 400 400
claude-haiku-4-5-20251001 accepted accepted accepted
$ curl .../v1/messages -d '{"model":"claude-opus-5","temperature":0,...}'
{"type":"error","error":{"message":"`temperature` is deprecated for this model."}}

Anthropic's guidance is to omit these and steer via prompting; adaptive thinking requires the model to control its own sampling. So the fabricated default makes every agent that never asked for a temperature unusable on those models — the flagship and balanced tiers.

Sending temperature: 1 is not an adequate workaround: it is accepted, but it suppresses the model's thinking block (['thinking','text'] becomes ['text']), silently degrading output.

Fix

Stop inventing the default. temperature is dropped from AGENT_CONFIG.defaults, the runner type carries it as number?, and the spec value passes through untouched.

Lua omits a key assigned nil in a table constructor, so the existing options table drops the field on its own once the fabrication stops — no conditional assignment needed.

An explicit temperature, including an explicit 0, is still forwarded. Only "author said nothing" now means "send nothing".

The wippy.llm Claude mapper is deliberately untouched: claude_options.temperature = contract_options.temperature is already nil-transparent, and having the provider strip a value a caller explicitly set would be the mirror-image bug.

Tests

src/agent/test is new — the agent package shipped tests with no harness to run them, mirroring src/llm/test. That gap is why this went unnoticed.

Four cases, all in agent_test.lua:

  • spec omits temperature -> runner field is nil and options carries no temperature/top_p/top_k
  • spec sets 0.7 -> forwarded as 0.7
  • spec sets 0 explicitly -> forwarded as 0, not treated as unset
  • the pre-existing "default values for missing fields" case now asserts nil rather than 0

Before the fix: 2 failed / 312. After: 312 passed.

AgentRunner defaulted temperature to 0 whenever a compiled spec omitted
it, and the runner type made the field non-optional, so a caller had no
way to express "send no temperature at all".

Claude 4.7+ and Opus/Sonnet 5 reject temperature, top_p and top_k
outright with a 400 ("`temperature` is deprecated for this model"), and
Anthropic's migration guidance is to omit them and steer via prompting.
The fabricated default therefore makes every agent that never asked for
a temperature unusable on those models.

The spec type already declared temperature as optional; the runner now
carries it as optional too and passes it through untouched. Lua omits a
key assigned nil in a table constructor, so the options table drops the
field on its own once the default stops being invented. An explicit
temperature -- including an explicit 0 -- is still forwarded.

Adds src/agent/test, mirroring src/llm/test, because the agent package
shipped tests with no harness to run them.
@wolfy-j
wolfy-j force-pushed the fix/agent-omit-unset-sampling-params branch from c4357f9 to 266ac92 Compare July 28, 2026 21:21
The agent package shipped tests that no harness and no CI job executed.
With src/agent/test in place the module joins both matrices.

Linting it for the first time surfaced 7 type errors in compiler.lua,
fixed here:

- set_from_list and behaviors_from_trait accumulate into untyped locals,
  so their inferred returns ({[string]: true}, {[integer]: table}) did
  not match the declared ones. Both locals now carry the declared type.
- normalize_raw_binding already validates that contract, binding and
  kind are non-empty strings, but returned an untyped table, so every
  field read downstream degraded to any and no BindingSpec built from
  one type-checked. It now returns NormalizedBinding, threaded through
  collect_raw_bindings and raw_bindings_from_trait.
- Binding ids arrive as registry data of any type against a string?
  field. optional_id carries a value through as a string and leaves nil
  alone, shared by the three sites that build a spec.
@wolfy-j
wolfy-j force-pushed the fix/agent-omit-unset-sampling-params branch from 266ac92 to 9c8eacf Compare July 28, 2026 21:25
@wolfy-j
wolfy-j merged commit 1202d62 into master Jul 28, 2026
21 checks passed
@wolfy-j
wolfy-j deleted the fix/agent-omit-unset-sampling-params branch July 28, 2026 21:27
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