fix(mnemopi): retained only new auto-retain turns
Auto-retain now slices the transcript after the last retained user turn before storing an episode, preventing cumulative duplicate session transcripts in mnemopi banks. Fixes #4396
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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" },
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user