feat(config): support per-model reasoning_effort overrides - #51377
feat(config): support per-model reasoning_effort overrides#51377ScotterMonk wants to merge 1 commit into
Conversation
Related: implements per-model reasoning_effort overrides via a top-level agent.reasoning_overrides dict — the same goal as feature request #21256 (which proposed scoping it under custom_providers). Cross-linking for reviewer context. |
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved
Well-designed feature: per-model reasoning_effort overrides in config.yaml. Different models can now have different reasoning effort levels (e.g., Claude Opus at xhigh, GPT-5 at low).
Looks Good
- Clean implementation:
reasoning_overridesdict inagent:config section - Model name matching is flexible (dots/dashes interchangeable, provider prefix optional)
reasoning_configis properly restored on model switch and fallback activation- Config example documents the feature with clear examples
- Tests cover the resolution logic
- Large PR (1181 additions) but well-scoped to one feature across the agent transport layer
Reviewed by Hermes Agent
|
Building on @alt-glitch's triage note — wanted to address the relationship between this PR, #20594, and the closed #15511 directly. This PR and #20594 solve different problems:
They're complementary. The top-level Active issues that would benefit from this:
Why top-level instead of
Happy to address any concerns about the spelling-tolerant matching or test coverage. The helper generates bounded variant sets (dots↔dashes, provider prefix stripping) and is tested with 90 cases including false-positive guards. |
0acb055 to
864dd95
Compare
Add agent.reasoning_overrides dict to config.yaml. Users can now set
a reasoning_effort per model, overriding the global agent.reasoning_effort.
Example:
agent:
reasoning_effort: "medium" # global default
reasoning_overrides:
"openrouter/anthropic/claude-opus-4.5": "xhigh"
"openai/gpt-5": "low"
"claude-sonnet-4.6": "high" # bare model name also works
The helper is spelling-tolerant: override keys match regardless of
provider prefix or dots-vs-dashes normalization, so users can write
keys in any sensible form and they'll match.
Resolution priority:
1. Session-scoped /reasoning --session override (gateway only; unchanged)
2. Per-model override from agent.reasoning_overrides (spelling-tolerant)
3. Global agent.reasoning_effort (existing)
4. Provider default (unchanged)
Wired into:
- CLI startup (cli.py)
- Messaging gateway agent construction (gateway/run.py)
- Desktop/TUI _load_reasoning_config (tui_gateway/server.py)
- Cron job scheduler (cron/scheduler.py)
- /model mid-session switch (agent/agent_runtime_helpers.py)
+ _primary_runtime now tracks reasoning_config for correct fallback recovery
- Fallback activation (agent/chat_completion_helpers.py::try_activate_fallback)
+ Re-resolves reasoning_config for the fallback model (best-effort)
Closes NousResearch#21256 (per-model reasoning_effort defaults).
Note: no hermes config set agent.reasoning_overrides.<model> support;
users edit the YAML directly. _set_nested splits on "." and would
corrupt model keys containing version dots.
864dd95 to
d17d31b
Compare
|
Quick status update since my earlier comment — this PR has been stable and all-green for three weeks now:
The design has held up well under review. The top-level
90 test cases across 6 test files cover the resolution logic, model switch, fallback recovery, and the YAML-boolean-False edge case (where Would appreciate a maintainer review and merge consideration. Happy to address any remaining feedback. |
|
Merged via PR #64458 — your commit was cherry-picked onto current main with your authorship preserved in git history (rebase merge, commit d9cdb81). Thanks for the thorough work, @ScotterMonk: the spelling-tolerant matcher, the six-surface wire-up, and the test coverage all survived intact. On top of your commit we unified the per-surface resolution logic into a single shared chokepoint ( Fixes #21256. |
Summary
Adds
agent.reasoning_overridesdict to config.yaml, letting users configure a per-modelreasoning_effortthat overrides the globalagent.reasoning_effort. This solves the common pain point of wantingxhighreasoning for Claude Opus butmediumfor Gemini Flash — without changing it manually each time.The helper (
resolve_per_model_reasoning_effort) is spelling-tolerant: keys match regardless of provider prefix or dots-vs-dashes normalization. Users can writeclaude-opus-4.5,claude-opus-4-5,anthropic/claude-opus-4.5, oropenrouter/anthropic/claude-opus-4.5— all match the same model.Why this approach (not
custom_providers[].models)Issue #15511 proposed scoping per-model reasoning to
custom_providers, which was closed as "not planned" because it:This PR uses a top-level
agent.reasoning_overridesdict — conceptually consistent withagent.reasoning_effort, supports all providers, and the key format (provider/model) matches what users see in/model.Resolution priority
/reasoning --sessionoverride (gateway only — PR fix(gateway): make /reasoning session-scoped by default #15533, unchanged)agent.reasoning_overrides(spelling-tolerant, this PR)agent.reasoning_effort(existing)Wired into
cli.py) — per-model override checked at agent constructiongateway/run.py::_load_reasoning_config) — sametui_gateway/server.py::_load_reasoning_config) — samecron/scheduler.py) — same/modelmid-session switch (agent_runtime_helpers.py::switch_model) — re-resolves reasoning_config on switch, and saves it to_primary_runtimefor correct fallback recoverychat_completion_helpers.py::try_activate_fallback) — re-resolves reasoning_config for the fallback model (best-effort, wrapped in try/except)Files changed (17 total)
Implementation:
hermes_constants.py— helper functionshermes_cli/config.py— DEFAULT_CONFIG + version bumpcli.py,gateway/run.py,tui_gateway/server.py,cron/scheduler.py— wire-upagent/agent_runtime_helpers.py—/modelswitch +_primary_runtimesnapshotagent/chat_completion_helpers.py— fallback re-resolutionTests (90 new test cases across 6 files)
Docs:
cli-config.yaml.example,website/docs/user-guide/configuration.md,docs/PER_MODEL_REASONING.mdBehavioral guarantees
reasoning_overrides: {}is the default — zero behavior change/reasoning --sessionstill wins over per-model overridesgemini-2.0-flashwill NOT match override keygemini-flashrestore_primary_runtime()returns reasoning_config to primary's valueFixes #21256