feat: cache-stable memory snapshot as first message of history - #26
Open
caos30 wants to merge 1 commit into
Open
feat: cache-stable memory snapshot as first message of history#26caos30 wants to merge 1 commit into
caos30 wants to merge 1 commit into
Conversation
Replace the per-request system-prompt injection with a frozen snapshot injected as a synthetic first message of the conversation. Why: the system prompt sits BEFORE the message history in the request payload, so any memory edit (or the ms-precision timestamps in the injected metadata, joshuadavidthomas#24) invalidated the provider prompt cache for the entire session — including the multi-million-token histories where cache matters most. Users measured ~10% cache hit where ~90% was expected. Design: - experimental.chat.messages.transform unshifts a synthetic user message containing the rendered memory blocks, prefixed with a versioned HTML comment marker so the plugin can recognise (and never clobber) its own injection among messages injected by other plugins - the snapshot is frozen per session: once rendered, the exact same bytes are re-injected on every request, keeping the system prompt + history a stable cache prefix. In-session memory edits remain visible to the model as natural amendments in the conversation history (coherent timeline: the model still sees what a block said when the session started) - snapshots persist to ~/.local/share/opencode/agent-memory/snapshots/ (atomic writes) so they survive opencode/VM restarts byte-identically; in-memory Map is only an L1 cache - session.compacted invalidates the snapshot so post-compaction requests get a fresh one - journal system note stays in the system prompt (static text, cache-safe) Validation: 8-turn real session against a DeepSeek provider panel — 90.6% cache hit on the first turn, 98.2% overall, prefix never broke across memory block edits, AGENTS.md edits and project skill edits. Refs joshuadavidthomas#8, joshuadavidthomas#24. Alternative to joshuadavidthomas#25 (which fixes the injection placement only); if this lands, joshuadavidthomas#25 can be closed.
|
2 clusters identified |
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
The system prompt sits before the message history in the request payload, so injecting memory blocks into the system prompt breaks the provider prompt cache in two ways:
<memory_metadata>change every request (<memory_metadata> breaks KV-cache / prompt-cache by injecting ms-precision timestamp every turn #24)This plugin encourages frequent memory updates, which makes the problem worse the more it is used as intended.
Approach
Inject the memory blocks as a synthetic first message of the conversation (
experimental.chat.messages.transform) instead of the system prompt, and freeze them per session:<!-- opencode-agent-memory:snapshot:1 -->) lets the plugin recognise its own injection and never clobber messages injected by other plugins.~/.local/share/opencode/agent-memory/snapshots/<sessionID>.xml(atomic writes, 30-day purge) so they survive opencode/VM restarts byte-identically — a restart does not bust the cache either. In-memory Map is only an L1 cache.session.compactedinvalidates the snapshot so post-compaction requests render a fresh one.Validation
Real 8-turn session (4 user / 4 agent, 80 API requests, ~10.9M tokens) against a DeepSeek usage panel:
The cache prefix never broke across: memory block edits mid-session, AGENTS.md edits and project skill edits. Before this change the same workload measured ~10% cache hit.
Unit tests cover freeze/re-use, simulated restart (new store instance, same disk → no re-render), invalidation and purge.
bun test: 32 pass.tsc --noEmit: clean.Refs #8, #24. This is a larger but more complete alternative to #25 (which fixes injection placement only); if this lands, #25 can be closed.