feat(agents): derive a provider-declared default utility model when unset#103769
Conversation
…nset When agents.defaults.utilityModel is not set, utility tasks (titles, progress narration) now use the primary provider's declared small model (modelCatalog.providers.<id>.defaultUtilityModel: OpenAI -> gpt-5.6-luna, Anthropic -> claude-haiku-4-5). Auth is inherent because derivation follows the agent's primary provider. Setting utilityModel to an empty string disables utility routing entirely; narration turns on automatically when a default resolves and stays off otherwise. Formatting/lint/tests verified on Testbox (oxfmt, oxlint, plugins:inventory, docs:map, import-cycles, check:test-types, 4 test files).
The GPT-5.6 family 400s on temperature via the Responses API (live-verified: gpt-5.6-luna/-terra reject it; gpt-5.5 and gpt-5.4-mini/nano accept it). supportsOpenAITemperature gates all three OpenAI payload builders, with a catalog compat override (compat.supportsTemperature) declared for the 5.6 family in the openai manifest. Without this, utility tasks (titles, narration) would fail once gpt-5.6-luna becomes the derived default. Gates on Testbox: oxfmt, oxlint, plugins:inventory, import-cycles, check:test-types, 6 test files. Live proof on Testbox: gpt-5.6-luna and claude-haiku-4-5 one-shot completions succeed with temperature requested.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9601834136
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const defaultUtilityModel = plugin.modelCatalog?.providers?.[provider]?.defaultUtilityModel; | ||
| const modelId = defaultUtilityModel?.trim(); | ||
| if (modelId) { | ||
| return `${provider}/${modelId}`; |
There was a problem hiding this comment.
Preserve auth profiles on auto utility models
When the primary model is profile-qualified (for example openai/gpt-5.4@work) and utilityModel is unset, this derives openai/gpt-5.6-luna without the @work suffix. resolveSimpleCompletionSelectionForAgent only extracts a profile from the returned ref, so generated titles/progress narration now use the default OpenAI credential, or fail if only the selected profile exists, instead of reusing the primary model's profile. Preserve the primary ref's auth profile when deriving the provider default.
Useful? React with 👍 / 👎.
| /** Whether the model accepts the temperature parameter (GPT-5.6 family rejects it). */ | ||
| supportsTemperature?: boolean; |
There was a problem hiding this comment.
Allow temperature compat in user model config
This adds supportsTemperature to manifest model-catalog compat, but the strict user config compat schema still does not accept that key (src/config/zod-schema.core.ts's ModelCompatSchema has no supportsTemperature). For custom/OpenAI-compatible provider rows that need to disable temperature, or override the GPT-5.6 default, openclaw.json validation rejects the config before the request builders can use the new flag. Add the same field to the public model compat type and Zod schema.
Useful? React with 👍 / 👎.
…utility default A primary like openai/gpt-5.5@work previously reached utility tasks via the profiled fallback; the derived default shares the provider, so its trailing auth profile carries over instead of silently switching to default credentials (Codex review). Gates on Testbox: oxfmt, check:test-types, tests.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52498f7aef
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| if (options?.temperature !== undefined) { | ||
| if (options?.temperature !== undefined && supportsOpenAITemperature(model)) { |
There was a problem hiding this comment.
Gate temperature in OpenClaw transport too
When an OpenAI Responses model is routed through OpenClaw's managed transport (for example configs using request.proxy, request.tls, or a local service; see src/agents/provider-transport-stream.ts:102), this new helper is bypassed and src/agents/openai-transport-stream.ts:2665 still always sends temperature. The new default utility model is openai/gpt-5.6-luna, and title/narration callers pass temperature: 0.3, so those proxied/transport-aware OpenAI setups still hit the GPT-5.6 400 that this change is trying to avoid; apply the same supportsOpenAITemperature check in the OpenClaw transport builders.
Useful? React with 👍 / 👎.
|
Codex review: needs real behavior proof before merge. Reviewed July 10, 2026, 11:49 AM ET / 15:49 UTC. Summary PR surface: Source +132, Tests +226, Docs +4. Total +362 across 24 files. Reproducibility: yes. for the patch defects: direct source inspection shows the strict config contracts and managed Responses transport omit the new capability. The external GPT-5.6 rejection itself lacks inspectable live proof. Review metrics: 2 noteworthy metrics.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review findings
Review detailsBest possible solution: Use one public temperature-capability contract across every OpenAI Responses transport, preserve provider-owned utility defaults and auth profiles, and ship automatic unset behavior only after explicit acceptance plus inspectable fresh-install and upgrade proof. Do we have a high-confidence way to reproduce the issue? Yes for the patch defects: direct source inspection shows the strict config contracts and managed Responses transport omit the new capability. The external GPT-5.6 rejection itself lacks inspectable live proof. Is this the best way to solve the issue? No in its current form. Provider-owned metadata is the right boundary, but the implementation must cover the public config and managed transport siblings, and the changed default needs explicit product acceptance. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against e0df20b0b39e. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +132, Tests +226, Docs +4. Total +362 across 24 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
Merged via squash.
|
…nset (openclaw#103769) * feat(agents): derive a provider-declared default utility model when unset When agents.defaults.utilityModel is not set, utility tasks (titles, progress narration) now use the primary provider's declared small model (modelCatalog.providers.<id>.defaultUtilityModel: OpenAI -> gpt-5.6-luna, Anthropic -> claude-haiku-4-5). Auth is inherent because derivation follows the agent's primary provider. Setting utilityModel to an empty string disables utility routing entirely; narration turns on automatically when a default resolves and stays off otherwise. Formatting/lint/tests verified on Testbox (oxfmt, oxlint, plugins:inventory, docs:map, import-cycles, check:test-types, 4 test files). * fix(ai): drop the temperature parameter for models that reject it The GPT-5.6 family 400s on temperature via the Responses API (live-verified: gpt-5.6-luna/-terra reject it; gpt-5.5 and gpt-5.4-mini/nano accept it). supportsOpenAITemperature gates all three OpenAI payload builders, with a catalog compat override (compat.supportsTemperature) declared for the 5.6 family in the openai manifest. Without this, utility tasks (titles, narration) would fail once gpt-5.6-luna becomes the derived default. Gates on Testbox: oxfmt, oxlint, plugins:inventory, import-cycles, check:test-types, 6 test files. Live proof on Testbox: gpt-5.6-luna and claude-haiku-4-5 one-shot completions succeed with temperature requested. * fix(agents): carry the primary model's auth profile onto the derived utility default A primary like openai/gpt-5.5@work previously reached utility tasks via the profiled fallback; the derived default shares the provider, so its trailing auth profile carries over instead of silently switching to default credentials (Codex review). Gates on Testbox: oxfmt, check:test-types, tests. * docs: realign the manifest provider-fields table after adding defaultUtilityModel
Closes #103768. Follow-up to #103463 / #103461.
What Problem This Solves
utilityModel(titles, progress narration) is opt-in config, so most installs pay primary-model prices for internal one-shots and never get narration. We already know from the agent's primary provider which vendor the user can call — and each major vendor ships a tiny model for exactly this.Why This Change Was Made
modelCatalog.providers.<id>.defaultUtilityModel(typed + normalized in@openclaw/model-catalog-core), mirroring themediaUnderstandingProviderMetadata.defaultModelspattern. Seeded: OpenAI →gpt-5.6-luna(cheapest current-gen), Anthropic →claude-haiku-4-5. Google deferred (its catalog lives in plugin TS, not the manifestprovidersrecord).src/agents/utility-model.ts): unsetutilityModelresolves the primary provider's declared default via the process-current plugin metadata snapshot — synchronous, cached, no probing or extra model calls; auth is inherent because the agent already runs on that provider. ExplicitutilityModelwins;utilityModel: ""(agent- or defaults-level) disables utility routing entirely (titles → primary, narration off).attachProgressNarratorToReplyOptionsnow gates on the shared resolver) and still never falls back to the primary model.temperaturewith a 400 on the Responses API while 5.5/5.4 accept it — utility callers sendtemperature: 0.3, so the derived default would have failed every call.supportsOpenAITemperature(catalogcompat.supportsTemperatureoverride + family fallback, declared for all four gpt-5.6 manifest entries) now gates all three OpenAI payload builders (responses, shared, chatgpt-responses).User Impact
utilityModel.utilityModel: "". Docs updated:docs/gateway/config-agents.md,docs/concepts/models.md,docs/concepts/progress-drafts.md,docs/plugins/manifest.md(new provider field).utilityModelconfigs behave identically.Evidence
openai/gpt-5.6-lunacompletes with temperature requested-but-stripped (reproduced the 400 without the fix),openai/gpt-5.5keeps temperature,anthropic/claude-haiku-4-5completes with real auth (allstopReason=stopwith sensible status text).plugins:inventory:check,docs:map:check,check:import-cycles,check:test-types, and test files:src/agents/utility-model.test.ts(new),simple-completion-runtime.selection.test.ts,progress-narrator.test.ts,model-catalog-normalize.test.ts,openai-reasoning-effort.test.ts,openai-responses-shared.test.ts,test/scripts/plugin-sdk-surface-report.test.ts.""disable vs explicit, agent-level override), provider-default derivation from injected snapshots, primary-provider derivation, empty-utility selection fallback, narration gating both directions, catalog normalization passthrough, temperature family/compat matrix, payload-builder temperature drop/keep.