You Already Pay for Thinking You Don't Need: Reasoning Effort in Every Hermes Model Picker


It is 11:30pm and you ask Hermes to rename formatTs to formatTimestamp in utils/date.ts. It finishes in seven seconds — and the trace bills a few thousand thinking tokens at ultra reasoning effort. The model is not the problem. “Ultra” is a global default you set last week for one architecture review, and nobody has touched it since. Hermes has always let you dial reasoning effort; the controls were just scattered across /reasoning, config.yaml, and a Desktop submenu that only appeared on hover. Knowing the feature exists and actually using it were two different sets of people.

That changed with PR #110346: every model picker now asks for the effort level right after you pick the model. This guide walks the six places you can set effort (one-off run, session, the /model flag, the interactive pickers, hermes model, and Desktop), plus the precedence between them — so you pay for the thinking a task needs instead of the thinking you configured last week.

What reasoning effort actually controls

Reasoning effort is the thinking budget a model is allowed to spend before it answers. For models that expose the control, the level is translated into the request’s thinking/reasoning parameters; for models that do not, the setting is simply inert.

There is exactly one source of truth for the ladder, in hermes_constants.py:

VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh", "max", "ultra")

Seven levels, low to high. parse_reasoning_effort() turns a typed word into a config dict: a recognized level becomes {"enabled": True, "effort": "<level>"}, an empty string or an unrecognized word returns None (meaning “use the provider default”), and none is an explicit off switch — reasoning_effort: false and none both mean “do not think”.

The important part: this applies per request. On the same model, the quality gap between low and ultra is usually far smaller than the latency and cost gap. So the real question is never “which level is best” — it is “how many levels is this task worth”.

Scope How you set it Where it lands
One run only hermes --reasoning low -z "..." that process, nothing written
Current session (classic CLI) /reasoning high session override
Current session, with a model switch /model X --reasoning high session pin
Your global default /reasoning high --global agent.reasoning_effort
Every model you name agent.reasoning_overrides per-model, beats the global default
One auxiliary task hermes model → Configure auxiliary models… auxiliary.vision.reasoning_effort
Every subagent delegation.reasoning_effort the delegation block
One Desktop chat the composer reasoning pill that session

The smallest lever: one-off runs

If you are running a single command for a well-understood job, you do not need to touch config at all. The top-level --reasoning flag exists for exactly this:

# One-shot: a rename. Low is plenty.
hermes --reasoning low -z "rename formatTs to formatTimestamp in utils/date.ts"

# Interactive, but this session is cleaning up messy minutes
hermes chat --reasoning high

The help text is explicit that this overrides the effective effort for that run only; the persistent level still lives in config.yaml (or per model under agent.reasoning_overrides). For shell scripts, CI jobs, and batch file work this is the cleanest option: the effort travels with the command instead of quietly becoming tomorrow’s default.

Sessions: /reasoning and /model --reasoning

In the classic CLI, /reasoning is the dedicated command — it handles both the effort level and how reasoning is displayed:

/reasoning high           # this session only
/reasoning high --global  # write agent.reasoning_effort for future runs
/reasoning                # no argument: pick interactively
/reasoning show           # show/hide the reasoning stream (full prints it all)
/reasoning none           # turn reasoning off

PR #110346 made switching models and setting effort the same gesture. The /model parser now accepts --reasoning <level>, so one request carries both the pick and its effort:

/model openrouter/anthropic/claude-opus-4.8 --reasoning high

Three details that come up constantly:

  • The scope follows the model switch. Session by default; --global writes agent.reasoning_effort; --once snapshots the current value, applies the effort for a single turn, and restores it afterwards.
  • The effort is applied after the agent swap. That is not a nitpick: the switch path re-resolves reasoning_config from config.yaml, so writing the effort first would get clobbered. The ordering is deliberate.
  • A bad level errors instead of silently doing nothing. /model --reasoning turbo returns /model --reasoning takes none, minimal, low, medium, high, xhigh, max or ultra.

You can also skip the flags entirely. The interactive /model picker is now three stages — provider, model, then a third stage titled Reasoning effort for <model> that lists the full ladder plus a none (disable reasoning) row and a “keep current” row. When the route’s capability map says there is no reasoning control, that third stage is hidden rather than offering a dial that does nothing.

The Ink TUI (hermes --tui) runs the same three-stage flow; step 3 is labelled Reasoning effort (step 3/3) and emits model, provider, effort, and scope as one gateway command. On messaging platforms (Telegram, Slack, and friends), /model --reasoning goes through the same applier that /reasoning uses, so the two cannot drift apart.

hermes model: the effort step for the main model and every auxiliary task

hermes model is the entry point most people use first (see the hermes model reference). Before PR #110346 it asked about effort only on Copilot routes, and the auxiliary flow never asked at all.

Now:

  • After the main model is saved, one shared step follows: the seven levels, Disable reasoning, and Skip to keep the current value. Models the catalog marks as having no reasoning control skip the step.
  • The provider list gained a Reasoning effort for the current model… row for when you want to change only the effort.
  • Configure auxiliary models… (provider → model) ends with the same effort step plus a Provider default row that leaves the level to the provider. Those values are stored as auxiliary.<task>.reasoning_effort, or delegation.reasoning_effort for delegation.
  • The task list shows the level inline, e.g. openrouter · model · high, and Reset all to auto clears every override at once.
  • Blocks that deliberately have no such key — the MoA slots and memory_query_rewrite — skip the step. They are not missed; they just configure depth somewhere else.

