From 1b419dc1adbd29a1cc1a3bb6f9f099ee8eb3468f Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 14 Apr 2026 19:30:03 -0400 Subject: [PATCH] memory: tighten dreaming bot triage fixes --- .../memory-core/src/dreaming-phases.test.ts | 4 +- .../src/short-term-promotion.test.ts | 40 +++++++++++++++++++ .../memory-core/src/short-term-promotion.ts | 15 ++++--- .../host/session-files.test.ts | 25 ++++++++++++ src/memory-host-sdk/host/session-files.ts | 2 +- 5 files changed, 77 insertions(+), 9 deletions(-) diff --git a/extensions/memory-core/src/dreaming-phases.test.ts b/extensions/memory-core/src/dreaming-phases.test.ts index 827a3f0402d..9e587e1ec45 100644 --- a/extensions/memory-core/src/dreaming-phases.test.ts +++ b/extensions/memory-core/src/dreaming-phases.test.ts @@ -713,8 +713,8 @@ describe("memory-core dreaming phases", () => { expect(Object.keys(sessionIngestion.files)).toHaveLength(1); expect(Object.values(sessionIngestion.files)).toEqual([ expect.objectContaining({ - lineCount: 2, - lastContentLine: 2, + lineCount: 0, + lastContentLine: 0, contentHash: expect.any(String), }), ]); diff --git a/extensions/memory-core/src/short-term-promotion.test.ts b/extensions/memory-core/src/short-term-promotion.test.ts index 9be0b976963..1b9dfb4fbef 100644 --- a/extensions/memory-core/src/short-term-promotion.test.ts +++ b/extensions/memory-core/src/short-term-promotion.test.ts @@ -230,6 +230,38 @@ describe("short-term promotion", () => { }); }); + it("ignores bullet-prefixed dreaming snippets when recording short-term recalls", async () => { + await withTempWorkspace(async (workspaceDir) => { + await recordShortTermRecalls({ + workspaceDir, + query: "action preference", + results: [ + { + path: "memory/2026-04-03.md", + source: "memory", + startLine: 1, + endLine: 5, + score: 0.92, + snippet: [ + "- Candidate: Default to action.", + " - confidence: 0.76", + " - evidence: memory/.dreams/session-corpus/2026-04-08.txt:1-1", + " - recalls: 3", + " - status: staged", + ].join("\n"), + }, + ], + }); + + expect( + JSON.parse(await fs.readFile(resolveShortTermRecallStorePath(workspaceDir), "utf-8")), + ).toMatchObject({ + version: 1, + entries: {}, + }); + }); + }); + it("records recalls and ranks candidates with weighted scores", async () => { await withTempWorkspace(async (workspaceDir) => { await recordShortTermRecalls({ @@ -1015,6 +1047,14 @@ describe("short-term promotion", () => { }); }); + it("treats diff-prefixed dreaming snippets as contaminated", () => { + expect( + __testing.isContaminatedDreamingSnippet( + "@@ -1,1 - Candidate: Default to action. confidence: 0.76 evidence: memory/.dreams/session-corpus/2026-04-08.txt:1-1 recalls: 3 status: staged", + ), + ).toBe(true); + }); + it("skips direct candidates that exceed maxAgeDays during apply", async () => { await withTempWorkspace(async (workspaceDir) => { const applied = await applyShortTermPromotions({ diff --git a/extensions/memory-core/src/short-term-promotion.ts b/extensions/memory-core/src/short-term-promotion.ts index 20bfd85122c..8f42deae663 100644 --- a/extensions/memory-core/src/short-term-promotion.ts +++ b/extensions/memory-core/src/short-term-promotion.ts @@ -33,12 +33,16 @@ const SHORT_TERM_LOCK_WAIT_TIMEOUT_MS = 10_000; const SHORT_TERM_LOCK_STALE_MS = 60_000; const SHORT_TERM_LOCK_RETRY_DELAY_MS = 40; const DREAMING_NARRATIVE_PROMPT_PREFIX = "Write a dream diary entry from these memory fragments"; -const DREAMING_PROMOTION_META_PREFIX = "openclaw-memory-promotion:"; // Repeated dreaming revisits should be able to clear the default promotion gate // without requiring separate organic recall traffic for the same snippet. const PHASE_SIGNAL_LIGHT_BOOST_MAX = 0.06; const PHASE_SIGNAL_REM_BOOST_MAX = 0.09; const PHASE_SIGNAL_HALF_LIFE_DAYS = 14; +const DREAMING_NARRATIVE_RUN_PREFIX = "dreaming-narrative-"; +const DREAMING_CANDIDATE_LEAD_RE = + /^(?:[-*+>]\s+|@@\s*-\d+(?:,\d+)?\s+[-*+]\s+|[([]\s*)*Candidate:/i; +const DREAMING_REFLECTION_LEAD_RE = + /^(?:[-*+>]\s+|@@\s*-\d+(?:,\d+)?\s+[-*+]\s+|[([]\s*)*Reflections?:/i; const inProcessShortTermLocks = new Map>(); const ensuredShortTermDirs = new Map>(); @@ -244,15 +248,14 @@ function isContaminatedDreamingSnippet(raw: string): boolean { } if ( snippet.includes(DREAMING_NARRATIVE_PROMPT_PREFIX) || - snippet.includes(DREAMING_PROMOTION_META_PREFIX) || - snippet.includes("dreaming-narrative-") + snippet.includes(PROMOTION_MARKER_PREFIX) || + snippet.includes(DREAMING_NARRATIVE_RUN_PREFIX) ) { return true; } - const hasCandidateLead = /^Candidate:/i.test(snippet) || /[([]\s*Candidate:/i.test(snippet); - const hasReflectionLead = - /^Reflections?:/i.test(snippet) || /[([]\s*Reflections?:/i.test(snippet); + const hasCandidateLead = DREAMING_CANDIDATE_LEAD_RE.test(snippet); + const hasReflectionLead = DREAMING_REFLECTION_LEAD_RE.test(snippet); const hasConfidence = /\bconfidence:\s*\d/i.test(snippet); const hasEvidence = /\bevidence:\s*(?:memory\/\.dreams\/session-corpus\/|memory\/)/i.test( snippet, diff --git a/src/memory-host-sdk/host/session-files.test.ts b/src/memory-host-sdk/host/session-files.test.ts index e0ac3ad57b5..c585698b96c 100644 --- a/src/memory-host-sdk/host/session-files.test.ts +++ b/src/memory-host-sdk/host/session-files.test.ts @@ -203,4 +203,29 @@ describe("buildSessionEntry", () => { expect(entry?.content).toContain("Assistant: A drifting archive breathed in moonlight."); expect(entry?.lineMap).toEqual([1, 2]); }); + + it("does not flag transcripts when dreaming markers only appear mid-string", async () => { + const jsonlLines = [ + JSON.stringify({ + type: "custom", + customType: "note", + data: { + runId: "user-context-dreaming-narrative-light-1775894400455", + }, + }), + JSON.stringify({ + type: "message", + message: { role: "user", content: "Keep the archive index updated." }, + }), + ]; + const filePath = path.join(tmpDir, "substring-marker-session.jsonl"); + await fs.writeFile(filePath, jsonlLines.join("\n")); + + const entry = await buildSessionEntry(filePath); + + expect(entry).not.toBeNull(); + expect(entry?.generatedByDreamingNarrative).toBeUndefined(); + expect(entry?.content).toContain("User: Keep the archive index updated."); + expect(entry?.lineMap).toEqual([2]); + }); }); diff --git a/src/memory-host-sdk/host/session-files.ts b/src/memory-host-sdk/host/session-files.ts index 7417cc59a62..221f21f8ca5 100644 --- a/src/memory-host-sdk/host/session-files.ts +++ b/src/memory-host-sdk/host/session-files.ts @@ -47,7 +47,7 @@ function isDreamingNarrativeBootstrapRecord(record: unknown): boolean { } function hasDreamingNarrativeRunId(value: unknown): boolean { - return typeof value === "string" && value.includes(DREAMING_NARRATIVE_RUN_PREFIX); + return typeof value === "string" && value.startsWith(DREAMING_NARRATIVE_RUN_PREFIX); } function isDreamingNarrativeGeneratedRecord(record: unknown): boolean {