From 9ad5b8eb47f41b3cd4691fc9bd98d967f85253ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillipp=20J=C3=A4ger?= Date: Sun, 9 Aug 2026 14:52:17 +0200 Subject: [PATCH] fix: deduplicate embedded Tags footer in compaction restore Auto-capture embeds a "Tags: ..." footer inside the memory body (added in #131); compaction restore appends its own canonical Tags line, duplicating the tags in the restored context. Strip the embedded footer when a canonical line follows. Regression from #131. --- src/index.ts | 10 ++++++- tests/compaction-agent-preservation.test.ts | 31 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 5128167..a233b51 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1023,8 +1023,16 @@ function formatMemoriesForCompaction(memories: any[]): string { let output = `## Restored Session Memory\n\n`; memories.forEach((m, i) => { + // Auto-capture stores the tags footer inside the memory body itself + // ("…\n\nTags: …"). Strip it when a canonical tags line follows, so the + // tags are not duplicated in the restored context (#131). + const body = + m.tags && m.tags.length > 0 + ? (m.memory ?? "").replace(/\n*Tags: [^\n]*\s*$/, "") + : (m.memory ?? ""); + output += `### Memory ${i + 1}\n`; - output += `${m.memory}\n\n`; + output += `${body}\n\n`; if (m.tags && m.tags.length > 0) { output += `Tags: ${m.tags.join(", ")}\n\n`; } diff --git a/tests/compaction-agent-preservation.test.ts b/tests/compaction-agent-preservation.test.ts index 614e9c4..e3b288b 100644 --- a/tests/compaction-agent-preservation.test.ts +++ b/tests/compaction-agent-preservation.test.ts @@ -200,6 +200,37 @@ describe("session.compacted agent preservation (#236)", () => { expect(result.parsed?.promptCalls[0]?.body?.noReply).toBe(true); }); + it("does not duplicate the Tags line when the stored memory already embeds one (regression from #131)", () => { + const result = runCompactionScenario({ + sessionAgent: "my-orchestrator", + memories: [ + { + memory: "We chose libSQL over sqlite3.\n\nTags: architecture, decision", + tags: ["architecture", "decision"], + }, + ], + messages: [{ info: { role: "user", agent: "my-orchestrator" } }], + }); + + expect(result.exitCode).toBe(0); + const text = result.parsed?.promptCalls[0]?.body?.parts?.[0]?.text ?? ""; + const tagsLines = text.match(/^Tags: /gm) ?? []; + expect(tagsLines).toHaveLength(1); + }); + + it("still appends a single Tags line when the memory body has no embedded footer", () => { + const result = runCompactionScenario({ + sessionAgent: "my-orchestrator", + memories: [{ memory: "Plain memory body without footer.", tags: ["decision"] }], + messages: [{ info: { role: "user", agent: "my-orchestrator" } }], + }); + + expect(result.exitCode).toBe(0); + const text = result.parsed?.promptCalls[0]?.body?.parts?.[0]?.text ?? ""; + const tagsLines = text.match(/^Tags: /gm) ?? []; + expect(tagsLines).toHaveLength(1); + }); + it("does not call session.prompt when there are no memories", () => { const result = runCompactionScenario({ sessionAgent: "my-orchestrator",