How hard the model thinks
A reasoning model spends time thinking before it answers, and how much is a per-request
setting: reasoning_effort on the OpenAI wire, which OpenRouter forwards to the model. It is the
single biggest lever on how long a round takes.
The rungs
Resolved by ReasoningEffortResolution where AgentChatClient builds the round's one ChatOptions,
so a single decision covers every provider call the round makes, tool-loop iterations included.
First non-blank wins.
| # | Where | Who sets it |
|---|---|---|
| 1 | ThreadComposer.Effort — the /effort pick |
the person chatting |
| 2 | AgentConfiguration.ReasoningEffort — reasoningEffort: in the agent's front matter |
the agent's author: this is where the WORK is known |
| 3 | ModelDefinition.ReasoningEffort on the LanguageModel node |
an admin, for the whole deployment |
| 4 | ModelTierDefinition.ReasoningEffort — the agent's tier, else chat |
the tier registry (Provider/Tier/*) |
| — | nothing is sent | the provider decides |
The provider's default is not neutral, and it is what the Executive Assistant ran at. Measured on
OpenRouter's model API (reasoning block of GET /api/v1/models, 2026-09-30): z-ai/glm-5.3 is
mandatory: true, supported_efforts: [max, high, low], default_effort: max. Before rungs 2
and 4 existed, a thread started without effort on an agent that pins nothing (the Executive
Assistant) sent no reasoning_effort and no max_tokens at all, so GLM-5.3 thought at max — an
earlier round of nine cheap tool calls took 3 min 11 s that way (AI/RoundTiming).
Rung 4 is the declared default. The shipped tiers carry: utility medium · chat medium ·
reasoning high · review max · coding xhigh. An agent that names no tier is an interactive one
and resolves against chat, so an agent authored without an effort still runs at medium. A tier
node with a blank value inherits the shipped value for its id — tier nodes are seeded
create-if-absent, so every node that predates the field is blank, and reading blank as "send
nothing" would have made the default a no-op on every existing deployment. An operator changes the
default by editing the tier node (reasoningEffort), which wins over the shipped value.
A blank rung 1 is declining, not choosing. Picking Model default writes an empty string, which hands the decision back to rungs 2–4.
Every shipped agent declares its effort
No blanks: AgentReasoningEffortDeclaredTest fails the build on an agent under any */Agent/
folder without a known reasoningEffort. Chat and assistant agents run at medium (a person is
waiting), utility micro-jobs at medium (moderate, not none — they still decide something),
coding agents at xhigh (a wrong patch costs more than a slow one). The rest are justified row by
row.
| Agent | Tier | Effort | Why |
|---|---|---|---|
| Assistant (default) | — | medium | interactive chat |
| Executive Assistant | — | medium | interactive assistant (was: provider default = GLM max) |
| Email Router | — | medium | assistant work on an inbound mail |
| Researcher | — | medium | a chat's delegate — the person is still waiting |
| Worker | — | medium | a chat's delegate for bulk writes, not code |
| Tutor | — | medium | interactive teaching |
| TrainingSim | — | xhigh | writes the C# cell a learner runs — writing code |
| Description Writer · Node Initializer · Pull Request Writer · Thread Namer | utility | medium | utility |
| Notification Triage | light (= chat) | medium | utility decision per event |
| Log Triage | reasoning | high | analysis that ends in a ticket, not code |
| Feedback Agent | — | medium (from the chat tier) |
interactive capture. Its node is JSON, and GitSync reads JSON strictly, so an image that predates the field would refuse the whole snapshot. The field goes into the file once this release is on every portal; until then rung 4 gives it medium. The guard lists it by name and fails once the file declares one |
| RemoteControl · Voice · Music | — / chat | medium | interactive; latency is the product |
| Quality Time · Quality Time Deutsch | reasoning | medium | spoken interactive host — the tier would say high |
| bug-triage | coding | xhigh | writes the fix and its test |
| devops | — | xhigh | coding-tier work: proposes code and PR changes |
| reviewer (Governance) | — | high | validates a proposal against evidence; analysis, not code |
| live-defect-triage · loki-error-triage · platform-update · pr-babysitter · triage | reasoning | high | diagnosis and classification, not code |
| pr-reviewer | review | max | decision D1 of Governance/PullRequestSteward — one careful pass per head; the round-length fix is the tool-call budget (Plugins #2577), not a lower effort |
Satellite repositories declare theirs the same way (see their agent folders).
Sent in the model's own vocabulary
Providers do not share one vocabulary, and OpenRouter maps an unsupported level to "the nearest
supported" one — which for a level BETWEEN two supported ones is an undocumented tie. GLM-5.3 has no
medium (between its low and high) and no xhigh (between its high and max), so the chat
default and the coding default would each have been a coin the gateway flips. The OpenAI-wire client
(OpenAIReasoningEffortWire) decides instead: the level is snapped to the weakest accepted level at
or above it (ReasoningEffortLevels.Snap) and sent under that exact spelling, so a request is never
silently weakened.
| Resolved | GLM-5.3 receives | gpt-5.2 | Claude 5 line |
|---|---|---|---|
| medium | high |
medium |
medium |
| high | high |
high |
high |
| xhigh | max |
xhigh |
xhigh |
| max | max |
xhigh |
xhigh ¹ |
The accepted levels come from ModelDefinition.SupportedReasoningEfforts on the node, else the
built-in ModelReasoningEfforts table (read from OpenRouter's model API, like ModelPricing), else
"unknown" — and then the level is sent unchanged. ¹ The abstraction's top member is ExtraHigh, so a
resolved max leaves the engine as xhigh: on a model that accepts only max it is sent as max,
on one that accepts both it is sent as xhigh.
One accepted warning suppression. Sending a spelling the abstraction cannot express (max)
means setting ChatCompletionOptions.ReasoningEffortLevel, which the OpenAI SDK marks for evaluation
(OPENAI001). OpenAIReasoningEffortWire disables that one diagnostic around that one assignment,
with a disable/restore pair and the reason inline. That is the same scoped form the repository uses
for CS0618 and CA1416. This is a deliberate, recorded exception, not a precedent for silencing
warnings. If the SDK promotes the API to stable, delete the pair.
Choosing it
- In the chat —
/effort, or the ⚡ chip under the composer. The pick lives on your composer and follows you across tabs and threads until you change it. - On the agent —
reasoningEffort:in its front matter (none·minimal·low·medium·high·xhigh·max). The agent's repository is the place to change it, never a live edit: an agent in a GitSynced package is overwritten by the next sync. - On the model, for everyone —
reasoningEfforton theLanguageModelnode. - On the tier —
reasoningEffortonProvider/Tier/<id>.
An unknown spelling costs the SETTING, never the model: the round logs one warning naming the spelling and the rung it came from, and sends nothing.
A known distortion that no effort setting fixes
An OpenRouter key with PII masking enabled redacts names, e-mail addresses and code in every
request behind it — the model sees [PERSON_NAME] and [EMAIL] instead of the text. That is an
ACCOUNT setting on the key, not a per-request one, so it distorts every model on that key equally
and no effort level changes it. Read a round that "cannot see" a name or an address against the
key's settings before tuning the effort.
Under the Claude Code harness
/effort belongs to the CLI there, and the portal forwards the command 1:1 — the levels are the
CLI's own and the chip shows what the CLI reports. The mesh rungs above apply to the MeshWeaver
harness, which is the one that talks to a provider itself.
Why the picker takes VALUES, not nodes
/effort is the first Pick skill whose options are not mesh nodes. SkillAction.Choices is that
shape: a fixed list of {value, label, description} on the skill's own front matter, rendered by the
same widget, with the same keyboard handling and the same row anatomy as /agent and /model, and
written to the named composer field verbatim. The alternative — seeding five ReasoningEffort nodes
into every mesh before the setting can be chosen at all — makes a word the provider already
understands into a deployment step.
Any skill can use it: declare choices: instead of query: under action:. A skill that declares
both keeps its query, so nothing existing changes meaning.
What the menu shows
One row shape for every pick (ThreadChatView.PickerRow): icon column, name, one meta line, and a ✓
on whatever is selected right now. A model's meta line is derived from its definition — how hard it
thinks, and its per-million prices — because a model node carries no description and a row that only
repeats the wire id tells the person choosing nothing they did not already type.
Models group under their provider's title by the nearest title that OWNS their namespace, not by the
namespace itself. Every model the catalog sync writes sits directly under its provider with the
vendor prefix inside the id (Provider/OpenRouter + openai/gpt-5.2); a model created by hand
splits that id at the slash and lands one level deeper (Provider/OpenRouter/z-ai + glm-5.3). Both
are the same model under the same provider, and before this the second one rendered as an untitled
row after the whole block — configured, pinned to the top of its provider by Order: -2, and
unfindable by anyone scrolling for it.