anomalyco / anomalyco/opencode
Compaction reuses the session's prompt_cache_key while sending a maximally divergent prefix — a guaranteed full cache write that can never be read
@neriousy is already working on this.
Since Aug 18, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
The compaction call is dispatched with the same prompt_cache_key as the main conversation while
sending a request whose prefix diverges from it at the very first byte. The result is a full-price
cache write on every compaction that can never produce a read, submitted onto the same cache
partition the live conversation depends on.
Both call sites derive the key from the session id alone:
- main loop — https://github.com/anomalyco/opencode/blob/39be1595993de57a5a667c86e21ff788a80ebbbe/packages/core/src/session/model-request.ts#L223
- compaction — https://github.com/anomalyco/opencode/blob/39be1595993de57a5a667c86e21ff788a80ebbbe/packages/core/src/session/compaction.ts#L277
SessionPromptCacheKey.make is a pure function of the id, so the two are byte-identical.
What is sent under that shared key:
tools: [], one synthetic user message, no system part. Prompt caching is prefix matching and the
tool block is the first thing in the prefix, so replacing the full tool set with an empty one
guarantees divergence at position zero. The payload is also a different serialization of the same
conversation ([Assistant tool call]: name(args), tool output truncated to
TOOL_OUTPUT_MAX_CHARS = 2000) rather than the wire messages the main loop already has cached.
None of that divergence is a bug — the docs specify tools-disabled summarization. Sharing the
conversation's cache key with it is. The compaction request:
- can never read from cache — nothing with that prefix has ever been written;
- writes the entire serialized head at write price, which near the compaction ceiling is several
hundred thousand tokens; - does both under the live conversation's key, where a provider treating
prompt_cache_keyas a
routing or partition hint may evict the entry the next main-loop request is about to read.
prompt_cache_key is the wire field on every affected protocol — open-responses.ts:542,
openai-chat.ts:514, openrouter.ts:124, and xai.ts:51 (as x-grok-conv-id).
Measured impact
Across a local opencode.db with 3,245 costed sessions, compaction spend is separable: the
compaction call's usage is published via UsageRecorded and lands on the session row, never on an
assistant message row. The difference between the two is the compaction call.
| sessions | session cost | session − Σ(message) | |
|---|---|---|---|
| has ≥1 compaction | 52 | $2,089.69 | $36.95 |
| no compaction | 3,193 | $570.22 | $0.05 |
Only compaction.ts:266 and title.ts:76 publish UsageRecorded; the 3,193-session control prices
title generation at $0.05 in total, so essentially all of the $36.95 is compaction. That is ~1.4% of
spend that is, by construction, 100% cache-miss.
Expected behaviour
Either of:
- Preferred — issue the compaction request as a continuation of the already-cached main-loop
prefix (same tools, same system, same lowered messages) with the summarize instruction appended, so
the bulk of it is a cache read rather than a cold write; or - give compaction a distinct key (e.g.
${sessionID}:compaction), which at minimum stops a
guaranteed-miss request from sharing a partition with the conversation it is summarizing, and lets
successive compactions within one session hit each other.
Secondary, same function
buildPrompt places the volatile <previous-summary> block before the stable SUMMARY_TEMPLATE:
Everything after the first divergence is uncacheable, so putting the per-compaction summary first
makes the fixed template — the one part of this prompt identical across compactions — unreachable by
any prefix cache. Emitting SUMMARY_TEMPLATE first and the previous summary after it is a pure
reordering with no behavioural change. Moot if the first option above is taken.
Plugins
No response
OpenCode version
0.0.0-beta-17498 (source verified at 39be1595993de57a5a667c86e21ff788a80ebbbe)
Steps to reproduce
- Run any session on a provider that reports
cache_write_tokensuntil auto-compaction fires. - Compare the session's total cost against the sum of
$.costover its assistant messages. - The difference is the compaction call, and its
cache.readis zero every time.
Screenshot and/or share link
No response
Operating System
No response
Terminal
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.