-
Notifications
You must be signed in to change notification settings - Fork 40
Fix #32 snapshot deletion semantics #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
52b6ef0
e90b00c
728b9be
6829003
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1396,7 +1396,6 @@ let snapshotTakenAt: string | null = null; | |
| let snapshotTakenOnDate: string | null = null; | ||
| let snapshotReason: string | null = null; | ||
| let snapshotDirty = false; | ||
|
|
||
| function refreshMemorySnapshot(reason: string) { | ||
| memorySnapshot = buildMemoryContext(""); | ||
| snapshotTakenAt = nowTimestamp(); | ||
|
|
@@ -1405,9 +1404,11 @@ function refreshMemorySnapshot(reason: string) { | |
| snapshotDirty = false; | ||
| } | ||
|
|
||
| function getSnapshotMode(): "stable" | "per-turn" { | ||
| function getSnapshotMode(): "stable" | "refresh" | "per-turn" { | ||
| const mode = (process.env.PI_MEMORY_SNAPSHOT ?? "stable").toLowerCase(); | ||
| return mode === "per-turn" ? "per-turn" : "stable"; | ||
| if (mode === "per-turn") return "per-turn"; | ||
| if (mode === "refresh") return "refresh"; | ||
| return "stable"; | ||
| } | ||
|
|
||
| /** Reset snapshot state (for testing). */ | ||
|
|
@@ -1548,18 +1549,33 @@ export default function (pi: ExtensionAPI) { | |
| const searchResults = skipSearch ? "" : await searchRelevantMemories(event.prompt ?? ""); | ||
| memoryContext = buildMemoryContext(searchResults); | ||
| } else { | ||
| // "stable" means stable: once taken, the block is emitted byte-for-byte | ||
| // for the rest of the session. Refreshing on a long-term write or a | ||
| // midnight rollover rewrites the tail of the system prompt and voids the | ||
| // whole conversation's prefix cache — the exact cost the snapshot exists | ||
| // to avoid, paid on the single most common in-session event. The fresh | ||
| // state is not lost: the write is in tool-call history a few messages | ||
| // back, deletions are sent as a correction message below, and | ||
| // memory_read / memory_search reach the files directly. "refresh" restores the old | ||
| // checkpoint behaviour. | ||
| const today = todayStr(); | ||
| const needsRefresh = memorySnapshot === null || snapshotDirty || snapshotTakenOnDate !== today; | ||
| if (needsRefresh) { | ||
| const stale = mode === "refresh" && (snapshotDirty || snapshotTakenOnDate !== today); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
With the default Useful? React with 👍 / 👎. |
||
| if (memorySnapshot === null || stale) { | ||
| const reason = | ||
| memorySnapshot === null ? "before_agent_start" : snapshotDirty ? "long_term_write" : "day_rollover"; | ||
| refreshMemorySnapshot(reason); | ||
| } | ||
| memoryContext = memorySnapshot ?? ""; | ||
| // Deliberately carries no timestamp and no reason word: both change | ||
| // between turns without the memory itself changing, which is enough on | ||
| // its own to invalidate the cache this branch is trying to preserve. | ||
| snapshotCaveat = | ||
| `Snapshot ${snapshotReason} at ${snapshotTakenAt}. ` + | ||
| "Use memory_read / memory_search for the authoritative latest state; " + | ||
| "recent writes may also be visible in tool-call history."; | ||
| mode === "refresh" | ||
| ? `Snapshot ${snapshotReason} at ${snapshotTakenAt}. ` + | ||
| "Use memory_read / memory_search for the authoritative latest state; " + | ||
| "recent writes may also be visible in tool-call history." | ||
| : "Loaded once at session start and not re-read since. Use memory_read / memory_search " + | ||
| "for the authoritative latest state; anything written this session is in tool-call history."; | ||
| } | ||
|
|
||
| if (!memoryContext) return; | ||
|
|
@@ -2114,10 +2130,11 @@ export default function (pi: ExtensionAPI) { | |
| // If either write fails, we never report a successful unrecoverable deletion. | ||
| const recovery = writeRecoveryRecord(target, recoveryDate, result.removed); | ||
| fs.writeFileSync(filePath, result.content, "utf-8"); | ||
| // Deleted facts must leave the injected snapshot too, whichever file | ||
| // they lived in — a forgotten-but-still-injected memory defeats the | ||
| // point of forgetting. | ||
| snapshotDirty = true; | ||
| // Forget is a privacy-sensitive mutation. Refresh the snapshot immediately | ||
| // so deleted content disappears from authoritative context without being | ||
| // copied into persisted correction messages. This intentionally spends one | ||
| // cache invalidation on an explicit deletion. | ||
| refreshMemorySnapshot("memory_forget"); | ||
| await ensureQmdAvailableForUpdate(); | ||
| scheduleQmdUpdate(); | ||
|
|
||
|
|
@@ -2185,7 +2202,9 @@ export default function (pi: ExtensionAPI) { | |
| if (missingEntries.length > 0) { | ||
| const separator = existing.trim() ? "\n\n" : ""; | ||
| fs.writeFileSync(targetPath, `${existing}${separator}${missingEntries.join("\n\n")}\n`, "utf-8"); | ||
| snapshotDirty = true; | ||
| // Restore changes which durable facts are authoritative, so refresh the | ||
| // snapshot instead of persisting restored content in a correction message. | ||
| refreshMemorySnapshot("memory_restore"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This changes AGENTS.md reference: AGENTS.md:L40-L42 Useful? React with 👍 / 👎. |
||
| await ensureQmdAvailableForUpdate(); | ||
| scheduleQmdUpdate(); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The configuration table says stable-mode deletions append a correction, but this commit removes correction messages and immediately rebuilds the snapshot in
memory_forgetandmemory_restore; the preceding behavior section also says they refresh. Update this row so users are not given contradictory cache and persistence semantics.Useful? React with 👍 / 👎.