Why this matters more than it looks: compression, title generation, and approval checks run on every turn. Dropping those from high to minimal (or off) saves more money than fine-tuning the main model’s level ever will.

Desktop: the composer pill, and per-task effort in Settings

The Desktop model picker lives in the composer, just left of the microphone. Before PR #109907 the only effort control was a hover submenu inside a model row; the combined pill read Deepseek V4 Flash · Med, users read that as “this model only does medium”, and the submenu stayed undiscovered.

Now effort has its own affordance:

  • A reasoning pill next to the model pill shows the level actually in effect for this surface (session value first, else the config default) and opens Thinking / Fast / Effort rows directly. It hides when the catalog reports reasoning: false for the active model, and stays visible while capabilities are still unknown, so it never flickers during the catalog fetch.
  • The model pill no longer folds the effort into its own label; each control says one thing.

Per-task effort on the Desktop side took two more PRs. #110435 added a Reasoning select to each row under Settings → Model → Auxiliary models: inherit · main model effort (the default, which removes that task’s override), Off, or a level — writing the same auxiliary.<task>.reasoning_effort key hermes model writes. The API contract behind it is worth knowing: omit the field to leave an override alone, pass an explicit null to clear it (back to inherit), pass a level to set it. An unknown value like turbo returns a 400 listing every accepted level instead of being dropped on the floor.

PR #110535 closed the loop on Desktop auxiliary pins: all 11 slots can be pinned, saving unrelated Settings no longer rewrites or drops those pins (config writes became section-scoped instead of writing a fully expanded snapshot back to disk), and when a review task is routed to a different model its reasoning effort is honoured rather than being swallowed by the parent session’s setting.

One gotcha: the Desktop app remembers a per-model effort/fast preset locally. That is a convenience for the app and does not change crons or subagents — for those, use hermes model or the config file.

How the layers stack, and where model_overrides fits

Precedence, highest first (this is also the runtime resolution order): session override (/reasoning --session, /model --reasoning) → per-model override agent.reasoning_overrides → global default agent.reasoning_effort. A config.yaml you can copy:

agent:
  # The global default; this is the line `/reasoning high --global` writes
  reasoning_effort: medium
  # Per-model: keys are spelling-tolerant (exact, dots/dashes interchangeable,
  # provider prefix optional), first match wins, and it beats the default above.
  # No `hermes config set` support for this key — edit the YAML directly.
  reasoning_overrides:
    "openrouter/anthropic/claude-opus-4.8": high
    "deepseek/deepseek-v4-flash": low

auxiliary:
  compression:
    reasoning_effort: minimal   # summarizing history needs no deep thought
  title_generation:
    reasoning_effort: none      # titles: just turn it off
  vision:
    reasoning_effort: ""        # empty = provider default

delegation:
  reasoning_effort: low         # subagents; leave empty to inherit the parent

model_overrides plays a different role here (see the model_overrides guide): it is where model metadata lives — context_window, supports_vision, supports_reasoning and friends. It does not set effort, but it decides whether the effort control exists at all: when the capability map says a route has no reasoning control, the hermes model step, the /model picker’s third stage, and the Desktop pill all hide together. So if you are on a local or brand-new model the catalog has never heard of, declare the capabilities first and the effort controls will show up.

Two exceptions worth internalizing:

  • background_review.reasoning_effort is ignored while the review stays on the main model. In that case the fork inherits the conversation’s reasoning config verbatim, which is what keeps its request bytes on the parent’s prompt-cache prefix. To get a different model and a different effort, set the review’s provider/model too — otherwise Hermes warns you once, on purpose.
  • MoA effort is configured per slot inside the preset, not on the auxiliary block: moa.presets.<name>.reference_models[].reasoning_effort and aggregator.reasoning_effort. Each reference can run at its own depth and the aggregator can go deeper — which is why the MoA blocks have no reasoning_effort key by design.

A starting setup that pays for itself

If you would rather not hold the whole precedence chain in your head, do these five things:

  1. Leave the main model at medium. Raise it for a single task with /model ... --reasoning high instead of editing the global default.
  2. Run hermes model → Configure auxiliary models… and set compression to minimal and title_generation to off. These run every turn, so they pay back immediately.
  3. Set delegation.reasoning_effort: low. Subagents usually execute a task that has already been decomposed; they do not need to re-plan it.
  4. Use hermes --reasoning low -z "..." for scripts and one-off jobs, so a temporary need never becomes a permanent default.
  5. If a long run stalls, check the turn budget before reaching for more thinking — that is what the max_turns guide is about. And on free-tier routes, the no-API-key setup pairs well with minimal.

The short version

Reasoning effort is not a “higher is better” tuning knob; it is a scoping problem. In one config.yaml you have separate dials for the session, per model, per auxiliary task, and per subagent. PR #110346 moved those dials onto the pickers you already use — all that is left is deciding how many levels each job deserves, so that one review last month stops paying ultra rates for a rename this month.