fix(cache): delay history image pruning to preserve prompt cache prefix#58038
Conversation
pruneProcessedHistoryImages was stripping image blocks from every already-answered user turn on each run. Turn N sends image bytes → provider caches the prefix. Turn N+1 replaces image with text marker → bytes diverge at that message → cache miss from there onward. Now only prune images older than 3 assistant turns. Recent history stays byte-identical so the cached prefix survives, while legacy sessions with persisted image payloads still get cleaned up.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 922344f985
ℹ️ 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".
Greptile SummaryThis PR fixes prompt cache invalidation caused by Key changes:
The boundary conditions are correct: Confidence Score: 5/5
Reviews (1): Last reviewed commit: "fix(cache): delay history image pruning ..." | Re-trigger Greptile |
…ix (openclaw#58038) pruneProcessedHistoryImages was stripping image blocks from every already-answered user turn on each run. Turn N sends image bytes → provider caches the prefix. Turn N+1 replaces image with text marker → bytes diverge at that message → cache miss from there onward. Now only prune images older than 3 assistant turns. Recent history stays byte-identical so the cached prefix survives, while legacy sessions with persisted image payloads still get cleaned up.
|
Granted that this is probably not worth spending human time on, since this whole path is only for cleaning up legacy sessions anyway, but: This makes things worse, right? This moves the point where the inputs diverge from the longest cached prefix to earlier in the context. Since the pruning is idempotent, if you just prune the whole conversation's images, you have all of it in cache from then on, but this change instead means that at each new turn n, you may mutate the assistant input at turn n-3, and anything past that becomes uncached input. (until you get past the legacy image injections, anyway) |
(#60633) * docs: correct overstated prompt-cache comments from #58036 #58037 #58038 * docs: restore purpose context in MCP tool sort comment * docs: drop misleading 'legacy' framing from image-prune comments * docs: restore useful context stripped from image-prune comments * docs: restore 'deterministically' in MCP tool sort comment * docs: restore 'idempotent' at attempt.ts callsite * docs: restore 'provider prompt cache' in context-guard comment
…ix (openclaw#58038) pruneProcessedHistoryImages was stripping image blocks from every already-answered user turn on each run. Turn N sends image bytes → provider caches the prefix. Turn N+1 replaces image with text marker → bytes diverge at that message → cache miss from there onward. Now only prune images older than 3 assistant turns. Recent history stays byte-identical so the cached prefix survives, while legacy sessions with persisted image payloads still get cleaned up.
…enclaw#58037 openclaw#58038 (openclaw#60633) * docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038 * docs: restore purpose context in MCP tool sort comment * docs: drop misleading 'legacy' framing from image-prune comments * docs: restore useful context stripped from image-prune comments * docs: restore 'deterministically' in MCP tool sort comment * docs: restore 'idempotent' at attempt.ts callsite * docs: restore 'provider prompt cache' in context-guard comment
…ix (openclaw#58038) pruneProcessedHistoryImages was stripping image blocks from every already-answered user turn on each run. Turn N sends image bytes → provider caches the prefix. Turn N+1 replaces image with text marker → bytes diverge at that message → cache miss from there onward. Now only prune images older than 3 assistant turns. Recent history stays byte-identical so the cached prefix survives, while legacy sessions with persisted image payloads still get cleaned up.
…enclaw#58037 openclaw#58038 (openclaw#60633) * docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038 * docs: restore purpose context in MCP tool sort comment * docs: drop misleading 'legacy' framing from image-prune comments * docs: restore useful context stripped from image-prune comments * docs: restore 'deterministically' in MCP tool sort comment * docs: restore 'idempotent' at attempt.ts callsite * docs: restore 'provider prompt cache' in context-guard comment
…ix (openclaw#58038) pruneProcessedHistoryImages was stripping image blocks from every already-answered user turn on each run. Turn N sends image bytes → provider caches the prefix. Turn N+1 replaces image with text marker → bytes diverge at that message → cache miss from there onward. Now only prune images older than 3 assistant turns. Recent history stays byte-identical so the cached prefix survives, while legacy sessions with persisted image payloads still get cleaned up.
…enclaw#58037 openclaw#58038 (openclaw#60633) * docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038 * docs: restore purpose context in MCP tool sort comment * docs: drop misleading 'legacy' framing from image-prune comments * docs: restore useful context stripped from image-prune comments * docs: restore 'deterministically' in MCP tool sort comment * docs: restore 'idempotent' at attempt.ts callsite * docs: restore 'provider prompt cache' in context-guard comment
…ix (openclaw#58038) pruneProcessedHistoryImages was stripping image blocks from every already-answered user turn on each run. Turn N sends image bytes → provider caches the prefix. Turn N+1 replaces image with text marker → bytes diverge at that message → cache miss from there onward. Now only prune images older than 3 assistant turns. Recent history stays byte-identical so the cached prefix survives, while legacy sessions with persisted image payloads still get cleaned up.
…enclaw#58037 openclaw#58038 (openclaw#60633) * docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038 * docs: restore purpose context in MCP tool sort comment * docs: drop misleading 'legacy' framing from image-prune comments * docs: restore useful context stripped from image-prune comments * docs: restore 'deterministically' in MCP tool sort comment * docs: restore 'idempotent' at attempt.ts callsite * docs: restore 'provider prompt cache' in context-guard comment
…ix (openclaw#58038) pruneProcessedHistoryImages was stripping image blocks from every already-answered user turn on each run. Turn N sends image bytes → provider caches the prefix. Turn N+1 replaces image with text marker → bytes diverge at that message → cache miss from there onward. Now only prune images older than 3 assistant turns. Recent history stays byte-identical so the cached prefix survives, while legacy sessions with persisted image payloads still get cleaned up.
…enclaw#58037 openclaw#58038 (openclaw#60633) * docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038 * docs: restore purpose context in MCP tool sort comment * docs: drop misleading 'legacy' framing from image-prune comments * docs: restore useful context stripped from image-prune comments * docs: restore 'deterministically' in MCP tool sort comment * docs: restore 'idempotent' at attempt.ts callsite * docs: restore 'provider prompt cache' in context-guard comment
Problem
pruneProcessedHistoryImagesreplaces image blocks with[image data removed - already processed by model]text for every user/toolResult message before the last assistant turn, on every run.This defeats prompt caching for any conversation that includes images.
Fix
Only prune images older than 3 assistant turns (
PRESERVE_RECENT_ASSISTANT_TURNS). Recent history stays byte-identical so the cached prefix survives across turns, while legacy sessions with persisted image payloads still get cleaned up once they age out.The original purpose — per the doc comment — was "idempotent cleanup for legacy sessions", so aggressive per-turn pruning was never needed.
Verification
PRUNED_HISTORY_IMAGE_MARKERappearing at turn N+1 (only referenced in this file + test)applyPiAutoCompactionGuard), not by this prune — keeping ~3 turns of images is a small delta vs. the previous 0-1state-migrations.ts) still exists, so this cleanup remains load-bearing for migrated sessionsTests
6 tests pass including two new cases:
keeps image blocks within the last 3 assistant turns to preserve prompt cacheprunes only old images while preserving recent ones🤖 Generated with Claude Code