memory: avoid rereading skipped dreaming transcripts

This commit is contained in:
Gustavo Madeira Santana
2026-04-14 20:02:33 -04:00
parent 46fa55ace1
commit 7f73079270
3 changed files with 91 additions and 7 deletions

View File

@@ -720,6 +720,92 @@ describe("memory-core dreaming phases", () => {
]);
});
it("does not reread unchanged dreaming-generated transcripts after checkpointing skip state", async () => {
const workspaceDir = await createDreamingWorkspace();
vi.stubEnv("OPENCLAW_TEST_FAST", "1");
vi.stubEnv("OPENCLAW_STATE_DIR", path.join(workspaceDir, ".state"));
const sessionsDir = resolveSessionTranscriptsDirForAgent("main");
await fs.mkdir(sessionsDir, { recursive: true });
const transcriptPath = path.join(sessionsDir, "dreaming-narrative.jsonl");
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "custom",
customType: "openclaw:bootstrap-context:full",
data: {
runId: "dreaming-narrative-light-1775894400455",
sessionId: "dream-session-1",
},
}),
JSON.stringify({
type: "message",
message: {
role: "user",
timestamp: "2026-04-05T18:01:00.000Z",
content: [
{ type: "text", text: "Write a dream diary entry from these memory fragments." },
],
},
}),
].join("\n") + "\n",
"utf-8",
);
const mtime = new Date("2026-04-05T18:05:00.000Z");
await fs.utimes(transcriptPath, mtime, mtime);
const { beforeAgentReply } = createHarness(
{
agents: {
defaults: {
workspace: workspaceDir,
},
list: [{ id: "main", workspace: workspaceDir }],
},
plugins: {
entries: {
"memory-core": {
config: {
dreaming: {
enabled: true,
phases: {
light: {
enabled: true,
limit: 20,
lookbackDays: 7,
},
},
},
},
},
},
},
},
workspaceDir,
);
try {
await beforeAgentReply(
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
{ trigger: "heartbeat", workspaceDir },
);
const readFileSpy = vi.spyOn(fs, "readFile");
await beforeAgentReply(
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
{ trigger: "heartbeat", workspaceDir },
);
expect(readFileSpy.mock.calls.some(([target]) => String(target) === transcriptPath)).toBe(
false,
);
readFileSpy.mockRestore();
} finally {
vi.restoreAllMocks();
vi.unstubAllEnvs();
}
});
it("dedupes reset/deleted session archives instead of double-ingesting", async () => {
const workspaceDir = await createDreamingWorkspace();
vi.stubEnv("OPENCLAW_TEST_FAST", "1");

View File

@@ -739,10 +739,7 @@ async function collectSessionIngestionBatches(params: {
mtimeMs: Math.floor(Math.max(0, stat.mtimeMs)),
size: Math.floor(Math.max(0, stat.size)),
};
const cursorAtEnd =
previous !== undefined &&
previous.lineCount > 0 &&
previous.lastContentLine >= previous.lineCount;
const cursorAtEnd = previous !== undefined && previous.lastContentLine >= previous.lineCount;
const unchanged =
Boolean(previous) &&
previous.mtimeMs === fingerprint.mtimeMs &&

View File

@@ -39,6 +39,7 @@ const PHASE_SIGNAL_REM_BOOST_MAX = 0.09;
const PHASE_SIGNAL_HALF_LIFE_DAYS = 14;
const DREAMING_TRANSCRIPT_PROMPT_LINE_RE =
/\[[^\]]*dreaming-narrative[^\]]*]\s*Write a dream diary entry from these memory fragments:?/i;
const DREAMING_DIFF_PREFIX_RE = /@@\s*-\d+(?:,\d+)?\s+[-*+]\s+/iy;
const inProcessShortTermLocks = new Map<string, Promise<void>>();
const ensuredShortTermDirs = new Map<string, Promise<void>>();
@@ -240,10 +241,10 @@ function normalizeSnippet(raw: string): string {
function consumeDreamingLeadPrefix(snippet: string): string {
let index = 0;
while (index < snippet.length) {
const remaining = snippet.slice(index);
const diffMatch = /^@@\s*-\d+(?:,\d+)?\s+[-*+]\s+/i.exec(remaining);
DREAMING_DIFF_PREFIX_RE.lastIndex = index;
const diffMatch = DREAMING_DIFF_PREFIX_RE.exec(snippet);
if (diffMatch) {
index += diffMatch[0].length;
index = DREAMING_DIFF_PREFIX_RE.lastIndex;
continue;
}
const char = snippet[index];