| 1 | # Reasoning controls by provider |
| 2 | |
| 3 | Reasonix exposes a single `/effort` knob (and the per-provider `effort` / |
| 4 | `thinking` config fields), but OpenAI-compatible backends disagree on *how* |
| 5 | chain-of-thought is requested on the wire. The `openai` provider adapts the |
| 6 | request shape per backend; this table is the reference for which protocol each |
| 7 | known backend uses and which parameters it honours or ignores. |
| 8 | |
| 9 | ## Auto-detected backends |
| 10 | |
| 11 | These are recognised by base URL (see `internal/provider/openai/host.go`) and |
| 12 | get a tailored request shape automatically — no extra config needed. |
| 13 | |
| 14 | | Provider | Base URL | Reasoning control | `/effort` levels | Notes | |
| 15 | |----------|----------|-------------------|------------------|-------| |
| 16 | | DeepSeek V4 Flash | `api.deepseek.com`, `*.deepseek.com` | `thinking.type` + `reasoning_effort` (depth) | `auto`, `disabled`, `low`, `high`, `max` | Thinking on by default; `disabled` turns it off via `thinking.type=disabled`. Compatibility input `medium` normalizes to `high`, while `xhigh` normalizes to `high`. Reasoning is replayed on every historical assistant turn that carries it, including turns without tool calls. | |
| 17 | | DeepSeek V4 Pro | `api.deepseek.com`, `*.deepseek.com` | `thinking.type` + `reasoning_effort` (depth) | `auto`, `disabled`, `low`, `high`, `max` | Thinking on by default; `disabled` turns it off via `thinking.type=disabled`. Compatibility inputs `medium` and `xhigh` normalize to `high`. Reasoning is replayed on every historical assistant turn that carries it, including turns without tool calls. | |
| 18 | | MiniMax M3 | `api.minimaxi.com`, `*.minimaxi.com` | `thinking.type` (`adaptive`\|`disabled`) | `auto`, `adaptive`, `disabled` | No depth scale; `reasoning_effort` is omitted. | |
| 19 | | Zhipu GLM | `open.bigmodel.cn` / `*.bigmodel.cn`, `api.z.ai` / `*.z.ai` | `thinking.type` (`enabled`\|`disabled`) | `auto`, `enabled`, `disabled` | **`reasoning_effort` is silently ignored** by the endpoint, so reasoning is driven purely through `thinking.type`. | |
| 20 | |
| 21 | ## Explicit per-model scales |
| 22 | |
| 23 | | Provider/model | Base URL | Reasoning control | `/effort` levels | Notes | |
| 24 | |----------------|----------|-------------------|------------------|-------| |
| 25 | | Kimi CN/Global `kimi-k3` | `api.moonshot.cn/v1`, `api.moonshot.ai/v1` | `reasoning_effort` | `low`, `high`, `max` | Always thinks; defaults to `max`. Reasonix replays the complete assistant message, uses `max_completion_tokens`, and omits K3's fixed sampling fields. | |
| 26 | | Custom Kimi K3 gateway | Any OpenAI-compatible K3 endpoint | `reasoning_effort` | `low`, `high`, `max` | Select `reasoning_protocol = "kimi-k3"` to opt into K3's complete-message replay and request shape. | |
| 27 | | OpenCode Go `kimi-k3` | `opencode.ai/zen/go/v1` | `reasoning_effort` | `high`, `max` | Relay-specific scale; defaults to `max` and keeps the relay's standard OpenAI-compatible request shape. | |
| 28 | | Token Rhythm DeepSeek V4 | `tokenrhythm.studio/v1` | DeepSeek `thinking.type` + `reasoning_effort` | Model-specific DeepSeek scale | Selected through the preset's model override, independent of the gateway host. | |
| 29 | | Token Rhythm GLM 5/5.1/5.2 | `tokenrhythm.studio/v1` | GLM `thinking.type` (`enabled`\|`disabled`) | `auto`, `enabled`, `disabled` | Selected through the preset's model override; `reasoning_effort` is omitted. | |
| 30 | |
| 31 | On the Token Rhythm endpoint, exact GLM model IDs (`glm-5`, `glm-5.1`, and |
| 32 | `glm-5.2`) automatically select the official GLM request shape even when an |
| 33 | existing configuration has no `reasoning_protocol` field. The endpoint check |
| 34 | keeps unrelated mixed-model gateways backward-compatible. A `model_overrides` |
| 35 | entry with explicit `reasoning_protocol = "glm"` remains available for aliases |
| 36 | and custom model IDs. While GLM thinking is enabled, Reasonix retains and |
| 37 | returns the original `reasoning_content` unchanged in later history, as required |
| 38 | by GLM interleaved and preserved thinking. |
| 39 | |
| 40 | For a custom gateway that serves Kimi K3, select **Kimi K3 reasoning** in the |
| 41 | provider editor's advanced reasoning protocol field, or configure it directly: |
| 42 | |
| 43 | ```toml |
| 44 | [[providers]] |
| 45 | name = "my-kimi-gateway" |
| 46 | kind = "openai" |
| 47 | base_url = "https://my-gateway.example.com/v1" |
| 48 | model = "kimi-k3" |
| 49 | api_key_env = "MY_KIMI_API_KEY" |
| 50 | reasoning_protocol = "kimi-k3" |
| 51 | ``` |
| 52 | |
| 53 | This explicit protocol is needed when the gateway host cannot be safely |
| 54 | auto-detected. It preserves `reasoning_content` in later assistant history, |
| 55 | uses `max_completion_tokens`, and omits K3's fixed sampling fields. Do not add |
| 56 | it to the curated OpenCode Go preset: that relay intentionally keeps its |
| 57 | standard OpenAI-compatible request shape and its own `high`/`max` scale. |
| 58 | While this protocol is selected, Reasonix always exposes K3's fixed |
| 59 | `auto`/`low`/`high`/`max` effort menu with `max` as the protocol default; |
| 60 | persisted `supported_efforts` metadata is retained but does not override it. |
| 61 | |
| 62 | ## DeepSeek Anthropic-compatible endpoint |
| 63 | |
| 64 | The default official DeepSeek provider uses Chat Completions at |
| 65 | `https://api.deepseek.com`, with the independent [`web_search` tool](WEB_SEARCH.md) |
| 66 | using Messages for search. The optional `deepseek-anthropic` preset remains |
| 67 | available. When selected for the main conversation, Reasonix emits |
| 68 | `thinking.type=enabled|disabled` with `output_config.effort`, replays unsigned |
| 69 | DeepSeek thinking blocks from every historical assistant turn that carries |
| 70 | reasoning when the request declares tools (tool-call turn or not), omits unsupported |
| 71 | images, and relies on DeepSeek's automatic prefix cache instead of ignored |
| 72 | `cache_control` markers. |
| 73 | |
| 74 | The preset exposes the same model-specific effort scale for Flash and Pro: |
| 75 | `auto`, `disabled`, `low`, `high`, and `max`. The Anthropic-compatible endpoint |
| 76 | accepts `low|high|max` on the wire. Legacy `medium` and `xhigh` both normalize |
| 77 | to `high`. |
| 78 | |
| 79 | The OpenAI-compatible DeepSeek path follows the same replay rule when a request |
| 80 | declares tools: every historical assistant turn with stored `reasoning_content` |
| 81 | is serialized back verbatim, whether or not that turn called a tool. Without |
| 82 | tools, DeepSeek ignores this field and does not concatenate it into context. If |
| 83 | an old session still fails with the provider's specific reasoning pass-back HTTP |
| 84 | 400, Reasonix rebuilds only the provider-visible projection of the old history, |
| 85 | retries once, and leaves later turns on the normal replay path; canonical |
| 86 | session history remains unchanged. |
| 87 | |
| 88 | ## Missing-reasoning recovery |
| 89 | |
| 90 | Adapters own replay requirements. Complete DeepSeek Chat responses can use empty |
| 91 | `reasoning_content`; compatible Responses endpoints can omit absent reasoning |
| 92 | items. These paths continue without an extra generation. Strict contracts allow |
| 93 | one recovery for missing or unfinished required proof, preferring repairable |
| 94 | history over exact regeneration. The two recoveries do not stack, and neither |
| 95 | changes the selected model or protocol. Client-truncated proof is never replaced |
| 96 | with an empty field. |
| 97 | |
| 98 | ## Everything else (standard `reasoning_effort`) |
| 99 | |
| 100 | Any other OpenAI-compatible backend falls through to the standard |
| 101 | `reasoning_effort` scale (`low`\|`medium`\|`high`). A resolved provider/model |
| 102 | entry may explicitly advertise a different supported scale; in that case |
| 103 | Reasonix preserves those declared values instead of applying the generic |
| 104 | ceiling. Curated per-model capability metadata can opt into another scale as |
| 105 | shown above. |
| 106 | |
| 107 | Surveyed popular providers that need **no special handling** because they |
| 108 | already follow the standard convention: |
| 109 | |
| 110 | Qwen (`dashscope.aliyuncs.com`), Yi |
| 111 | (`api.01.ai`), SiliconFlow (`api.siliconflow.cn`), Stepfun (`api.stepfun.com`), |
| 112 | Groq (`api.groq.com`), Together (`api.together.xyz`), OpenRouter |
| 113 | (`openrouter.ai`), Perplexity (`api.perplexity.ai`), xAI (`api.x.ai`). |
| 114 | |
| 115 | For a backend that uses a binary `thinking.type` toggle but is **not** |
| 116 | auto-detected, set the vendor-agnostic `thinking` field on the provider entry: |
| 117 | |
| 118 | ```toml |
| 119 | [[providers]] |
| 120 | name = "my-glm-proxy" |
| 121 | kind = "openai" |
| 122 | base_url = "https://my-gateway.example.com/v1" |
| 123 | model = "glm-4.6" |
| 124 | api_key_env = "MY_API_KEY" |
| 125 | thinking = "disabled" # enabled | disabled — emits thinking.type |
| 126 | ``` |
| 127 | |
| 128 | ## Troubleshooting |
| 129 | |
| 130 | If a model keeps thinking when you asked it not to (or vice versa): |
| 131 | |
| 132 | 1. Check the table above — a backend may **ignore** the parameter you set |
| 133 | (e.g. Zhipu ignores `reasoning_effort`; use `thinking`/`/effort` instead). |
| 134 | 2. If the backend isn't auto-detected, set the explicit `thinking` field. |
| 135 | 3. If the backend uses a non-OpenAI protocol entirely (e.g. Baidu Wenxin), the |
| 136 | `openai` kind cannot drive its thinking mode — that needs a dedicated |
| 137 | provider kind. |
| 138 | |
| 139 | Distinguishing "provider ignores the field" from a Reasonix bug starts here: |
| 140 | the request shape Reasonix emits is fixed per the table, so a mismatch between |
| 141 | the table and observed behaviour is the provider's, not Reasonix's. |
| 142 | |
| 143 | ## Reasoning replay and interrupted execution |
| 144 | |
| 145 | Replay contracts belong to adapters: DeepSeek Chat retains its empty |
| 146 | `reasoning_content` fallback; DeepSeek Responses can omit an absent reasoning |
| 147 | item but retains items actually returned. Anthropic unsigned thinking and native |
| 148 | Claude signed thinking have separate requirements. Missing content or proofs |
| 149 | never produce fabricated blocks. Unknown gateways do not gain extra empty-value |
| 150 | compatibility from a DeepSeek model name; explicit protocol configuration applies. |
| 151 | |
| 152 | Anthropic preserves initial thinking, signature fragments, signed empty text, and |
| 153 | separate signed blocks. Responses preserves full reasoning items and uses the |
| 154 | completed response's final snapshot for the same item ID. Missing, explicitly |
| 155 | empty, client-truncated, and unfinished states are distinct. Required truncated or |
| 156 | unfinished reasoning cannot use an empty fallback to release tool execution. |
| 157 | |
| 158 | Compatibility conversion runs before strict recovery. Native Claude's complete, |
| 159 | unsigned thinking from a non-tool assistant turn can become ordinary assistant |
| 160 | text in the outbound view. It is not applied to client/server tool turns, mixed |
| 161 | signed/unsigned proofs, redacted data, or incomplete/truncated reasoning. Raw |
| 162 | local thinking stays intact; no signature is invented. |
| 163 | |
| 164 | An unknown Anthropic gateway does not acquire Claude's signature requirement |
| 165 | from its model name or adaptive-thinking setting. With thinking replay enabled, |
| 166 | its actually received unsigned blocks are retained without adding a signature. |
| 167 | An absent block is not synthesized. Explicit `reasoning_protocol = "deepseek"` |
| 168 | continues to enforce the DeepSeek replay contract. A concrete replay rejection |
| 169 | from the server uses the existing bounded history repair, retaining completed |
| 170 | execution facts instead of repeating tools. |
| 171 | |
| 172 | Native signed and DeepSeek replay prefixes stay unchanged. Gateways whose enabled |
| 173 | thinking was previously dropped now receive their actual blocks; this corrects |
| 174 | lossy serialization but can change the old prefix once. No ordinary user setting |
| 175 | or additional persisted format is introduced by these conversions. |
| 176 | |
| 177 | Strict replay repair and reasoning HTTP 400 repair share one recovery and consume the current model round retry budget. Only the outbound view changes; original records and completed-tool facts remain available. New calls still pass replay validation. |
| 178 | |
| 179 | Tool results are recorded in call order, crossing a durability barrier before the |
| 180 | next writer starts. Read-only parallel groups checkpoint after the group returns. |
| 181 | Persistence failure prevents later tools from starting. Recovery distinguishes |
| 182 | completed, definitely not started, and outcome unknown. Unknown calls require |
| 183 | checking filesystem or external side effects before retrying. Missing results do |
| 184 | not prove non-execution, and unfinished siblings do not hide completed writes. |
| 185 | |
| 186 | The optional fields `reasoning_state`, `thinking_blocks`, `tool_run_state`, and |
| 187 | recovery fields `not_started_tools` / `unknown_tools` preserve legacy reads. |
| 188 | Older sessions infer state from existing fields; interrupted placeholders mean |
| 189 | unknown outcome. Older clients can ignore new metadata for display, but cannot be |
| 190 | guaranteed to resume sessions relying on multiple signed blocks or opaque Responses |
| 191 | items; use the current version for those sessions. Healthy histories are not |
| 192 | stripped each round. Fault repair can change the repaired prefix's cache hit; |
| 193 | newly appended healthy tool rounds stay outside the old-prefix repair. |
| 194 | |
| 195 | |
| 196 | ## Automatic retries and waiting |
| 197 | |
| 198 | Model rounds allow three additional attempts with 2/4/8-second backoff. HTTP, |
| 199 | stream, and protocol recovery no longer reset separate budgets. Server retry |
| 200 | delays take precedence. Only the main conversation can keep waiting after |
| 201 | transient connection, throttling, or service failures, normally every 60 seconds. |
| 202 | Partial generations, protocol failures, credentials, and exhausted quota cannot |
| 203 | enter unlimited generation. Search, summaries, compaction, and subagents use |
| 204 | finite retries. Cancellation and existing task limits remain effective; restarting |
| 205 | the app does not automatically resume network activity. Request counts and known |
| 206 | usage accumulate; missing usage is marked unknown, not interpreted as free. |
| 207 | |
| 208 | ## File write verification |
| 209 | |
| 210 | Built-in writes and edits persist versioned `write_intents` before modification, |
| 211 | including before/after content digests, encoding, path, and execution route. |
| 212 | Failed persistence prevents writing. Metadata never enters model requests or tool |
| 213 | schemas. Recovery reads the original route and reports satisfied postconditions |
| 214 | only when every target matches; it does not invent the original execution result. |
| 215 | Conflicts, unknown versions, and unavailable or replaced transports stay unknown. |
| 216 | There is no fallback to a similarly named local file. Identical unresolved writes |
| 217 | are blocked; read-only inspection remains available. Shell/MCP effects are not |
| 218 | automatically verified. Old sessions remain readable without evidence; older |
| 219 | clients do not provide the new recovery guarantees. |
| 220 | |
| 221 | |
| 222 | ### Verified official recovery behavior |
| 223 | |
| 224 | The 2026-09-05 official Flash/Pro probes distinguished original provider call |
| 225 | IDs from replacement IDs: omitting reasoning can succeed with the former and |
| 226 | return a protocol-specific HTTP 400 with the latter. Responses names |
| 227 | `reasoning_text`, Messages names `content[].thinking`, and Chat names |
| 228 | `reasoning_content`; all use the existing bounded recovery path. Do not infer |
| 229 | unconditional omission support from one successful request. |
| 230 | |
| 231 | EOF inside an unterminated JSON event now enters finite stream recovery; |
| 232 | malformed complete events remain errors. History repair carries bounded, |
| 233 | escaped, model-visible completed tool outputs with their originating user turns. |
| 234 | It retains the original repair boundary so later turns cannot reintroduce the |
| 235 | removed protocol history. Raw/local-only output remains excluded. Only fault |
| 236 | recovery changes that prefix; healthy schema and history stay stable. |
| 237 | |
| 238 | File-write intent persistence runs on the actual prepared dispatch context, |
| 239 | before the effect. Missing terminal usage remains unknown through estimation |
| 240 | and aggregation. See [the validation report](RECOVERY_VALIDATION.md) for real |
| 241 | endpoint results, observed model variability, and the distinction between API |
| 242 | acceptance and fault-injected Agent tests. |
| 243 |