Conversation
`promptCacheExplicit` had no effect on where the breakpoint landed, because `buildSystemMessage` collapsed the stable instructions and the dynamic tail into one string for OpenAI and Azure. The OpenAI client marks the last system/developer message, so the breakpoint sat behind per-turn content and the cached prefix was invalidated every turn. The dynamic tail now moves behind the stable prefix, reusing the relocation Anthropic and OpenRouter already use, while leaving `promptCacheProvider` undefined so no `cache_control` or `cachePoint` marker is stamped: the OpenAI breakpoints are attached to the serialized request, not the message content.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
agents/src/agents/AgentContext.ts
Lines 1050 to 1052 in e3fe253
When explicit caching is enabled and both instruction strings are nonempty, this converts the entire dynamic system tail into a HumanMessage. That tail includes host-supplied additional_instructions and cross-run summary context; before this commit they were part of the SystemMessage, and AgentInputFields still documents additional_instructions as a system tail. On OpenAI and Azure this lowers its instruction priority to user level, allowing later user content to override constraints and changing agent behavior merely by enabling caching. Preserve a system/developer role for the tail and make the request serializer target the stable instruction message or block for its breakpoint.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
What breaks today
promptCacheExplicitturns on GPT-5.6 explicit caching:applyManagedRequestParamssendsprompt_cache_options, andaddChatCacheBreakpoints/addResponseCacheBreakpointsstampprompt_cache_breakpointon the lastsystem/developermessage plus the last cacheable message before the latest user turn.That first breakpoint is the cross-conversation one, and today it lands in the wrong place.
AgentContextkeepsinstructions(stable) andadditional_instructions(dynamic) apart the whole way down — right up tobuildSystemMessage, which for every provider except Anthropic, OpenRouter and Bedrock ends at:OpenAI and Azure fall through to that line. One system message, stable prefix welded to the dynamic tail, and the breakpoint marks the end of the whole thing. Since the tail is where per-turn content lives — timestamps, memory, retrieved file context, runtime additions — the marked prefix changes on nearly every request. It is written and never read.
Enabling
promptCacheExplicittherefore costs a cache write per turn and buys no cross-conversation reuse, whileprompt_cache_options: { mode: 'explicit' }has already opted the request out of the implicit latest-message breakpoint it would otherwise have had.After this change
The dynamic tail moves out of the system message and in behind the stable prefix, so the breakpoint the client places marks content that actually recurs:
This is the relocation Anthropic and OpenRouter already perform (
shouldMoveDynamicInstructions→buildPromptCacheDynamicTail→buildBodyWithPromptCacheDynamicTail). The OpenAI path reuses all of it and adds nothing new structurally.What it deliberately does not reuse is the marker stamping.
getPromptCacheProvider()still returnsundefinedfor OpenAI and Azure, soaddStablePromptCacheMarkers,addTailCacheControland thecache_controlsystem blocks stay off: those emit Anthropic-format fields, and OpenAI has no such field. The GPT-5.6 breakpoints are attached later, to the serialized request, insideLibreChatOpenAICompletions/LibreChatOpenAIResponses.Nothing changes unless
promptCacheExplicit === trueon an OpenAI or Azure client. Anthropic, OpenRouter, Bedrock and every default-configured OpenAI request keep their existing message shape.Mechanism
The two concerns that were previously fused under one flag are now separated:
cache_controlcache_controlcachePointpromptCacheExplicitFour call sites:
usesOpenAIExplicitPromptCache()— new private predicate: provider isOPENAIorAZUREandclientOptions.promptCacheExplicit === true.buildPromptCacheDynamicTail— takessplitsDynamicInstructionsinstead ofpromptCacheProvider; it only ever used that argument as a boolean.buildBodyWithPromptCacheDynamicTail— appliesaddStablePromptCacheMarkersonly whenpromptCacheProvider != null, leaving the OpenAI prefix untouched.buildSystemMessage— the fallback dropsdynamicInstructionswhenshouldMoveDynamicInstructionsis set, so relocated content is not also duplicated in the system message. That flag is false at this line for every pre-existing provider, so the fallback is unchanged for them.The summary-carrier branch follows the same split:
hasSummaryBody && promptCacheProvider == nullbecomeshasSummaryBody && !splitsDynamicInstructions, so the summary rides the relocated tail rather than being prepended twice.Tests
src/agents/__tests__/AgentContext.test.ts:moves the dynamic tail behind stable history for openAI/azureOpenAI explicit caching— asserts the system message holds only the stable prefix, the tail sits before the final human turn, and nocache_controlappears on any message.keeps dynamic-only instructions in the system message under explicit caching— guards the newshouldMoveDynamicInstructionsfallback against dropping instructions when there is no stable prefix to anchor.npx jest src/agents src/graphs src/summarization→ 432 passed.tsc -p tsconfig.build.jsonclean. The three failing suites in a full run (src/llm/{anthropic,google,vertexai}/llm.spec.ts) are live-API tests that fail identically on a clean checkout.Downstream
This unblocks the explicit-breakpoint half of danny-avila/LibreChat#14949. LibreChat lands the request-level work separately (deterministic
prompt_cache_key,prompt_cache_retention, and thepromptCacheExplicitlever itself, default off); with this change it can enable that lever without the breakpoint being spent on volatile content.