You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This report is the result of a code-level investigation carried out with the help of Claude (Anthropic's Claude Code), reading through the actual provider/transform code paths cited below. All the file/line references were verified against the current main branch.
Describe the bug
During a single task's tool-call loop (reasoning → tool call → tool result → reasoning → ...), some providers are supposed to carry the model's reasoning across steps via ModelInfo.preserveReasoning, the same way DeepSeek, MiMo, and Z.ai/GLM already do correctly (src/api/providers/opencode-go.ts:172-177). For Bedrock and the MiniMax direct provider, this is still broken today, so the model re-derives the same reasoning from scratch at every step of a tool loop instead of building on it — this is only about continuity within one task's tool loop, not about remembering reasoning across genuinely new user messages (every vendor intentionally drops it there, which is expected).
Root causes found by reading the code:
Bedrock — convertToBedrockConverseMessages (src/api/transform/bedrock-converse-format.ts:30-209) has no handling at all for reasoning/thinking blocks; they fall into the "Default case for unknown block types" and get replaced by the literal string "[Unknown Block Type]" (bedrock-converse-format.ts:198-201). This affects the 2 models that have preserveReasoning: true today (moonshot.kimi-k2-thinking line 551, minimax.minimax-m2 line 561, in packages/types/src/providers/bedrock.ts).
Claude on Bedrock is not hit by this today, but only incidentally: no anthropic.claude-* entry sets preserveReasoning, so Task.buildCleanConversationHistory() (see below) already strips Claude's reasoning block before it ever reaches this converter — Claude never gets the chance to hit the "[Unknown Block Type]" path. This bug is latent, not absent, for Claude: the moment someone enables preserveReasoning for a Claude Bedrock model (e.g. as part of resolution path chore: disable automatic deploy workflows #3 below), Claude would immediately fall into the exact same corruption unless this converter has been fixed first. This is why path feat: support OAuth 2.1 for streamable-http MCP servers #1 should land before path chore: disable automatic deploy workflows #3 is ever attempted on Bedrock.
MiniMax (direct provider) — MiniMaxHandler.createMessage (src/api/providers/minimax.ts:80-116) sends Anthropic-shaped messages straight to this.client.messages.create() with no sanitization, unlike anthropic.ts and anthropic-vertex.ts, which both call filterNonAnthropicBlocks before sending. Several MiniMax models have preserveReasoning: true; whether MiniMax's API tolerates a raw {type:"reasoning", text} block reaching it unfiltered is unverified.
Claude's extended thinking (all surfaces, separate/bigger issue) — getThoughtSignature(), needed to build a real signed type:"thinking" block (src/core/task/apiConversationHistory.ts:14,48), is only implemented for Gemini (src/api/providers/gemini.ts:628). Neither anthropic.ts:361 nor anthropic-vertex.ts:206 implement it — both still carry the comment "Signature for multi-turn thinking would require using stream.finalMessage() ... which requires restructuring the streaming approach." So Claude's thinking is never preserved across tool-loop steps anywhere today, on top of the Bedrock-specific corruption above.
The stripping gate itself is at src/core/task/Task.ts:4926 (buildCleanConversationHistory) / Task.ts:5030 (shouldPreserveForApi = this.api.getModel().info.preserveReasoning === true) — still in place and working as designed for providers that implement the flag correctly.
Note: a previously-identified Fireworks bug (the same preserveReasoning flag being silently ignored because BaseOpenAiCompatibleProvider never round-tripped reasoning_content) turned out to already be fixed by commit 8e76b8d7c ("fix(deepseek): round-trip reasoning_content in thinking mode to prevent 400 errors", #775) — the fix landed in the shared convertToOpenAiMessages (src/api/transform/openai-format.ts:274-280,516-583), so it incidentally also fixed Fireworks. No action needed there anymore; not included in this report.
To Reproduce
For Bedrock (any model with preserveReasoning: true, e.g. moonshot.kimi-k2-thinking):
Configure Zoo Code with the Bedrock provider and moonshot.kimi-k2-thinking (or minimax.minimax-m2).
Start a task that requires several tool calls in a row (e.g. read a file, then edit it, then run a command).
Let the model reason, then call a tool, then reason again for the next tool call.
Inspect the actual request payload sent on the second/third tool-call step (e.g. via a logging proxy) — the previous step's reasoning content has been replaced with the literal string "[Unknown Block Type]".
Observe that the model's reasoning at each step re-derives the same analysis instead of building on the previous step, and/or reacts to "[Unknown Block Type]" as if it were real prior content.
Expected behavior
Reasoning content produced during a step of the tool-call loop should be preserved and correctly forwarded on the next step of the same task, exactly as already happens for DeepSeek, MiMo, and Z.ai/GLM models. It should never be replaced by a placeholder string, and preserveReasoning: true should have an actual effect regardless of which provider serves the model.
Screenshots
N/A — not visible in the Zoo Code UI itself (reasoning displays correctly there); only shows up by inspecting the outgoing request payload. Happy to provide a captured request body showing "[Unknown Block Type]" on Bedrock if useful.
Video
N/A
What version of zoo are you running
3.82.0+ (confirmed against current main, commit 500152b78, as of this investigation — not tied to a specific tagged release).
None of the existing issues cover the Bedrock corruption or MiniMax cases above, or the Claude signature-capture gap.
Consequences:
Wasted tokens and cost: the model regenerates the same multi-step reasoning from scratch at every tool-call round trip instead of building on it once. On long agentic tasks this multiplies reasoning-token cost roughly by the number of steps — directly hits users on pay-per-token Bedrock/MiniMax plans for no benefit.
Degraded tool-use quality: models like Kimi K2 Thinking are documented to rely on seeing their prior reasoning to keep tool-call arguments/sequencing consistent across a multi-step plan; losing it mid-task can make the model contradict or forget decisions it just made a step earlier.
Silent failure mode: no error or warning anywhere — the reasoning displays correctly in the Zoo Code UI right up until it's dropped/corrupted, so nothing looks wrong unless you inspect the actual request payload.
Corrupted context, not just lost context, on Bedrock: the "[Unknown Block Type]" literal is actively sent to the model as if it were real prior content — worse than simply not having the reasoning at all.
Inconsistent product behavior: preserveReasoning is an advertised capability of the model catalog, but whether it actually works depends entirely on which provider happens to serve the model — DeepSeek/MiMo/Z.ai/Fireworks users get correct behavior, Bedrock/MiniMax users silently don't, for models otherwise equally capable of interleaved thinking.
Proposed resolution paths:
Teach bedrock-converse-format.ts to map reasoning/thinking blocks to the Converse API's reasoningContent shape instead of "[Unknown Block Type]". Fixes the 2 Bedrock models that already set the flag, low risk.
Audit MiniMax's direct provider: sanitize/handle the reasoning block explicitly (e.g. reuse filterNonAnthropicBlocks or an equivalent) instead of an unverified raw passthrough.
(Separate, larger effort) Implement signature capture for Claude (stream.finalMessage()) on anthropic.ts/anthropic-vertex.ts, then extend to Bedrock on top of fix feat: support OAuth 2.1 for streamable-http MCP servers #1. More work, higher risk of API rejection if done wrong — scope independently from 1–2.
Suggest doing 1–2 first, then 3 as a separate follow-up epic.
Another idea could be to add an "auto-condense" feature for reasoning: each time reasoning is produced, it would be condensed into a shorter form, so we can save context without losing the reasoning that was done.
Describe the bug
During a single task's tool-call loop (reasoning → tool call → tool result → reasoning → ...), some providers are supposed to carry the model's reasoning across steps via
ModelInfo.preserveReasoning, the same way DeepSeek, MiMo, and Z.ai/GLM already do correctly (src/api/providers/opencode-go.ts:172-177). For Bedrock and the MiniMax direct provider, this is still broken today, so the model re-derives the same reasoning from scratch at every step of a tool loop instead of building on it — this is only about continuity within one task's tool loop, not about remembering reasoning across genuinely new user messages (every vendor intentionally drops it there, which is expected).Root causes found by reading the code:
convertToBedrockConverseMessages(src/api/transform/bedrock-converse-format.ts:30-209) has no handling at all forreasoning/thinkingblocks; they fall into the "Default case for unknown block types" and get replaced by the literal string"[Unknown Block Type]"(bedrock-converse-format.ts:198-201). This affects the 2 models that havepreserveReasoning: truetoday (moonshot.kimi-k2-thinkingline 551,minimax.minimax-m2line 561, inpackages/types/src/providers/bedrock.ts).anthropic.claude-*entry setspreserveReasoning, soTask.buildCleanConversationHistory()(see below) already strips Claude's reasoning block before it ever reaches this converter — Claude never gets the chance to hit the"[Unknown Block Type]"path. This bug is latent, not absent, for Claude: the moment someone enablespreserveReasoningfor a Claude Bedrock model (e.g. as part of resolution path chore: disable automatic deploy workflows #3 below), Claude would immediately fall into the exact same corruption unless this converter has been fixed first. This is why path feat: support OAuth 2.1 for streamable-http MCP servers #1 should land before path chore: disable automatic deploy workflows #3 is ever attempted on Bedrock.MiniMaxHandler.createMessage(src/api/providers/minimax.ts:80-116) sends Anthropic-shaped messages straight tothis.client.messages.create()with no sanitization, unlikeanthropic.tsandanthropic-vertex.ts, which both callfilterNonAnthropicBlocksbefore sending. Several MiniMax models havepreserveReasoning: true; whether MiniMax's API tolerates a raw{type:"reasoning", text}block reaching it unfiltered is unverified.getThoughtSignature(), needed to build a real signedtype:"thinking"block (src/core/task/apiConversationHistory.ts:14,48), is only implemented for Gemini (src/api/providers/gemini.ts:628). Neitheranthropic.ts:361noranthropic-vertex.ts:206implement it — both still carry the comment "Signature for multi-turn thinking would require using stream.finalMessage() ... which requires restructuring the streaming approach." So Claude's thinking is never preserved across tool-loop steps anywhere today, on top of the Bedrock-specific corruption above.src/core/task/Task.ts:4926(buildCleanConversationHistory) /Task.ts:5030(shouldPreserveForApi = this.api.getModel().info.preserveReasoning === true) — still in place and working as designed for providers that implement the flag correctly.Note: a previously-identified Fireworks bug (the same
preserveReasoningflag being silently ignored becauseBaseOpenAiCompatibleProvidernever round-trippedreasoning_content) turned out to already be fixed by commit8e76b8d7c("fix(deepseek): round-trip reasoning_content in thinking mode to prevent 400 errors", #775) — the fix landed in the sharedconvertToOpenAiMessages(src/api/transform/openai-format.ts:274-280,516-583), so it incidentally also fixed Fireworks. No action needed there anymore; not included in this report.To Reproduce
For Bedrock (any model with
preserveReasoning: true, e.g.moonshot.kimi-k2-thinking):moonshot.kimi-k2-thinking(orminimax.minimax-m2)."[Unknown Block Type]".Expected behavior
Reasoning content produced during a step of the tool-call loop should be preserved and correctly forwarded on the next step of the same task, exactly as already happens for DeepSeek, MiMo, and Z.ai/GLM models. It should never be replaced by a placeholder string, and
preserveReasoning: trueshould have an actual effect regardless of which provider serves the model.Screenshots
N/A — not visible in the Zoo Code UI itself (reasoning displays correctly there); only shows up by inspecting the outgoing request payload. Happy to provide a captured request body showing
"[Unknown Block Type]"on Bedrock if useful.Video
N/A
What version of zoo are you running
3.82.0+ (confirmed against current
main, commit500152b78, as of this investigation — not tied to a specific tagged release).Additional context
Related existing issues/PRs :
preserveReasoningcan never betruebecausegetModel()has no per-model catalog). PR fix(openai): preserve reasoning for local OpenAI-compatible models via R1 toggle #1119 already addresses this by making the "R1 format" toggle also setpreserveReasoning: true; as of last check it's active (awaiting-maintainer, not merged, blocked on a second round of change requests) — not abandoned, just not landed. This issue does not duplicate that work."omitted") — that's about visibility, not the cross-turn persistence gap described here.None of the existing issues cover the Bedrock corruption or MiniMax cases above, or the Claude signature-capture gap.
Consequences:
"[Unknown Block Type]"literal is actively sent to the model as if it were real prior content — worse than simply not having the reasoning at all.preserveReasoningis an advertised capability of the model catalog, but whether it actually works depends entirely on which provider happens to serve the model — DeepSeek/MiMo/Z.ai/Fireworks users get correct behavior, Bedrock/MiniMax users silently don't, for models otherwise equally capable of interleaved thinking.Proposed resolution paths:
bedrock-converse-format.tsto mapreasoning/thinkingblocks to the Converse API'sreasoningContentshape instead of"[Unknown Block Type]". Fixes the 2 Bedrock models that already set the flag, low risk.filterNonAnthropicBlocksor an equivalent) instead of an unverified raw passthrough.stream.finalMessage()) onanthropic.ts/anthropic-vertex.ts, then extend to Bedrock on top of fix feat: support OAuth 2.1 for streamable-http MCP servers #1. More work, higher risk of API rejection if done wrong — scope independently from 1–2.Suggest doing 1–2 first, then 3 as a separate follow-up epic.
Another idea could be to add an "auto-condense" feature for reasoning: each time reasoning is produced, it would be condensed into a shorter form, so we can save context without losing the reasoning that was done.