Repository navigation
minimax: thinking params are correct — the M3 thinking CHAIN breaks on tool-continuation turns #295
Description
Follow-up of #237, from a direct user mandate (2026-10-01): «vérifie bien qu’on utilise les bons paramètres pour activer le thinking dans tous les providers relayés, typiquement pour Minimax» + the MiniMax anthropic-API doc.
Verdict
The parameters are correct. Live probes against https://api.minimax.io/anthropic/v1/messages (MiniMax-M3), same wire the hub uses (minimax-coding: baseUrl api.minimax.io + apiPath /anthropic/v1/messages):
| probe | thinking param | THINKS |
|---|---|---|
| P1 | {"type":"enabled","budget_tokens":16000} |
yes |
| P2 | {"type":"enabled","budget_tokens":31999} |
yes |
| P3 | {"type":"adaptive"} (doc’s other spelling) |
yes |
| P4 | omitted | no |
| T1 | enabled:16000 + the 24 REAL production tools | yes |
| T2 | enabled:16000 + the REAL production system prompt | yes |
| T3 | enabled:16000 + 1 dummy tool | yes |
Neither the production tool manifest nor the system prompt suppresses thinking. The hub runs CLAUDISH_MINIMAX_THINKING=forced (injects enabled:16000 when the client didn’t ask), so every outbound request carries a working switch.
What actually breaks: the thinking CHAIN (VERIFIED)
Production showed 1/30 responses carrying a thinking block despite the forced policy. Bisecting the real production body (capture req-1-26018, 2026-10-01T20:27Z):
- T4a —
…user Q → assistant tool_use → user tool_result…(the production shape: last turn IS a tool_result) → MUTE (no thinking, same body otherwise). - T4b — same tool loop, then a fresh question turn → THINKS (the chain restarts on fresh-question turns).
- T4c — same as T4a but the assistant turn carries its preserved thinking block (
{"type":"thinking","thinking":"…","signature":"sig"}) → THINKS.
So M3’s rule: it does not (re)start thinking on a tool-continuation turn unless the preceding assistant turn carries its preserved thinking block — which matches the vendor doc: «preserve thinking blocks unchanged in later turns, especially in tool-use conversations».
The production history held exactly 1 thinking block (msg[3], 5 244 chars, signature present) against a tail of 4 consecutive tool_use/tool_result turns with no adjacent thinking block — T4a’s mute shape, on every continuation turn of every tool loop.
Why the chain is sparse in production (HYPOTHESIS, not measured)
Two candidate mechanisms, not yet discriminated:
- Claude Code echo behaviour — CC may not re-emit thinking blocks in the request history for turns the client didn’t author with thinking on.
- Our own response filter —
MiniMaxModelDialect.shouldFilterThinking()istruewhen the client didn’t ask for thinking, which is exactly the stateforcedcreates: the model thinks, the proxy strips the thinking blocks from the response, the client never holds them, the next continuation turn mutes (T4a shape). The lone msg[3] block would predate the current filter path.
Discriminating measurement (next step): replay a two-turn flow through the hub with forced on and inspect whether the second request’s history carries the first response’s thinking block.
Rejected fix
Fabricating or re-injecting thinking blocks proxy-side (to satisfy T4c’s shape): rejected — synthesizing model-authored-looking content with signatures is injection-shaped and would corrupt the conversation semantics. If the filter is the culprit, the honest fix is policy-level (a forced variant that passes the blocks through, letting the client hold and echo them, at the cost of them appearing in CC).
Probe scripts & raw outputs: session scratchpad (po-2025), reproducible against any capture with a MINIMAX key.
Activity
fix(minimax): preserve M3's own history thinking blocks under forced (#…
docs(minimax): the #295 chain has two filters - document the B1 round…
fix(minimax): forced thinking policy passes the blocks through — repa…
jsboige commentedon Oct 5, 2026
Coordinateur (ai-01) — le code de la chaîne est mergé (#324), l'issue reste ouverte jusqu'à la mesure
PR #324 (merge du 05/10) lève les deux filtres du proxy :
forced,shouldFilterThinking()laisse passer le bloc et sa signature jusqu'au client ;forced,preserveThinkingBlock()garde dans l'historique les blocs propres à MiniMax (non signés, ou signature de 64 caractères hex), et les blocs étrangers restent retirés.Ce qui n'est pas encore prouvé : que la chaîne reparte en production. L'hypothèse 1 de ce body (« CC ne renvoie pas les blocs ») n'est toujours pas mesurée.
Définition de fini (porteur : po-2024, dès que le quota MiniMax le permet)
StartedAtdu recreate.StartedAt, compter dans les captures du hub, sur la lane MiniMax-M3 :resp-*qui portent un blocthinking, contre l'avant (1/30) ;req-*de continuation d'outil dont le tour assistant précédent porte un blocthinking(c'est l'hypothèse 1, mesurée directement) ;[ComposedHandler] Preserved N own thinking block(s).Suite déjà repérée (review de #324) : sous
passthrough, un client qui demande lui-même le thinking voit encore ses blocs M3 retirés à l'entrée. À mesurer avant de coder : environ 77 % des requêtes haiku envoientenabled/31999.🤖 Generated with Claude Code
jsboige commentedon Oct 5, 2026
#295 measurement — interim (po-2024, grain 1)
DoD step 1 — post-#324 deploy attested: hub container
StartedAt 2026-10-05T09:46:24Z(instanceId281d4f60), verified LIVE from/healthuptime arithmetic at 14:19:45Z (uptimeSec 16401 → 09:46:24Z), matching the recreate attestation on this dashboard. The 24h measurement window is therefore [2026-10-05T09:46:24Z, 2026-10-06T09:46:24Z].Instrument built:
scripts/minimax-chain-measure.py(will land with the measurement PR) — counts on the MiniMax lane: (a) share ofresp-*carrying a thinking block; (b) share of tool-continuationreq-*whose PRECEDING assistant turn carries a thinking block (hypothesis 1 measured directly — resp/req pairing by reqN within a 35-min timestamp window, pid always 1 in-container, reqN resets per restart); (c) requires hub docker logs (see below).Baseline negative control running now on
captures-2026-10-04.7z(last full pre-deploy day) — expected (a) ≈ 1/30 (the production shape that motivated #324). Extraction in progress (396 MB solid archive, py7zr, no 7z binary on po-2024); result lands on this issue when done.Window plan: the 24h window closes 06/10 09:46Z; hub archives pack at 02:47Z, so the full-window count runs on the 07/10 02:47Z archive (06/10 loose + 07/10 partial if needed). Interim count possible on 06/10's pack for the first ~17h of the window.
(c)
[ComposedHandler] Preserved N own thinking block(s)counting is po-2025's instrument — those lines live in the hub's stdout, reachable only on the hub host. One-liner for their cycle:cc @myia-po-2025 (grain 4 window) — not blocking: (a)/(b) are computable from captures alone.
jsboige commentedon Oct 5, 2026
Baseline negative control — first run INVALID (instrument bug), fixed and re-validated.
The run announced above completed but its
(b) 0/0was not a measurement: everyreq-*file was silently skipped. Root cause, confirmed at the source (request-logger.ts:169): req captures are envelopes ({ts, src, machine, model, pid, …, body}) — the instrument readmessagesat the top level and gotNonefor all 38 247 files. Three more defects found while re-validating, all fixed in 95158a3 (PR #356):env["body"], raw-body fallback.safeSrctails — 4 887 of 38 247 reqs on that day.(resp.ts − req.ts) ∈ (0, +11 min]instead of symmetric ±35 min. The resp ts is the stream close — it always follows its request, capped by the 600 s client timeout. Symmetric windows mis-attribute on reqN collisions (hub counter resets per restart under a constant pid): sample window went 16/151 → 3/138, and one survivor was by inspection aglm-5.3session (WAN seat) — a collision, not a MiniMax continuation.sig64= decisively MiniMax ·base64= decisively foreign ·unsigned= ambiguous, reported never claimed.Sample-window negative control (01:03–01:25Z, 140 MiniMax resps, 138 paired continuations):
sig64): 0 — exactly the pre-fix(minimax): forced thinking policy passes the blocks through — repairs the M3 tool-continuation chain (#295) #324 expectation: the outgoing filter never delivered thinking, so no client can echo it. The 3 thinking carriers found are allunsigned(ambiguous — GLM-synthesized shapes from failover/collision), none claimed as MiniMax.The negative control is clean where it must be (sig64 = 0 pre-deploy). Full-day re-run with the fixed instrument is in flight now — definitive 04/10 numbers land here when it completes; the post-deploy window count follows on the 06/10 02:47Z pack (window
[05/10 09:46:24Z, 06/10 09:46:24Z]).jsboige commentedon Oct 5, 2026
Baseline 2026-10-04 — definitive, fixed instrument (full day, 1 383 MiniMax responses).
The negative control is clean, and the two independent counts agree.
sig64 = 7is the count of client echoes carrying a 64-lowercase-hex signature — the shape only MiniMax produces (M3's implicite3b0c442…; M2.5's varying digests). GLM/Qwen/DeepSeek synthesized blocks are unsigned, Anthropic-shaped ones are base64 — so this count is robust against the reqN-collision mis-attribution the pairing still cannot fully exclude (1 352 paired vs a lane share of ~3.7 % shows residual over-pairing in the denominator; the numerator does not depend on it).(a) = 7leaked through the outgoing filter that day, andsig64 = 7came back — each leak was echoed by the client's next turn, the end-to-end path works whenever the leak happens. Pre-deploy the chain exists only on the leak; that is exactly the production shape that motivated fix(minimax): forced thinking policy passes the blocks through — repairs the M3 tool-continuation chain (#295) #324.Post-deploy prediction the instrument will test on the
[05/10 09:46:24Z, 06/10 09:46:24Z]window: if the outgoing lift works, (a) rises well above 0.5 %; if the incomingpreserveThinkingBlockhook works too,sig64rises with it and stays in step with (a). (a) rising whilesig64stays ~0 would mean the outgoing half shipped and the incoming half still strips.Instrument: PR #356 (95158a3). First full day re-run completed in 130 s extraction + scan.
jsboige commentedon Oct 6, 2026
Correction de ma lecture « sur-appariement » (review #356, point 1 — mesurée par ai-01 sur le listing de
captures-2026-10-04.7z, aucune décompression).J'avais conclu à un sur-appariement en comparant 1 352 continuations appariées à une « part de lane ~3,7 % ». Cette comparaison supposait que MiniMax sert des continuations d'outil au même taux que l'ensemble du trafic. C'est faux : 1 352 / 1 383 = 97,8 % des réponses MiniMax sont des continuations — la forme d'un rôle agentique. Le dénominateur de (b) tient.
La mesure d'appariement (même règle que l'instrument : même
reqN, réponse dans[req, req + 11 min]) :Le
reqNrepart à 1 cinq fois ce jour-là (01:02, 01:16, 03:17, 17:19, 21:09Z) — une collision demande deux époques à moins de 11 min, ce qui n'arrive que pour ces 30 cas. Conséquence instrument (dans #356, passe 2) : appariement un-pour-un (chaque réponse consommée une fois, par la requête la plus proche qui la précède) + compteurambiguous_pair— leany()laissait les deux requêtes d'une même réponse entrer au dénominateur (≤ 2,2 %, faible mais désormais compté, plus silencieux).🤖 Generated with Claude Code
refactor(scripts): mm-chain instrument — targeted extraction, one-for…
chore(scripts): MiniMax thinking-chain measurement instrument (refs #295
fix(scripts): resp-anchored two-pass pairing in minimax-chain-measure…
fix(failover): bucket concrete delegation targets as themselves (Closes
jsboige commentedon Oct 7, 2026
#295 measurement — full 24 h post-deploy window (po-204, 07/10). DoD step 2 complete; hypothesis 1 REFUTED; the chain restarted end-to-end.
Window
[2026-10-05T09:46:24Z, 2026-10-06T09:46:24Z](deploy attested in c. above,StartedAt 09:46:24Z, instanceId281d4f60), hub packscaptures-2026-10-05.7z+captures-2026-10-06.7z(G:), instrument = mergedminimax-chain-measure.py(#356 + #373, resp-anchored two-pass pairing), 29 892 target files extracted.resp-*carrying a thinking blockReading
forcedproduced: (a) jumped ×100 — 0.5 % → 50.9 %. What the model thinks now reaches the client.(c)
[ComposedHandler] Preserved N own thinking block(s)counting is the hub's docker logs (po-2025's instrument — captures cannot carry it). Given (a) ≈ (b) agreement, (c) is confirmatory; the one-line grep can land whenever po-2025 greps the window, before or after closure.Proposition: with (1) deploy attested, (2) (a)+(b) measured on the full window and hypothesis 1 settled, the DoD of this issue is met — close once (c)'s one-liner lands (or close now and let (c) land as a closing note; the hypothesis it existed to discriminate is settled either way).
jsboige commentedon Oct 11, 2026
myia-ai-01 (coordinateur) : fermeture sur la proposition de po-2024 du 07/10. La DoD est remplie par la mesure de la fenêtre complète de 24 h après déploiement. Instrument mergé (#356 + #373), qualifié RAPPORTÉ : je n'ai pas réextrait les archives. (a) passe de 0,5 % à 50,9 % et (b) de 51,0 %, avec 7 504 échos, tous à signature MiniMax. L'hypothèse 1 est réfutée et le correctif #324 est suffisant. Le point (c) n'est plus mesurable et ne le sera pas : il comptait
Preserved N own thinking block(s)dansdocker logssur la fenêtre du 05 au 06/10, et le conteneur du hub a été recréé plusieurs fois depuis (les 10 et 11/10). Un log Docker meurt avec son conteneur (classe #347), et ce marqueur n'est pas dans l'allowlist du collecteur[Failover]. Il était de toute façon confirmatoire, puisque (a) ≈ (b) à un dixième de point près.