> ## Documentation Index
> Fetch the complete documentation index at: https://panopticon-cli.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Reasoning effort

> The canonical effort-level enum, how it's resolved, and where it's honored today

# Reasoning effort

**Reasoning effort** is the `--effort` level a coding-agent harness launches
at (Claude Code's `low`/`medium`/`high`/`xhigh`/`max`, and the nearest
equivalent on every other harness). Overdeck resolves it through one function,
`resolveEffort()` (`src/lib/agents/resolve-effort.ts`), on top of one enum,
`EFFORT_LEVELS` (`packages/contracts/src/effort.ts`) — instead of the ad hoc
`effort ?? 'high'` fallbacks and copied `'low' | 'medium' | 'high'` unions
scattered through earlier code.

## Levels

```ts theme={null}
EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max']
```

`xhigh` was added in Opus 4.7 (between `high` and `max`); `max` predates it
(Opus 4.6+/Sonnet 4.6). The default is **`high`** — nothing resolves to
`xhigh` or `max` unless something explicitly asked for it.

## Where effort comes from

`resolveEffort()` walks a fixed precedence chain and returns the first layer
that supplies a valid level, plus which layer won:

| Precedence | Source | Config key |
| - | - | - |
| 1 (highest) | Explicit override (a `--effort` flag, an API/RPC field) | n/a — passed directly |
| 2 | xBRIEF item | item `metadata.effort` |
| 3 | xBRIEF plan | plan `metadata.effort` |
| 4 | Tier | `tiered_execution.tiers.<name>.effort` |
| 5 | Sub-role | `roles.<role>.sub.<name>.effort` |
| 6 | Role | `roles.<role>.effort` |
| 7 | Project | `projects.yaml` project entry's `effort` |
| 8 (lowest) | Default | `high` |

An invalid **explicit** value throws `InvalidEffortError`. An invalid item,
plan, or project value is skipped silently (a project value also adds a
warning) and resolution falls through to the next layer — configuration typos
degrade rather than crash a spawn.

## Config keys

```yaml theme={null}
# config.yaml
roles:
  work:
    model: workhorse:mid
    effort: high
    sub:
      security:
        model: claude-opus-5-5
        effort: max

tiered_execution:
  tiers:
    frontier:
      model: claude-opus-5-5
      harness: claude-code
      difficulties: [expert]
      effort: xhigh
  escalation:
    effort_first: true
```

```yaml theme={null}
# projects.yaml
projects:
  myproject:
    path: /home/user/Projects/myproject
    effort: medium
```

```json theme={null}
// an xBRIEF plan or item's metadata (free-form key, like metadata.difficulty)
{ "metadata": { "effort": "high" } }
```

## Clamping

Once a level is resolved, `clampEffort()` checks it against what the target
model and harness actually support (`supportedEffortLevels()` intersects the
model's `effortLevels` from `model-capabilities.ts` with the harness's
`effortLevels` from `harness-behavior.ts`; either side being unrestricted
contributes all five levels):

* If the resolved level is supported, it's used as-is.
* If not, the highest supported level *below* it is used instead (`max`
  requested on a harness with no `max` falls to `xhigh`).
* If nothing supported ranks below it, the lowest supported level is used
  (`low` requested against a model that only supports `high`/`max` becomes
  `high`).
* If the model and harness restrictions don't overlap at all, the requested
  level is kept as-is and flagged with a warning rather than dropped.

