memory: narrow dreaming contamination heuristics

This commit is contained in:
Gustavo Madeira Santana
2026-04-14 19:54:17 -04:00
parent 1b9457500d
commit 46fa55ace1
2 changed files with 51 additions and 12 deletions

View File

@@ -262,6 +262,36 @@ describe("short-term promotion", () => {
});
});
it("keeps ordinary snippets that only quote dreaming prompt markers", async () => {
await withTempWorkspace(async (workspaceDir) => {
await recordShortTermRecalls({
workspaceDir,
query: "debug note",
results: [
{
path: "memory/2026-04-03.md",
source: "memory",
startLine: 1,
endLine: 1,
score: 0.75,
snippet:
"Debug note: quote Write a dream diary entry from these memory fragments for docs, but do not use dreaming-narrative-like labels in production.",
},
],
});
const store = JSON.parse(
await fs.readFile(resolveShortTermRecallStorePath(workspaceDir), "utf-8"),
) as { entries: Record<string, { snippet: string }> };
expect(Object.values(store.entries)).toEqual([
expect.objectContaining({
snippet:
"Debug note: quote Write a dream diary entry from these memory fragments for docs, but do not use dreaming-narrative-like labels in production.",
}),
]);
});
});
it("records recalls and ranks candidates with weighted scores", async () => {
await withTempWorkspace(async (workspaceDir) => {
await recordShortTermRecalls({
@@ -1063,6 +1093,22 @@ describe("short-term promotion", () => {
).toBe(true);
});
it("does not treat ordinary candidate notes with daily-memory evidence as contaminated", () => {
expect(
__testing.isContaminatedDreamingSnippet(
"Candidate: move backups weekly. confidence: 0.76 evidence: memory/2026-04-08.md:1-1",
),
).toBe(false);
});
it("treats transcript-style dreaming prompt echoes as contaminated", () => {
expect(
__testing.isContaminatedDreamingSnippet(
"[main/dreaming-narrative-light.jsonl#L1] Write a dream diary entry from these memory fragments:",
),
).toBe(true);
});
it("skips direct candidates that exceed maxAgeDays during apply", async () => {
await withTempWorkspace(async (workspaceDir) => {
const applied = await applyShortTermPromotions({

View File

@@ -32,13 +32,13 @@ const SHORT_TERM_LOCK_RELATIVE_PATH = path.join("memory", ".dreams", "short-term
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";
// 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_TRANSCRIPT_PROMPT_LINE_RE =
/\[[^\]]*dreaming-narrative[^\]]*]\s*Write a dream diary entry from these memory fragments:?/i;
const inProcessShortTermLocks = new Map<string, Promise<void>>();
const ensuredShortTermDirs = new Map<string, Promise<void>>();
@@ -277,9 +277,8 @@ function isContaminatedDreamingSnippet(raw: string): boolean {
return false;
}
if (
snippet.includes(DREAMING_NARRATIVE_PROMPT_PREFIX) ||
snippet.includes(PROMOTION_MARKER_PREFIX) ||
snippet.includes(DREAMING_NARRATIVE_RUN_PREFIX)
/<!--\s*openclaw-memory-promotion:/i.test(snippet) ||
DREAMING_TRANSCRIPT_PROMPT_LINE_RE.test(snippet)
) {
return true;
}
@@ -291,13 +290,7 @@ function isContaminatedDreamingSnippet(raw: string): boolean {
);
const hasStatus = /\bstatus:\s*staged\b/i.test(snippet);
const hasRecalls = /\brecalls:\s*\d+\b/i.test(snippet);
if (hasEvidence && hasNarrativeLead) {
return true;
}
const structuredMarkers = [hasConfidence, hasEvidence, hasStatus, hasRecalls].filter(
Boolean,
).length;
return hasNarrativeLead && structuredMarkers >= 2;
return hasNarrativeLead && hasConfidence && hasEvidence && hasStatus && hasRecalls;
}
function normalizeMemoryPath(rawPath: string): string {