Skip to content

fix(memory): encode bytes in OpenAI converter reasoning blocks - #718

Merged
jariy17 merged 1 commit into
aws:mainfrom
abhiramshaji-cyber:fix/openai-converter-redacted-bytes
Oct 27, 2026
Merged

jariy17 merged 1 commit into
aws:mainfrom
abhiramshaji-cyber:fix/openai-converter-redacted-bytes

Conversation

@abhiramshaji-cyber

Copy link
Copy Markdown
Contributor

Fixes #680

Agents using the OpenAI memory format crashed on any reply that carried hidden reasoning, and now that reply is saved and restored like any other.

What this code is for

OpenAIConverseConverter in converters/openai.py turns each Strands message into an OpenAI shaped JSON string before AgentCoreMemorySessionManager saves it to AgentCore Memory, and turns stored events back into Strands messages when a session is restored. Reasoning blocks have no OpenAI field, so they are kept as they are under _strands_reasoning_content.

Before this PR

A developer runs a Strands agent with AgentCoreMemorySessionManager(converter=OpenAIConverseConverter) on a model that returns reasoning as reasoningContent.redactedContent.

  1. The model answers with a block {"reasoningContent": {"redactedContent": b"..."}}, where the value is raw bytes.
  2. append_message calls create_message, which calls OpenAIConverseConverter.message_to_payload.
  3. _bedrock_to_openai copies the block into _strands_reasoning_content unchanged, bytes included.
  4. json.dumps(openai_msg) raises TypeError: Object of type bytes is not JSON serializable.
  5. The agent call fails with EventLoopException, on every turn that has redacted reasoning.

After this PR

Same developer, same model, same turn.

  1. Same as above through step 2.
  2. _bedrock_to_openai passes each reasoning block through encode_bytes_values, so bytes becomes {"__bytes_encoded__": true, "data": "<base64>"}.
  3. json.dumps succeeds and the event is saved.
  4. On restore, _openai_to_bedrock passes each stored block through decode_bytes_values, so the model gets back the original bytes.
  5. Reasoning blocks that hold only text are unchanged by both calls and still round trip as before.

The diagram

flowchart LR

classDef box fill:#eef1ff,stroke:#4c5bd4,color:#111827
classDef bad fill:#ffd6d6,stroke:#cc0000,color:#111827,stroke-width:3px
classDef good fill:#c8f0c8,stroke:#008800,color:#111827,stroke-width:3px

subgraph BEFORE["BEFORE: every redacted reasoning turn fails"]
direction TB
  B1["model returns redactedContent bytes"] --> B2["message_to_payload"]
  B2 --> B3["_bedrock_to_openai keeps raw block"]
  B3 --> B4{"json.dumps"}
  B4 -->|"bytes in block"| B5["TypeError, agent call fails"]
  B4 -->|"text only reasoning"| B6["saved"]
end

subgraph AFTER["AFTER: saved and restored as bytes"]
direction TB
  A1["model returns redactedContent bytes"] --> A2["message_to_payload"]
  A2 --> A3{"THE CHANGE: encode_bytes_values on block"}
  A3 --> A4{"json.dumps"}
  A4 -->|"bytes now base64"| A5["saved, decode_bytes_values on restore"]
  A4 -->|"text only reasoning"| A6["saved, unchanged"]
end

B1 ~~~ A1

class B1,B2,B3,B6 box
class A1,A2,A6 box
class B4,B5 bad
class A3,A5 good

linkStyle 3 stroke:#cc0000,stroke-width:5px
linkStyle 8 stroke:#008800,stroke-width:5px

style BEFORE fill:#fff5f5,stroke:#cc0000,color:#111827
style AFTER fill:#f3fff3,stroke:#008800,color:#111827
Loading

The change

In converters/openai.py, reasoning_blocks.append(item) becomes reasoning_blocks.append(encode_bytes_values(item)) and reasoning_items.append(rc) becomes reasoning_items.append(decode_bytes_values(rc)). Both helpers come from strands.types.session, the same pair SessionMessage.to_dict and SessionMessage.from_dict use, which is how AgentCoreMemoryConverter already handles this. A new test, test_redacted_reasoning_bytes_round_trip_in_openai_converter, saves and restores a redacted block and checks the bytes come back equal.

What changes for a user

  • Agents on models that return only redacted reasoning no longer crash when memory saves the turn.
  • The restored history carries the redacted block as bytes, so the model receives it back on the next turn.
  • Text reasoning blocks, tool calls and plain messages are saved and restored exactly as before.

OpenAIConverseConverter stored reasoningContent blocks as is, so a
redactedContent value (bytes) made json.dumps raise and broke the agent
call. Encode bytes the same way SessionMessage.to_dict does when saving,
and decode them on restore, matching AgentCoreMemoryConverter.

Fixes aws#680
@codecov-commenter

codecov-commenter commented Oct 27, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@813ab45). Learn more about missing BASE report.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #718   +/-   ##
=======================================
  Coverage        ?   89.68%           
=======================================
  Files           ?      125           
  Lines           ?    10885           
  Branches        ?     1694           
=======================================
  Hits            ?     9762           
  Misses          ?      735           
  Partials        ?      388           
Flag Coverage Δ
unittests 89.68% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@abhiramshaji-cyber

Copy link
Copy Markdown
Contributor Author

Thanks for the review @jariy17! The integration test jobs are waiting on the manual-approval environment. Could you approve that run when you get a chance? The breaking change check found nothing; it only failed because it can't post its comment from a fork.

@jariy17
jariy17 merged commit a6baec6 into aws:main Oct 27, 2026
28 of 38 checks passed
@abhiramshaji-cyber
abhiramshaji-cyber deleted the fix/openai-converter-redacted-bytes branch October 27, 2026 21:37

This branch is waiting to be deployed

1 waiting deployment
manual-approval — 1bec9f3e Waiting Oct 27, 2026 by abhiramshaji-cyber via Test (runtime) #1664
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s PR size: S

Projects

None yet

3 participants