From 92418fc9da1a5837c9770a2a7366e83d87de2fd3 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 10 Jun 2026 14:35:44 +0900 Subject: [PATCH] fix(memory-core): filter stale recall entries in REM harness preview --- CHANGELOG.md | 1 + .../memory-core/src/dreaming-phases.test.ts | 69 +++++++++++++++++++ extensions/memory-core/src/rem-harness.ts | 12 ++-- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9b72ff6e465..c1ec0d9eae8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Docs: https://docs.openclaw.ai - Release/CI/E2E: Docker E2E and live Docker harness runs now apply default memory, CPU, and process ceilings while preserving explicit per-lane overrides. - Release/CI/E2E: plugin lifecycle matrix resource sampling now fails phases that exceed RSS, wall-clock, or CPU ceilings instead of only logging the measurements. - Release/CI/E2E: Codex npm plugin live assertions now cap transcript discovery and diagnostic log reads so failure proof stays bounded. +- Memory: keep doctor REM harness previews aligned with live REM by dropping short-term recall snippets whose source files disappeared before rendering preview output. Thanks @samzong and @frankekn. - Tests/state isolation: QA Lab valid-tool-call metrics now require runtime tool-call evidence when runtime parity data is available instead of counting tool-backed scenario pass status alone. - Tests/state isolation: QA Lab runtime parity now fails planned-only tool-call rows without matching tool results instead of treating matching mock plans as real tool evidence. - Tests/state isolation: provider, media, auth, cron, task, session, sandbox, Gateway, and Codex timeout fixtures now scope more home/state/env data per test, reducing cross-test leakage and making release validation failures less noisy. (#90027, #89974) diff --git a/extensions/memory-core/src/dreaming-phases.test.ts b/extensions/memory-core/src/dreaming-phases.test.ts index 795a8764d86b..be6118674033 100644 --- a/extensions/memory-core/src/dreaming-phases.test.ts +++ b/extensions/memory-core/src/dreaming-phases.test.ts @@ -2916,6 +2916,75 @@ describe("previewRemHarness", () => { expect(preview.grounded).toBeNull(); }); + it("skips REM short-term candidates whose source file disappeared", async () => { + const workspaceDir = await createDreamingWorkspace(); + const nowMs = new Date("2026-04-15T12:00:00.000Z").getTime(); + await fs.writeFile( + path.join(workspaceDir, "memory", "2026-04-14.md"), + "Move backups to S3 Glacier.\n", + "utf-8", + ); + await recordShortTermRecalls({ + workspaceDir, + query: "live backup", + nowMs, + results: [ + { + path: "memory/2026-04-14.md", + startLine: 1, + endLine: 1, + score: 0.91, + snippet: "Move backups to S3 Glacier.", + source: "memory", + }, + ], + }); + await recordShortTermRecalls({ + workspaceDir, + query: "stale provider setup", + nowMs, + results: [ + { + path: "memory/.dreams/session-corpus/2026-04-16.txt", + startLine: 2, + endLine: 2, + score: 0.88, + snippet: "Assistant: Documented Ollama provider setup.", + source: "memory", + }, + ], + }); + + const preview = await previewRemHarness({ + workspaceDir, + nowMs, + pluginConfig: { + dreaming: { + enabled: true, + phases: { + rem: { + enabled: true, + lookbackDays: 7, + limit: 10, + minPatternStrength: 0, + }, + }, + }, + }, + }); + + const candidateTruthSnippets = preview.rem.candidateTruths + .map((entry) => entry.snippet) + .join("\n"); + const bodyText = preview.rem.bodyLines.join("\n"); + expect(preview.recallEntryCount).toBe(1); + expect(preview.rem.sourceEntryCount).toBe(1); + expect(candidateTruthSnippets).toContain("Move backups to S3 Glacier."); + expect(candidateTruthSnippets).not.toContain("Documented Ollama provider setup"); + expect(bodyText).toContain("Move backups to S3 Glacier."); + expect(bodyText).not.toContain("Documented Ollama provider setup"); + }); + it("skips REM preview when rem.limit=0 while still ranking deep candidates", async () => { const workspaceDir = await createDreamingWorkspace(); const nowMs = new Date("2026-04-15T12:00:00.000Z").getTime(); diff --git a/extensions/memory-core/src/rem-harness.ts b/extensions/memory-core/src/rem-harness.ts index 02d6e4a4f813..3eb824208e14 100644 --- a/extensions/memory-core/src/rem-harness.ts +++ b/extensions/memory-core/src/rem-harness.ts @@ -13,6 +13,7 @@ import { } from "./dreaming-phases.js"; import { previewGroundedRemMarkdown, type GroundedRemPreviewResult } from "./rem-evidence.js"; import { + filterLiveShortTermRecallEntries, rankShortTermPromotionCandidates, readShortTermRecallEntries, type PromotionCandidate, @@ -131,10 +132,13 @@ export async function previewRemHarness( workspaceDir: params.workspaceDir, nowMs, }); - const recallEntries = filterRecallEntriesWithinLookback({ - entries: allRecallEntries, - nowMs, - lookbackDays: remConfig.lookbackDays, + const recallEntries = await filterLiveShortTermRecallEntries({ + workspaceDir: params.workspaceDir, + entries: filterRecallEntriesWithinLookback({ + entries: allRecallEntries, + nowMs, + lookbackDays: remConfig.lookbackDays, + }), }); const remPreviewLimit = resolveRemPreviewLimit(remConfig.limit, params.remPreviewLimit); const remSkipped = remConfig.limit <= 0 || remPreviewLimit <= 0;