stable mode: stop invalidating the KV cache mid-session - #32
Open
Radu0120 wants to merge 1 commit into
Open
Conversation
The stable snapshot exists to keep the system prompt byte-identical between turns, but it refreshed on long-term writes and day rollover, and stamped a second-precision timestamp plus a reason word into the injected header. Both rewrite the prompt tail and void the prefix cache for the whole conversation - on the most common in-session event. - refresh only when no snapshot exists (session_start / after compact) - drop the volatile "Snapshot <reason> at <hh:mm:ss>" caveat line - deliver deletions/restores as an injected session message instead of re-rendering the block - add PI_MEMORY_SNAPSHOT=refresh for the previous behaviour Signed-off-by: RaduAdumitroaei <radueugen84@yahoo.com>
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.
PI_MEMORY_SNAPSHOT=stableis documented as snapshotting memory "for KV cache stability", but three things re-render or move the injected block during a session, and each one costs a full prompt reprocess:snapshotDirtyon a long-term write (index.ts:1713) — writing memory is what a session does, so the stable path breaks on first use. The comment justifying it notes the model "should always see" the write; the header two lines above already says recent writes are visible in tool-call history, which is where the fact actually is, a few messages back.snapshotTakenOnDate !== today— a midnight rollover dumps a live session's cache for a date change.Snapshot ${snapshotReason} at ${snapshotTakenAt}— a second-precision wall clock inside the cached prefix, plus a reason word whose length changes, so even a content-identical refresh moves every byte after it.Measured, llama.cpp + a 27B GatedDeltaNet hybrid, ~15k-token session:
target: long_termwritememory_forgetThe cost is architecture-dependent and worst on recurrent/hybrid models (GatedDeltaNet, Mamba): their state can only be continued forward, never rewound to a partial match, so a changed prefix reuses zero tokens rather than the shared head. On plain attention the same change costs the tail only — still real on a long session.
Changes
needsRefreshfires only when there is no snapshot.session_startandsession_before_compactremain the checkpoints; compaction rewrites the prompt anyway, so that boundary is free.before_agent_start→message,customType: "pi-memory-correction"), which lands at the tail of the history and costs only its own tokens. A forgotten memory still stops being authoritative — that part of the original reasoning is right, it just cannot be paid for in the prompt prefix. My first attempt appended the corrections to the system prompt instead; that is the 15.5k-token row in the table above, measured, which is what motivated the message channel.PI_MEMORY_SNAPSHOT=refreshrestores the previous behaviour exactly;stableandper-turnare unchanged in name and meaning. README env table, snapshot section and troubleshooting row updated.Tests
bun test test/unit.test.ts→ 184 pass, 0 fail. Updated the two tests that encoded the old contract (long_termwrite now asserts the prompt is unchanged; the caveat test asserts nohh:mm:ssappears), and added two:PI_MEMORY_SNAPSHOT=refreshstill refreshes on a long-term write, andmemory_forgetsends a correction message once, drains it, and never touches the systemPrompt.npm run buildandbiome checkare clean.Note for review
pi delivers the injected message with role
user, so the correction reads as if the user said it. It is tagged withcustomTypeand the content is explicit about what it is, but if you would rather it went through a different channel, say so and I will rework it.One design call worth confirming: I made the cache-safe behaviour the default and put the old one behind
refresh, on the grounds that a mode namedstableshould be stable. Happy to invert that if you would rather the change be strictly opt-in.