dirge-code / dirge-code/dirge

effort "off" does not disable thinking on Sonnet 5 / Opus 5 / Fable (DisableWire::None omits the parameter, which no longer means off)

Open
#827 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
348
Forks
36
Avg merge
4h 44m
Merged PRs (30d)
30

Description

## Claim

From the 0.24.1 changelog, introducing `/effort`:

> `default` clears the override; **`off` is a real level that disables reasoning**.

On current Anthropic models it does not. `effort: "off"` (and `/effort off`) leaves thinking fully enabled on `claude-sonnet-5`, `claude-opus-5` and `claude-fable-5`, so a user who explicitly opts out still pays the latency and the reasoning tokens — silently, on the one setting whose whole purpose is opting out.

## Cause

`adapter.rs` gives each provider a `DisableWire` describing how thinking is turned off. Anthropic's is `None` — "omit the parameter":

```rust
("anthropic", EffortWire::AnthropicBudget, DisableWire::None),
```

Omitting `thinking` was a correct disable for Claude 4.6 and earlier. It stopped being one on the current generation:

| Model | `thinking` omitted |
|---|---|
| Sonnet 4.6 / Opus 4.6 | no thinking — omit still works |
| Sonnet 5 | **thinks** (adaptive by default) |
| Opus 5 | **thinks** (adaptive by default) |
| Fable 5 | **always thinks**, cannot be disabled |

So the wire shape didn't change; what omitting it means did.

## The payload already exists in the codebase

Anthropic is one of only three providers left on `DisableWire::None`; five others carry real disable wires (`ThinkingToggle`, `ChatTemplateKwargs`, `GeminiZeroBudget`, `OllamaThink`).

Notably **GLM** — which mimics Anthropic's API — is on `ThinkingToggle`, which emits:

```json
{"thinking": {"type": "disabled"}}
```

That is Anthropic's own documented shape. dirge already sends exactly the payload Anthropic needs; it just doesn't send it to Anthropic.

## Why the fix is not a one-line table change

Model support is uneven, so a blanket switch to `ThinkingToggle` would trade this bug for 400s:

- **Sonnet 5** accepts `{"type": "disabled"}`.
- **Opus 5** accepts it **only at effort `high` or below** — pairing it with `xhigh`/`max` is rejected. Since `off` means no thinking at all, that combination shouldn't arise, but the constraint is worth encoding rather than assuming.
- **Fable 5 rejects it outright** — thinking is unconditional there. `off` can never be honoured; warning that the level is unavailable seems more honest than silently accepting it.
- **Claude 4.6 and earlier** must keep the current omit-the-parameter behaviour.

So it wants to be per-model, not per-provider — which may mean `DisableWire` needs a model-aware variant, or the choice moves to where the model id is known.

## Impact

This is what forces a non-thinking tier to be Haiku 4.5. There is currently no way to get a fast, non-thinking turn out of Opus 5 or Sonnet 5 through dirge, even though the API supports it.

Happy to put up a PR if the shape above looks right — say if you'd rather have it structured differently (e.g. keep `DisableWire` per-provider and gate on the model at the call site).

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.