Repository navigation
Bedrock prompt cache misses on the next turn: reasoning is not replayed and dynamic instructions sit after the system cache point #617
Description
Summary
With Bedrock Claude models, promptCache: true and extended thinking, the first call of a turn sent shortly after the previous one (well inside the 5 min TTL) usually reads only the system prompt from the cache and writes the whole conversation again. Two independent causes:
- C. Reasoning is not replayed, so the rebuilt history differs from what was cached. During a run, assistant steps carry signed
reasoningContent. On the next turn the history is rebuilt from the stored message:formatAgentMessagesmovesthinkparts toadditional_kwargs.reasoning_content, andconvertToConverseMessagesdoes not emit them (with or without a signature). From the first assistant step of the previous turn onward the bytes differ, so the previous turn's tail checkpoint can never match. - A. Dynamic instructions follow the system cache point on Bedrock.
AgentContext.buildSystemMessageplaces the dynamic block (for example the code-execution file note, the web-search date context, memories) after the systemcachePoint. For Anthropic and OpenRouter it is moved next to the last human message (shouldMoveDynamicInstructions), but not for Bedrock. Any change in that block, such as a newly attached file or the hour rolling over, invalidates every message after the system prompt even when the history is identical.
A third factor makes (1) worse: Bedrock gets a single message checkpoint at the tail (addBedrockTailCacheControl), and Bedrock only looks back about 20 content blocks from each cachePoint. The one prefix that does still match (up to the human message that opened the previous turn, written as the tail of that turn's first call) is out of reach once the previous turn had a few tool steps.
Reproduction
- Bedrock Claude model with
promptCache: trueand reasoning enabled, an agent with at least one tool. - Turn 1: a request that makes the model call a tool 5 or more times.
- Within a minute, turn 2: any short follow-up.
- Usage of the first call of turn 2:
cacheReadInputTokensequals the system prompt size, andcacheWriteInputTokenscovers the entire history. - Variant for (A): same, but attach a file on turn 2 so the code-execution file note changes. Even with a one-call turn 1, turn 2 reads only the system prompt.
A unit-level repro without network: build turn 1 in memory (AIMessages with signed reasoning_content plus tool calls), run each call through addBedrockTailCacheControl + systemRunnable + convertToConverseMessages, then rebuild turn 2 from the persisted payload via formatAgentMessages, and compare the flattened Converse blocks against a prefix cache with a 20-block lookback.
Suggested fixes
- A: on Bedrock with prompt caching, move the dynamic block out of the system message into a message right after the latest human message (as is already done for Anthropic), so it sits after the message checkpoints. It is worth wrapping it in an explicit marker, because Converse merges consecutive user messages into one turn and the block otherwise reads as part of the user's text.
- Lookback: place the second message
cachePointon the latest human message before the tail. On a turn's first call that is the human message that opened the previous turn, whose prefix is already cached. This stays within the limit of 4 cache points. - C: replay reasoning to Bedrock as
reasoningContentblocks when a signature is available, and keep the signature when the run's content is persisted (today only Vertex signatures survive). Until signatures are persisted, the two changes above recover most of the loss, because the matching prefix ends right before the first assistant step.
Impact
Measured over one week on a production deployment: at least 44% of the turns sent less than 5 minutes after the previous one (352 of 805) read only the system prompt on their first call. Making the second checkpoint reachable plus moving the dynamic block brought the first call of each follow-up turn back to reading everything up to the previous turn's opening message.
Related in LibreChat
LibreChat persists think content parts without the provider signature, so even a converter that replays reasoning has nothing signed to send on the next turn. If preferred, this part can be tracked as a separate LibreChat issue.
Activity
Haarris commentedon Oct 21, 2026
Thanks for the detailed write-up. From what I can see, #619 covers the reasoning replay (C) and #613 keeps the conversation-level context at a fixed system position, but nothing open yet changes the message checkpoints, so the lookback part is still unaddressed.
Are you planning a PR for the second message
cachePoint? If not, I'm happy to open one: keep the tail checkpoint, add one on the latest human message before the tail, and only when it fits under Bedrock's limit of 4 together with the system and tool checkpoints (so it stays valid if #613 adds a second system point).