Skip to content

🔖 feat: Anchor GPT-5.6 Cache Breakpoint to the Stable Prefix - #546

Open
berry-13 wants to merge 4 commits into
mainfrom
feat/openai-explicit-prompt-cache-boundary
Open

berry-13 wants to merge 4 commits into
mainfrom
feat/openai-explicit-prompt-cache-boundary

Conversation

@berry-13

@berry-13 berry-13 commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

With promptCacheExplicit on an OpenAI or Azure client, the GPT-5.6 instruction breakpoint landed on a system message that joined the stable instructions with the per-turn additional_instructions, so it was written every turn and never read back. AgentContext now keeps only the stable instructions in the system message and relocates the dynamic tail, still as a system message, behind stable history before the current turn. The cross-run summary stays ahead of history, and no Anthropic cache_control markers are added on this path.

On the client side, prompt_cache_breakpoint now marks the first instruction message, the end of the leading instruction run (for callers that split one prompt across several), the last history message before the relocated tail, and the message before the current turn: four at most, the explicit-mode limit. Nothing changes unless promptCacheExplicit is true.

Explicit-cache half of LibreChat-AI/LibreChat#15959, tracked at berry-13/LibreChat#243.

Change Type

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)

Testing

  • npx jest src/llm/openai src/agents src/graphs src/summarization: 701 passed, 0 failed
  • npx tsc --noEmit: clean; npx eslint on the touched files: clean
  • New request-shape tests in managedRequests.test.ts cover breakpoint placement for Chat Completions and Responses; new AgentContext.test.ts cases cover the stable system message, the system-role tail, dynamic-only instructions, summary placement, and unchanged Anthropic, OpenRouter, and Bedrock shapes
  • Not exercised against the live GPT-5.6 API

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • Local unit tests pass with my changes

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-03T14:42:21.054709Z d913723 New commits
🔒 Security Review ✅ Completed 2026-09-15T09:15:43.664358Z e3fe253 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

const dynamicTail = shouldMoveDynamicInstructions
? [new HumanMessage(dynamicInstructions)]
: [];

P1 Badge Keep the dynamic tail in an instruction role

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".

@berry-13

Copy link
Copy Markdown
Collaborator Author

Fixed in 77b12e10. The finding is right, and the repository's own declarations agree with it: additional_instructions is documented as a system tail in types/graph.ts:929 and types/run.ts:90, and buildDynamicInstructionsString fills it with that plus the cross-run summary under a comment saying the same. Routing OpenAI and Azure through the relocation emitted it as a HumanMessage, and on those providers a user message ranks below a system one, so enabling explicit caching let later user content override host constraints.

The tail now keeps a system role on the OpenAI explicit path. Anthropic and OpenRouter keep the HumanMessage they shipped with before this branch, which is not this change to make.

The breakpoint had to move with it, which is the second half of the suggestion: selectCacheBreakpointIndexes marked the last system or developer message, and with a system-role tail that is the volatile half, so it now marks the first. Where there is only one instruction message the two are the same message and nothing changes.

Tests: the OpenAI and Azure tails assert a system role, and the breakpoint test asserts it lands on the stable prefix; all three fail on e3fe2534. A fourth pins the Anthropic tail as human and passes on both heads. jest src/agents src/llm/openai is 365 passed, typecheck clean via tsc -p tsconfig.build.json. The 12 failures in the google, anthropic and vertexai llm specs are pre-existing and reproduce on e3fe2534 with this change reverted.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 77b12e10c9

ℹ️ 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".