Pi (`ohmypi`) and Muse Code harness rows stop at `xhigh`, so `max` clamps to
`xhigh` for them even though their CLIs parse `max`. Kimi K3 lists all five
levels and translates them at launch (`medium`→`high`, `xhigh`→`max`). See
[How each harness receives effort](/configuration/harnesses#how-each-harness-receives-effort).

**Config validation rejects; a resolved flag clamps.** Setting an
unsupported level in `config.yaml` (`roles.<role>.effort`,
`roles.<role>.sub.<name>.effort`, or `tiered_execution.tiers.<name>.effort`)
fails config load with `effortConfigErrors()`'s `is not supported by <model>`
message — you have to fix the file. An explicit `--effort` flag or a
resolved runtime value that turns out unsupported is clamped instead, with a
warning printed to stderr — a spawn never hard-fails over it.

## Tier escalation

`tiered_execution.escalation.effort_first` raises an item's effort one level
before promoting it to the next tier's model:

```yaml theme={null}
tiered_execution:
  escalation:
    enabled: true
    retries_at_tier: 1
    max_promotions: 2
    effort_first: true
```

For an item that has exhausted its retries at the current tier,
`decideEscalation()` (`src/lib/agents/tier-escalation.ts`) walks this order
(D9):

1. No effective difficulty → `block`.
2. Promotions already at `max_promotions` → `block`.
3. Already at `expert` → `block`.
4. Attempts below `retries_at_tier` → `retry`.
5. `effort_first` is on and this item has not raised effort yet → `raise-effort`
   one level above its current staffed effort (via `supportedEffortLevels()`,
   so the raise never exceeds what the model/harness pair accepts) instead of
   promoting to the next tier's model.
6. Otherwise → `promote` to the next tier's model, exactly as before
   `effort_first` existed.

Only one effort raise happens per tier — once `effectiveEffort` is set on the
item's override, the next escalation falls through to step 6 and promotes.
A raise does not count toward `max_promotions`: `applyEscalationAction()`
leaves `promotions` unchanged for a `raise-effort` action, only incrementing
it for a `promote`. When there is no supported level above the current
effort (already at `max`, or the model/harness pair caps below the next
level), `decideEscalation()` promotes instead of raising.

`decideEscalation()` and `applyEscalationAction()` are pure functions with no
caller yet — no supervisor-blocked or verification-failed event wires a real
escalation trigger. That connection is tracked in
[#4512](https://github.com/eltmon/overdeck/issues/4512).

In Settings, each tiered-execution crew has its own Effort select
(`aria-label="Crew effort"`, inherit or one of the five levels), the
Escalation panel has a "Raise effort before promoting the model" switch for
`effort_first`, and each review sub-role under Roles Models has its own
Effort select.

## Relaunch keeps effort

`state.json` stores the launched `effort` and its `effortSource` (PAN-4253).
Every relaunch path — resume, restart, crash recovery, the messaging
fresh-launch fallback, session rotation, crash respawn, and cloister model
handoff — goes through one helper, `resolveRelaunchEffort()`
(`src/lib/agents/relaunch-effort.ts`):

* A persisted `effort` wins on relaunch whatever its source. A
  `roles.<role>.effort` config change reaches new spawns only; it does not
  retroactively change an agent that already has a pinned effort.
* The persisted level is always re-clamped to the relaunch's model and
  harness, so a model or harness switch (`max` on claude-code relaunched
  under `ohmypi`) clamps down and the *clamped* level is what gets stored
  back — never the original request.
* A legacy or effort-less agent resolves through the normal
  role → project → default chain on its first relaunch, and that resolved
  value is then pinned for every relaunch after it.

## Changing effort on a running session

Pick a level in the composer's effort picker while a session runs. For a
claude-code conversation the picker calls
`POST /api/conversations/:name/thinking-level`; for an agent-backed panel
(a work agent or any other claude-code agent) it calls
`POST /api/agents/:id/effort`. Both take `{ "level": "<level>" }`.

Claude Code has no control channel, so Overdeck types `/effort <level>` into
the session's pane. It then waits up to 10 seconds for Claude Code's
`Set effort level to <level> (…)` confirmation in the session transcript, and
stores the level only after that confirmation arrives:

* The level is first clamped to the model, the same as at launch.
* A session that is mid-turn is refused (409 `busy`), because Claude Code
  queues typed input during a turn and the change could land after the
  window. A pending permission prompt or a subagent holding the input is also
  refused (409), and nothing is typed.
* No confirmation within 10 seconds answers 504 `not-confirmed`; a rejected
  level answers 422 `effort-rejected`. In both cases the stored effort is
  unchanged.
* A confirmed change is stored as `explicit`: on the conversation row, or in
  the agent's `state.json`, where [relaunch](#relaunch-keeps-effort) pins it.

Claude Code itself saves `low`, `medium`, `high`, and `xhigh` as the default
for new sessions of that model in `~/.claude/settings.json` (`max` is
session-only). Overdeck does not change that file. Its own sessions always
launch with `--effort`, so they are not affected, but your own interactive
`claude` sessions of that model start at the saved level.

**The effort chip.** For a running session the picker reads
`<Level> · <source>`, for example `High · default` or `Low · set`. The source
is the layer the level came from: `set` (an explicit choice), `item`, `plan`,
`tier`, `sub-role`, `role`, `project`, or `default`. `terminal` means the
session transcript shows a different level than Overdeck launched or stored,
so someone ran `/effort` in the native terminal. The chip's tooltip says
whether the level was **observed**, that is read from the session
transcript (claude-code 2.1.280 and later write the effort on every
assistant turn).

## What honors it today

* `pan start <id>` for work-agent spawns.
* Definition-less role runs (review sub-roles, the standing supervisor) and
  their codex/omp launcher fields (`getRoleRuntimeBaseCommand`,
  `getCodexLauncherFields`, `getOhmypiLauncherFields` in
  `src/lib/agents/runtime-command.ts`).
* Every relaunch path: resume (`resumeAgent`), restart (`restartAgent`),
  crash recovery (`recoverAgent`), the messaging fresh-launch fallback,
  session rotation, crash respawn, and cloister model handoff — see
  [Relaunch keeps effort](#relaunch-keeps-effort).
* Conversations — dashboard creation (sidebar and Home composer effort
  pickers, and the **New conversation with options…** dialog, whose default
  and its source come from `GET /api/effort/default?model=&harness=&issue=`), resume, restart-all, switch-model (clamped to the new model),
  and `pan handoff`/`pan fork --effort` (default: inherit the source
  conversation's effort). pi's `off`/`minimal` are stored as `low`.
* Live change on a running session — claude-code conversations and
  claude-code agents through `/effort`, and codex, ACP, OpenCode, and pi
  conversations through their control channels — see
  [Changing effort on a running session](#changing-effort-on-a-running-session).
* Every harness adapter — codex (TUI and app-server), OpenCode and Kimi via
  ACP, native Kimi Code, Pi and Muse — receives a resolved, clamped level
  from its producer and never applies its own default (see
  [How each harness receives effort](/configuration/harnesses#how-each-harness-receives-effort)).
* The review parent's fresh spawn — `roles.review.effort` (the resumed-session
  branch keeps the saved session's effort unchanged) (PAN-4256).
* The test role's dispatch — both the automatic post-review queue and the
  manual re-dispatch route — `roles.test.effort` (PAN-4256).
* `pan worker run --effort <level>` (PAN-4256).
* `pan spawn --effort <level>` — item `metadata.effort` → plan
  `metadata.effort` → `roles.worker.effort` → `high`; only claude-code and
  codex panes get a mapped `--effort`/`model_reasoning_effort` flag, every
  other harness launches with no effort flag (PAN-4256).
* `pan flywheel start` — `roles.flywheel.effort` at the next start; a paused
  Flywheel resumed keeps the effort it started with (PAN-4256).
* `POST /api/agents` (an explicit `effort` field in the body) and
  `POST /api/agents/:id/restart` (ditto, pinning the relaunch as `explicit`)
  (PAN-4256).
* `pan start --remote` work agents on Fly — `roles.work.effort` (PAN-4256).
* Tiered-execution work spawns — a registered swarm slot
  (`resolveSlotTierSpawnParams`) and the single work agent
  (`resolveSingleWorkTierSpawnParams`) both staff through `resolveStaffing()`,
  which resolves effort via the same precedence chain with the item's tier as
  layer 4, and `pickLaunchEffort()` (`src/lib/agents/launch-effort.ts`) lets
  that staffed value win over `pan start`'s non-explicit default while still
  letting an explicit `--effort` outrank it.
* The standing tier supervisor (`spawnTierSupervisor`) and the merge-train's
  re-review dispatch (`reDispatchVerification` in
  `src/lib/cloister/merge-train-deps.ts`), both via `resolveEffort({ role:
  'review', ... })`.
* Planning — `pan plan --effort`, the dashboard Plan dialog (default `high`,
  all five levels), and the planning half of `pan start` on an unplanned
  issue. The start-planning API rejects an unknown level with 400; the
  planner resolves with role `plan` (`roles.plan.effort` → project → `high`)
  and is clamped to the planning model and harness. The planning prompt's
  effort section names the resolved level, and `high` and above add the
  probe pass.
* `pan strike` (`roles.strike.effort` → project → `high`); `--dry-run`
  prints the resolved level and its source.
* Recording — every new `sessions.json` line carries the launch `effort`, and
  every new cost-ledger row (`cost_events.effort`, `events.jsonl`) carries the
  effort its request ran at: for claude-code the level on the transcript
  record, else the session's launch effort. Rows recorded before this change
  read as `unrecorded`.
* Reporting — `pan cost effort [--issue <id>] [--json]` groups spend by
  effort, one bucket per level plus `unrecorded`.
* Display — Agents page History rows and the detail header show
  `<level> (<source>)`; `pan status` prints `Effort:`; `pan show` prints
  `effort` and includes `effort`/`effortSource` in `--json`. The conversation
  composer chip shows it for conversations.

Everything else — issue-view and conversation-row display — still resolves
effort ad hoc and is tracked in sibling issues:

| Surface | Issue |
| - | - |
| Effort on issue-view agent rows, conversation rows, and Agents-page conversation entries | [#4510](https://github.com/eltmon/overdeck/issues/4510) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.