Skip to content

fix(bedrock): preserve reasoning signature on Converse interleaved-thinking replay - #36261

Open
fesalfayed wants to merge 1 commit into
NousResearch:mainfrom
fesalfayed:fix/bedrock-reasoning-signature
Open

fix(bedrock): preserve reasoning signature on Converse interleaved-thinking replay#36261
fesalfayed wants to merge 1 commit into
NousResearch:mainfrom
fesalfayed:fix/bedrock-reasoning-signature

Conversation

@fesalfayed

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes the AWS Bedrock (Converse API) analog of #35975 / #35586: signed Claude extended-thinking blocks were not round-tripped, so multi-turn agentic turns with interleaved thinking + tool_use silently lost their reasoning (and replaying reasoning without its original signature is rejected by Bedrock).

Root cause (three defects).

  1. Wrong schema key. normalize_converse_response read reasoningContent.text, but the Converse schema nests it at reasoningContent.reasoningText.{text, signature} (botocore ReasoningContentBlock / ReasoningTextBlock). On a real Bedrock response reasoning.get("text") is always "", so nothing was captured. The fix in 8d363f8 (fix(bedrock): preserve reasoningContent in normalized responses #20366/fix(bedrock): preserve reasoningContent in normalized responses #21202) only passed because its fixture used a flattened shape that does not match AWS.
  2. Signature dropped. reasoningText.signature was never read. AWS requires it on replay: "include the text and its signature unmodified."
  3. Replay never reconstructs reasoningContent. convert_messages_to_converse rebuilt assistant turns as [text][toolUse] only.

Fix. Read reasoningText.{text, signature}, carry it verbatim through the existing reasoning_details channel (same mechanism AnthropicTransport uses — no new state column), and re-emit the reasoningContent block ahead of tool_use on replay. Streaming deltas (flat shape) now also retain the signature.

How was it tested?

  • New TestReasoningSignatureRoundTrip (normalize → reasoning_details → replay, asserting order + signature). Fails on main (reasoning is None), passes here.
  • Corrected the test_raw_reasoning_content_response fixture to the real nested schema; added a provider_data["reasoning_details"] assertion.
  • tests/agent/test_bedrock_adapter.py + tests/agent/transports/: 435 passed, 0 regressions.

Closes #36260. Native-endpoint counterpart: #35586. Builds on the reasoningContent work in 8d363f8.

…inking replay

normalize_converse_response read reasoningContent.text, but the Converse
schema nests it at reasoningContent.reasoningText.{text,signature}, so on a
real Bedrock response nothing was captured and the signature was dropped.
convert_messages_to_converse also rebuilt assistant turns as [text][toolUse]
only, discarding signed thinking. Replaying signed reasoning without its
original signature (or omitting it) breaks multi-turn thinking on Bedrock —
the Converse analog of the native-endpoint reorder in NousResearch#35975.

Read reasoningText.{text,signature}, carry it verbatim through the existing
reasoning_details channel, and re-emit the reasoningContent block ahead of
tool_use on replay. Streaming deltas (flat shape) now also retain the
signature.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists provider/anthropic Anthropic native Messages API comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Jun 1, 2026
@fesalfayed

Copy link
Copy Markdown
Contributor Author

Validation refresh:

  • ruff check .
  • python scripts/check-windows-footguns.py --all
  • python -m pytest tests/agent/test_bedrock_adapter.py tests/agent/transports/test_bedrock_transport.py -q -o addopts=''139 passed
  • git diff --check origin/main...HEAD

Why this remains needed after the Anthropic fixes: those preserve Anthropic thinking/tool_use order, but Bedrock Converse uses reasoningContent.reasoningText.{text, signature}. Current main still reads reasoningContent.text and does not propagate the signature into provider replay data, so signed Bedrock reasoning is dropped before multi-turn replay.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying the real Converse schema and the missing signature path. Current main still reads the flattened key at agent/bedrock_adapter.py:705-710 and emits no replayed reasoning blocks at agent/bedrock_adapter.py:603-626.

Problems

  • agent/bedrock_adapter.py:557-581 reconstructs all reasoning_details before all tool calls. For the interleaved signed-thinking case in this PR's title, this changes [reasoning_1, tool_1, reasoning_2, tool_2] into [reasoning_1, reasoning_2, tool_1, tool_2]. The project’s native Anthropic implementation records an ordered-block channel for exactly this loss of cross-type ordering (agent/transports/anthropic.py:167-183; tests/agent/test_anthropic_thinking_block_order.py:9-29).

Suggested changes

  • Preserve and replay an ordered Bedrock block sequence when signed reasoning and tool uses coexist, rather than reconstructing from parallel lists.
  • Add a round-trip regression with two signed reasoning blocks separated by tool uses; the current new test covers only one leading reasoning block.

Automated hermes-sweeper review.

Comment thread agent/bedrock_adapter.py
# and rejects it if the text/signature are modified or dropped on a
# multi-turn replay. reasoning_details carries the verbatim
# reasoningText payload captured in normalize_converse_response.
for detail in (msg.get("reasoning_details") or []):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This front-loads every reasoning block before the separate tool-call loop below. For an interleaved signed response [reasoning_1, tool_1, reasoning_2, tool_2], replay becomes [reasoning_1, reasoning_2, tool_1, tool_2], invalidating the positional guarantee this PR targets. Preserve an ordered Bedrock block sequence instead of reconstructing from parallel lists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/anthropic Anthropic native Messages API sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

3 participants