Comment thread src/llm/openai/index.ts Outdated
Comment on lines 378 to 383
if (
(role === 'system' || role === 'developer') &&
cacheable[index] &&
instructionIndex === -1
) {
instructionIndex = index;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve caching for direct multi-instruction requests

When the exported ChatOpenAI/Azure wrappers are invoked directly rather than through AgentContext, multiple system/developer messages do not follow the assumed stable-first/volatile-last layout. For example, a short system preamble followed by a large stable developer prompt now receives a breakpoint only on the short first message; explicit mode therefore cannot cache the useful combined instruction prefix and may fall below the provider's minimum cacheable prefix. The previous last-instruction selection supported these requests, so the stable-first behavior should be limited to messages explicitly identified as the relocated AgentContext tail rather than applied to every request.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 7d27987. Valid regression: taking only the first instruction message left a direct caller's multi-message stable prompt unmarked once history separated it from the current turn. selectCacheBreakpointIndexes now marks both the first instruction (the stable prefix AgentContext builds) and the end of the leading instruction run, which are the same message when there is one. With the history breakpoint that is at most three, inside the four an explicit request may write. Covered for Chat Completions and Responses in managedRequests.test.ts ("also marks the end of a leading multi-message instruction prompt", "marks the leading instruction run on Responses input too").

berry-13 and others added 3 commits October 3, 2026 13:14
`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.
Moving the dynamic tail behind the stable prefix sent it as a `HumanMessage`
on OpenAI and Azure. That tail is `additional_instructions` and the cross-run
summary, declared a system tail in `graph.ts` and `run.ts` and built as one,
so enabling explicit caching dropped host constraints to user rank where later
user content outranks them: a caching switch changing how an agent behaves.

The tail keeps a system role on that path now. It stays a `HumanMessage` for
Anthropic and OpenRouter, which shipped it that way before this branch.

The breakpoint had to move with it. `selectCacheBreakpointIndexes` marked the
last system or developer message, which with a system-role tail is the
volatile half; it takes the first now, which is the stable prefix, and is the
same message whenever there is only one.
Taking only the first system or developer message dropped the breakpoint
from a direct caller that splits one stable prompt across several
instruction messages, once history separates it from the current turn.
The end of the leading instruction run is marked as well. It is the same
message whenever there is one instruction message, keeps the stable prefix
AgentContext builds marked, and stays at three breakpoints at most with the
history one, inside the four an explicit request may write.
@berry-13
berry-13 force-pushed the feat/openai-explicit-prompt-cache-boundary branch from 77b12e1 to 7d27987 Compare October 3, 2026 12:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d279878d4

ℹ️ 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".

? [new HumanMessage(dynamicInstructions)]
? [
keepsInstructionRole
? new SystemMessage(dynamicInstructions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve a breakpoint before the relocated dynamic tail

When explicit caching is enabled with additional_instructions, this inserts the dynamic system message immediately before the latest human message. selectCacheBreakpointIndexes then scans backward from that human message and chooses this tail as the history breakpoint, rather than the preceding assistant/history message. On each new user turn the tail moves after newly completed history, so the prefix ending at that breakpoint cannot match the prior turn; ordinary one-call chat turns therefore reuse only the stable-instructions breakpoint and repeatedly miss the potentially large conversation history that the previous fixed-at-the-front system layout could cache. Add a separate breakpoint immediately before the relocated tail (while retaining the tail breakpoint if needed for tool-loop calls).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in d913723. Valid: the history breakpoint landed on the relocated tail, which moves behind each new turn, so the history prefix never recurred. selectCacheBreakpointIndexes now also marks the last history message before an instruction run that directly precedes the current turn, keeping the tail breakpoint for tool-loop calls (four at most). The cross-run summary carrier had the same problem, so on the OpenAI explicit path it now stays ahead of history as it does without explicit caching. Tests: managedRequests "marks the history in front of a relocated tail, not only the tail" and AgentContext "keeps the summary ahead of history and only the tail behind it under explicit caching", both failing on 7d27987.

The relocated tail sits before the current turn and moves behind each new
one, so a breakpoint ending at it never recurs and the conversation history
in front of it was never read back. The client now also marks the last
history message before a run of instructions that directly precedes the
current turn, four breakpoints at most.

The cross-run summary no longer rides that tail on the OpenAI explicit path.
It stays ahead of history as it does without explicit caching, where it
changes only on re-summarization.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant