diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 860266c53..afda49031 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed mnemopi auto-retain storing cumulative full-session transcripts on every retention interval; subsequent retains now store only newly completed user-turn suffixes. ([#4396](https://github.com/can1357/oh-my-pi/issues/4396)) + ## [16.3.4] - 2026-07-03 ### Fixed diff --git a/packages/coding-agent/src/mnemopi/state.ts b/packages/coding-agent/src/mnemopi/state.ts index da3c384da..8831661d6 100644 --- a/packages/coding-agent/src/mnemopi/state.ts +++ b/packages/coding-agent/src/mnemopi/state.ts @@ -116,6 +116,22 @@ interface MnemopiStoredMemoryRow { session_id?: unknown; } +type MnemopiRetentionMessage = { role: string; content: string }; + +function sliceUnretainedMessages( + messages: MnemopiRetentionMessage[], + lastRetainedTurn: number, +): MnemopiRetentionMessage[] { + if (lastRetainedTurn <= 0) return messages; + let userTurns = 0; + for (let index = 0; index < messages.length; index++) { + if (messages[index].role !== "user") continue; + userTurns++; + if (userTurns > lastRetainedTurn) return messages.slice(index); + } + return []; +} + export function getMnemopiSessionState(session: AgentSession | undefined): MnemopiSessionState | undefined { return session ? (session as AgentSessionWithMnemopiState)[kMnemopiSessionState] : undefined; } @@ -339,7 +355,10 @@ export class MnemopiSessionState { const flat = extractMessages(this.session.sessionManager); const userTurns = flat.filter(message => message.role === "user").length; if (userTurns - this.lastRetainedTurn < this.config.retainEveryNTurns) return; - await this.retainMessages(flat, `${this.sessionId}-${Date.now()}`); + await this.retainMessages( + sliceUnretainedMessages(flat, this.lastRetainedTurn), + `${this.sessionId}-${Date.now()}`, + ); this.lastRetainedTurn = userTurns; } diff --git a/packages/coding-agent/test/memory-tools.test.ts b/packages/coding-agent/test/memory-tools.test.ts index ffc850e67..8afe7e1b3 100644 --- a/packages/coding-agent/test/memory-tools.test.ts +++ b/packages/coding-agent/test/memory-tools.test.ts @@ -450,14 +450,15 @@ describe("Mnemopi backend lifecycle", () => { tempDbPath = undefined; }); - it("auto-retain uses the cumulative transcript turn count", async () => { + it("auto-retain stores only the not-yet-retained suffix", async () => { const entries = Array.from({ length: 4 }, (_, index) => ({ type: "message", message: { role: "user", content: `turn ${index + 1}` }, })); - const state = registerMnemopiState(makeMnemopiConfig({ retainEveryNTurns: 4 }), { + const state = registerMnemopiState(makeMnemopiConfig({ retainEveryNTurns: 2 }), { cwd: "/work/project-alpha", }); + state.lastRetainedTurn = 2; (state.session.sessionManager as { getEntries: () => unknown[] }).getEntries = () => entries; const retainSpy = vi.spyOn(state, "retainMessages").mockResolvedValue(); @@ -465,8 +466,6 @@ describe("Mnemopi backend lifecycle", () => { expect(retainSpy).toHaveBeenCalledTimes(1); expect(retainSpy.mock.calls[0][0]).toEqual([ - { role: "user", content: "turn 1" }, - { role: "user", content: "turn 2" }, { role: "user", content: "turn 3" }, { role: "user", content: "turn 4" }, ]);