import { createHash } from "node:crypto" ;
import fs from "node:fs/promises" ;
import path from "node:path" ;
import { RequestScopedSubagentRuntimeError } from "openclaw/plugin-sdk/error-runtime" ;
import type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core" ;
import { resolveSessionTranscriptsDirForAgent } from "openclaw/plugin-sdk/memory-core" ;
import {
resolveMemoryCorePluginConfig,
resolveMemoryLightDreamingConfig,
resolveMemoryRemDreamingConfig,
} from "openclaw/plugin-sdk/memory-core-host-status" ;
import { describe, expect, it, vi } from "vitest" ;
import { __testing, runDreamingSweepPhases } from "./dreaming-phases.js" ;
import {
rankShortTermPromotionCandidates,
recordShortTermRecalls,
resolveShortTermPhaseSignalStorePath,
} from "./short-term-promotion.js" ;
import { createMemoryCoreTestHarness } from "./test-helpers.js" ;
const { createTempWorkspace } = createMemoryCoreTestHarness();
const DREAMING_TEST_BASE_TIME = new Date("2026-04-05T10:00:00.000Z" );
const DREAMING_TEST_DAY = "2026-04-05" ;
const LIGHT_DREAMING_TEST_CONFIG: OpenClawConfig = {
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
timezone: "UTC" ,
// The existing tests in this file were written when "inline" was the
// default storage mode and assert against `memory/<day>.md` directly.
// Pin the storage mode explicitly so they keep covering inline mode
// after the default flipped to "separate" in #66328.
storage: { mode: "inline" , separateReports: false },
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 2 ,
},
},
},
},
},
},
},
};
function createHarness(
config: OpenClawConfig,
workspaceDir?: string,
subagent?: Parameters<typeof __testing.runPhaseIfTriggered>[0 ]["subagent" ],
) {
const logger = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const resolvedConfig = workspaceDir
? {
...config,
agents: {
...config.agents,
defaults: {
...config.agents?.defaults,
workspace: workspaceDir,
userTimezone: config.agents?.defaults?.userTimezone ?? "UTC" ,
},
},
}
: {
...config,
agents: {
...config.agents,
defaults: {
...config.agents?.defaults,
userTimezone: config.agents?.defaults?.userTimezone ?? "UTC" ,
},
},
};
const pluginConfig = resolveMemoryCorePluginConfig(resolvedConfig) ?? {};
const beforeAgentReply = async (
event: { cleanedBody: string },
ctx: { trigger?: string; workspaceDir?: string },
) => {
const light = resolveMemoryLightDreamingConfig({ pluginConfig, cfg: resolvedConfig });
const lightResult = await __testing.runPhaseIfTriggered({
cleanedBody: event.cleanedBody,
trigger: ctx.trigger,
workspaceDir: ctx.workspaceDir,
cfg: resolvedConfig,
logger,
subagent,
phase: "light" ,
eventText: __testing.constants.LIGHT_SLEEP_EVENT_TEXT,
config: light,
});
if (lightResult) {
return lightResult;
}
const rem = resolveMemoryRemDreamingConfig({ pluginConfig, cfg: resolvedConfig });
return await __testing.runPhaseIfTriggered({
cleanedBody: event.cleanedBody,
trigger: ctx.trigger,
workspaceDir: ctx.workspaceDir,
cfg: resolvedConfig,
logger,
subagent,
phase: "rem" ,
eventText: __testing.constants.REM_SLEEP_EVENT_TEXT,
config: rem,
});
};
return { beforeAgentReply, logger };
}
function createMockNarrativeSubagent(response = "The archive hummed softly." ) {
const run = vi.fn(async (_params: { sessionKey: string; message: string }) => ({
runId: "dream-run-1" ,
}));
const waitForRun = vi.fn(async () => ({ status: "ok" }));
const getSessionMessages = vi.fn(async () => ({
messages: [{ role: "assistant" , content: response }],
}));
const deleteSession = vi.fn(async () => {});
return {
run,
waitForRun,
getSessionMessages,
deleteSession,
};
}
function setDreamingTestTime(offsetMinutes = 0 ) {
vi.setSystemTime(new Date(DREAMING_TEST_BASE_TIME.getTime() + offsetMinutes * 60 _000 ));
}
async function withDreamingTestClock(run: () => Promise<void >) {
vi.useFakeTimers();
try {
await run();
} finally {
vi.useRealTimers();
}
}
async function writeDailyNote(workspaceDir: string, lines: string[]): Promise<void > {
await fs.writeFile(
path.join(workspaceDir, "memory" , `${DREAMING_TEST_DAY}.md`),
lines.join("\n" ),
"utf-8" ,
);
}
async function createDreamingWorkspace(): Promise<string> {
const workspaceDir = await createTempWorkspace("openclaw-dreaming-phases-" );
await fs.mkdir(path.join(workspaceDir, "memory" ), { recursive: true });
return workspaceDir;
}
function createLightDreamingHarness(workspaceDir: string) {
return createHarness(LIGHT_DREAMING_TEST_CONFIG, workspaceDir);
}
async function triggerLightDreaming(
beforeAgentReply: NonNullable<ReturnType<typeof createHarness>["beforeAgentReply" ]>,
workspaceDir: string,
offsetMinutes: number,
): Promise<void > {
setDreamingTestTime(offsetMinutes);
await beforeAgentReply(
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
{ trigger: "heartbeat" , workspaceDir },
);
}
async function readCandidateSnippets(workspaceDir: string, nowIso: string): Promise<string[]> {
const candidates = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse(nowIso),
});
return candidates.map((candidate) => candidate.snippet);
}
describe("memory-core dreaming phases" , () => {
it("uses the hashed narrative session key for sweep-level fallback cleanup" , async () => {
const workspaceDir = await createDreamingWorkspace();
await writeDailyNote(workspaceDir, [
`# ${DREAMING_TEST_DAY}`,
"" ,
"- Move backups to S3 Glacier." ,
"- Keep retention at 365 days." ,
]);
const testConfig: OpenClawConfig = {
...LIGHT_DREAMING_TEST_CONFIG,
agents: {
defaults: {
workspace: workspaceDir,
userTimezone: "UTC" ,
},
},
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
timezone: "UTC" ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 2 ,
},
rem: {
enabled: false ,
limit: 0 ,
lookbackDays: 2 ,
},
},
},
},
},
},
},
};
const subagent = createMockNarrativeSubagent("The archive hummed softly." );
const logger = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const nowMs = Date.parse("2026-04-05T10:05:00.000Z" );
const workspaceHash = createHash("sha1" ).update(workspaceDir).digest("hex" ).slice(0 , 12 );
const expectedSessionKey = `dreaming-narrative-light-${workspaceHash}-${nowMs}`;
await runDreamingSweepPhases({
workspaceDir,
cfg: testConfig,
pluginConfig: resolveMemoryCorePluginConfig(testConfig),
logger,
subagent,
nowMs,
});
expect(subagent.deleteSession).toHaveBeenCalledTimes(2 );
expect(subagent.deleteSession).toHaveBeenNthCalledWith(1 , { sessionKey: expectedSessionKey });
expect(subagent.deleteSession).toHaveBeenNthCalledWith(2 , { sessionKey: expectedSessionKey });
});
it("swallows synchronous request-scoped cleanup failures after narrative fallback" , async () => {
const workspaceDir = await createDreamingWorkspace();
await writeDailyNote(workspaceDir, [
`# ${DREAMING_TEST_DAY}`,
"" ,
"- Move backups to S3 Glacier." ,
"- Keep retention at 365 days." ,
]);
const testConfig: OpenClawConfig = {
...LIGHT_DREAMING_TEST_CONFIG,
agents: {
defaults: {
workspace: workspaceDir,
userTimezone: "UTC" ,
},
},
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
timezone: "UTC" ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 2 ,
},
rem: {
enabled: false ,
limit: 0 ,
lookbackDays: 2 ,
},
},
},
},
},
},
},
};
const subagent = createMockNarrativeSubagent();
subagent.run.mockRejectedValue(new RequestScopedSubagentRuntimeError());
subagent.deleteSession.mockImplementation(() => {
throw new RequestScopedSubagentRuntimeError();
});
const logger = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
await expect(
runDreamingSweepPhases({
workspaceDir,
cfg: testConfig,
pluginConfig: resolveMemoryCorePluginConfig(testConfig),
logger,
subagent,
nowMs: Date.parse("2026-04-05T10:05:00.000Z" ),
}),
).resolves.toBeUndefined();
const dreams = await fs.readFile(path.join(workspaceDir, "DREAMS.md" ), "utf-8" );
expect(dreams).toContain("Move backups to S3 Glacier." );
expect(logger.error).not.toHaveBeenCalled();
});
it("does not re-ingest managed light dreaming blocks from daily notes" , async () => {
const workspaceDir = await createDreamingWorkspace();
await withDreamingTestClock(async () => {
await writeDailyNote(workspaceDir, [
`# ${DREAMING_TEST_DAY}`,
"" ,
"- Move backups to S3 Glacier." ,
"- Keep retention at 365 days." ,
]);
const { beforeAgentReply } = createLightDreamingHarness(workspaceDir);
const candidateCounts: number[] = [];
const candidateSnippets: string[][] = [];
for (let run = 0 ; run < 3 ; run += 1 ) {
await triggerLightDreaming(beforeAgentReply, workspaceDir, run + 1 );
candidateSnippets.push(
await readCandidateSnippets(workspaceDir, `2026 -04 -05 T10:0 ${run + 1 }:00 .000 Z`),
);
candidateCounts.push(candidateSnippets.at(-1 )?.length ?? 0 );
}
expect(candidateCounts).toEqual([1 , 1 , 1 ]);
expect(candidateSnippets).toEqual([
["Move backups to S3 Glacier.; Keep retention at 365 days." ],
["Move backups to S3 Glacier.; Keep retention at 365 days." ],
["Move backups to S3 Glacier.; Keep retention at 365 days." ],
]);
const dailyContent = await fs.readFile(
path.join(workspaceDir, "memory" , `${DREAMING_TEST_DAY}.md`),
"utf-8" ,
);
expect(dailyContent).toContain("## Light Sleep" );
expect(dailyContent.match(/^- Candidate:/gm)).toHaveLength(1 );
expect(dailyContent).not.toContain("Light Sleep: Candidate:" );
});
});
it("triggers light dreaming when the token is embedded in a reminder body" , async () => {
const workspaceDir = await createDreamingWorkspace();
await withDreamingTestClock(async () => {
await writeDailyNote(workspaceDir, [
`# ${DREAMING_TEST_DAY}`,
"" ,
"- Move backups to S3 Glacier." ,
"- Keep retention at 365 days." ,
]);
const { beforeAgentReply } = createLightDreamingHarness(workspaceDir);
setDreamingTestTime(1 );
await beforeAgentReply(
{
cleanedBody: [
"System: rotate logs" ,
"System: __openclaw_memory_core_light_sleep__" ,
"" ,
"A scheduled reminder has been triggered. The reminder content is:" ,
"" ,
"rotate logs" ,
"__openclaw_memory_core_light_sleep__" ,
"" ,
"Handle this reminder internally. Do not relay it to the user unless explicitly requested." ,
].join("\n" ),
},
{ trigger: "heartbeat" , workspaceDir },
);
const dailyContent = await fs.readFile(
path.join(workspaceDir, "memory" , `${DREAMING_TEST_DAY}.md`),
"utf-8" ,
);
expect(dailyContent).toContain("## Light Sleep" );
expect(dailyContent).toContain("Move backups to S3 Glacier." );
});
});
it("stops stripping a malformed managed block at the next section boundary" , async () => {
const workspaceDir = await createDreamingWorkspace();
await withDreamingTestClock(async () => {
await writeDailyNote(workspaceDir, [
`# ${DREAMING_TEST_DAY}`,
"" ,
"- Move backups to S3 Glacier." ,
"" ,
"## Light Sleep" ,
"<!-- openclaw:dreaming:light:start -->" ,
"- Candidate: Old staged summary." ,
"" ,
"## Ops" ,
"- Rotate access keys." ,
"" ,
"## Light Sleep" ,
"<!-- openclaw:dreaming:light:start -->" ,
"- Candidate: Fresh staged summary." ,
"<!-- openclaw:dreaming:light:end -->" ,
]);
const { beforeAgentReply } = createLightDreamingHarness(workspaceDir);
await triggerLightDreaming(beforeAgentReply, workspaceDir, 1 );
expect(await readCandidateSnippets(workspaceDir, "2026-04-05T10:01:00.000Z" )).toContain(
"Ops: Rotate access keys." ,
);
});
});
it("checkpoints daily ingestion and skips unchanged daily files" , async () => {
const workspaceDir = await createDreamingWorkspace();
const dailyPath = path.join(workspaceDir, "memory" , "2026-04-05.md" );
await fs.writeFile(
dailyPath,
["# 2026-04-05" , "" , "- Move backups to S3 Glacier." ].join("\n" ),
"utf-8" ,
);
const { beforeAgentReply } = createHarness(
{
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
// This test asserts inline-mode side effects on the daily
// file; pin storage explicitly after the default flipped to
// "separate" in #66328.
storage: { mode: "inline" , separateReports: false },
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 2 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
const readSpy = vi.spyOn(fs, "readFile" );
try {
await beforeAgentReply(
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
{ trigger: "heartbeat" , workspaceDir },
);
await beforeAgentReply(
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
{ trigger: "heartbeat" , workspaceDir },
);
} finally {
readSpy.mockRestore();
}
const dailyReadCount = readSpy.mock.calls.filter(
([target]) => typeof target === "string" && target === dailyPath,
).length;
expect(dailyReadCount).toBeLessThanOrEqual(1 );
await expect(
fs.access(path.join(workspaceDir, "memory" , ".dreams" , "daily-ingestion.json" )),
).resolves.toBeUndefined();
});
it("ingests recent daily memory files even before recall traffic exists" , async () => {
const workspaceDir = await createDreamingWorkspace();
await fs.writeFile(
path.join(workspaceDir, "memory" , "2026-04-05.md" ),
["# 2026-04-05" , "" , "- Move backups to S3 Glacier." , "- Keep retention at 365 days." ].join(
"\n" ,
),
"utf-8" ,
);
const before = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse("2026-04-05T10:00:00.000Z" ),
});
expect(before).toHaveLength(0 );
const { beforeAgentReply } = createHarness(
{
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 2 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
const after = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse("2026-04-05T10:05:00.000Z" ),
});
expect(after).toHaveLength(1 );
expect(after[0 ]?.dailyCount).toBeGreaterThan(0 );
expect(after[0 ]?.startLine).toBe(3 );
expect(after[0 ]?.endLine).toBe(4 );
expect(after[0 ]?.snippet).toContain("Move backups to S3 Glacier." );
expect(after[0 ]?.snippet).toContain("Keep retention at 365 days." );
});
it("renders non-zero light-sleep confidence for dreaming-ingested candidates" , async () => {
const workspaceDir = await createDreamingWorkspace();
await withDreamingTestClock(async () => {
await writeDailyNote(workspaceDir, [
`# ${DREAMING_TEST_DAY}`,
"" ,
"- Move backups to S3 Glacier." ,
"- Keep retention at 365 days." ,
]);
const { beforeAgentReply } = createLightDreamingHarness(workspaceDir);
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
const dailyContent = await fs.readFile(
path.join(workspaceDir, "memory" , `${DREAMING_TEST_DAY}.md`),
"utf-8" ,
);
expect(dailyContent).toContain("## Light Sleep" );
expect(dailyContent).toContain("confidence: 0.62" );
expect(dailyContent).not.toContain("confidence: 0.00" );
});
});
it("checkpoints session transcript ingestion and skips unchanged transcripts" , 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-main.jsonl" );
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "session" ,
id: "dreaming-main" ,
timestamp: "2026-04-05T18:00:00.000Z" ,
}),
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-05T18:01:00.000Z" ,
content: [{ type: "text" , text: "Move backups to S3 Glacier." }],
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-05T18:02:00.000Z" ,
content: [{ type: "text" , text: "Set retention to 365 days." }],
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
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,
);
const readSpy = vi.spyOn(fs, "readFile" );
let transcriptReadCount = 0 ;
try {
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
await triggerLightDreaming(beforeAgentReply, workspaceDir, 6 );
});
} finally {
transcriptReadCount = readSpy.mock.calls.filter(
([target]) => typeof target === "string" && target === transcriptPath,
).length;
readSpy.mockRestore();
vi.unstubAllEnvs();
}
expect(transcriptReadCount).toBeLessThanOrEqual(1 );
await expect(
fs.access(path.join(workspaceDir, "memory" , ".dreams" , "session-ingestion.json" )),
).resolves.toBeUndefined();
await expect(
fs.access(path.join(workspaceDir, "memory" , ".dreams" , "session-corpus" , "2026-04-05.txt" )),
).resolves.toBeUndefined();
const ranked = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse("2026-04-05T19:00:00.000Z" ),
});
expect(ranked.map((candidate) => candidate.path)).toContain(
"memory/.dreams/session-corpus/2026-04-05.txt" ,
);
expect(ranked.map((candidate) => candidate.snippet)).toEqual(
expect.arrayContaining([
expect.stringContaining("Move backups to S3 Glacier." ),
expect.stringContaining("Set retention to 365 days." ),
]),
);
});
it("redacts sensitive session content before writing session corpus" , 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-main.jsonl" );
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-05T18:01:00.000Z" ,
content: [{ type: "text" , text: "OPENAI_API_KEY=sk-1234567890abcdef" }],
},
}),
].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 withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
} finally {
vi.unstubAllEnvs();
}
const corpusPath = path.join(
workspaceDir,
"memory" ,
".dreams" ,
"session-corpus" ,
"2026-04-05.txt" ,
);
const corpus = await fs.readFile(corpusPath, "utf-8" );
expect(corpus).not.toContain("OPENAI_API_KEY=sk-1234567890abcdef" );
expect(corpus).toContain("OPENAI_API_KEY=sk-123…cdef" );
});
it("skips dreaming-generated narrative transcripts during session ingestion" , 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." },
],
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-05T18:02:00.000Z" ,
content: [{ type: "text" , text: "I drift through the same archive again." }],
},
}),
].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 },
);
} finally {
vi.unstubAllEnvs();
}
await expect(
fs.access(path.join(workspaceDir, "memory" , ".dreams" , "session-corpus" , "2026-04-05.txt" )),
).rejects.toMatchObject({ code: "ENOENT" });
const sessionIngestion = JSON.parse(
await fs.readFile(
path.join(workspaceDir, "memory" , ".dreams" , "session-ingestion.json" ),
"utf-8" ,
),
) as {
files: Record<
string,
{
lineCount: number;
lastContentLine: number;
contentHash: string;
}
>;
};
expect(Object.keys(sessionIngestion.files)).toHaveLength(1 );
expect(Object.values(sessionIngestion.files)).toEqual([
expect.objectContaining({
lineCount: 0 ,
lastContentLine: 0 ,
contentHash: expect.any(String),
}),
]);
});
it("skips dreaming transcripts when the session store identifies them before bootstrap lands" , 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: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-05T18:01:00.000Z" ,
content: [
{ type: "text" , text: "Write a dream diary entry from these memory fragments." },
],
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-05T18:02:00.000Z" ,
content: [{ type: "text" , text: "I drift through the same archive again." }],
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
await fs.writeFile(
path.join(sessionsDir, "sessions.json" ),
JSON.stringify({
"agent:main:dreaming-narrative-light-1775894400455" : {
sessionId: "dreaming-narrative" ,
sessionFile: transcriptPath,
updatedAt: Date.parse("2026-04-05T18:05:00.000Z" ),
},
}),
"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 },
);
} finally {
vi.unstubAllEnvs();
}
await expect(
fs.access(path.join(workspaceDir, "memory" , ".dreams" , "session-corpus" , "2026-04-05.txt" )),
).rejects.toMatchObject({ code: "ENOENT" });
const sessionIngestion = JSON.parse(
await fs.readFile(
path.join(workspaceDir, "memory" , ".dreams" , "session-ingestion.json" ),
"utf-8" ,
),
) as {
files: Record<
string,
{
lineCount: number;
lastContentLine: number;
contentHash: string;
}
>;
};
expect(Object.keys(sessionIngestion.files)).toHaveLength(1 );
expect(Object.values(sessionIngestion.files)).toEqual([
expect.objectContaining({
lineCount: 0 ,
lastContentLine: 0 ,
contentHash: expect.any(String),
}),
]);
});
it("skips isolated cron run transcripts during session ingestion" , 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, "cron-run.jsonl" );
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-05T18:01:00.000Z" ,
content:
"[cron:job-1 Codex Sessions Sync] Run Codex sessions sync: 1. Convert sessions 2. Update qmd" ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-05T18:02:00.000Z" ,
content: "Running Codex sessions sync..." ,
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
await fs.writeFile(
path.join(sessionsDir, "sessions.json" ),
JSON.stringify({
"agent:main:cron:job-1:run:run-1" : {
sessionId: "cron-run" ,
sessionFile: transcriptPath,
updatedAt: Date.now(),
},
}),
"utf-8" ,
);
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 },
);
} finally {
vi.unstubAllEnvs();
}
await expect(
fs.access(path.join(workspaceDir, "memory" , ".dreams" , "session-corpus" , "2026-04-05.txt" )),
).rejects.toMatchObject({ code: "ENOENT" });
const sessionIngestion = JSON.parse(
await fs.readFile(
path.join(workspaceDir, "memory" , ".dreams" , "session-ingestion.json" ),
"utf-8" ,
),
) as {
files: Record<
string,
{
lineCount: number;
lastContentLine: number;
contentHash: string;
}
>;
};
expect(Object.values(sessionIngestion.files)).toEqual([
expect.objectContaining({
lineCount: 0 ,
lastContentLine: 0 ,
contentHash: expect.any(String),
}),
]);
});
it("drops generated system wrapper text without suppressing paired assistant replies" , 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, "ordinary-session.jsonl" );
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-16T18:01:00.000Z" ,
content:
"System (untrusted): [2026-04-16 11:01:00 PDT] Exec completed (quiet-fo, code 0) :: Converted: 1" ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-16T18:01:30.000Z" ,
content: "Handled internally." ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-16T18:02:00.000Z" ,
content: "What changed in the sync?" ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-16T18:03:00.000Z" ,
content: "One new session was converted." ,
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
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,
);
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-04-16T19:00:00.000Z" ));
try {
await beforeAgentReply(
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
{ trigger: "heartbeat" , workspaceDir },
);
} finally {
vi.useRealTimers();
vi.unstubAllEnvs();
}
const corpus = await fs.readFile(
path.join(workspaceDir, "memory" , ".dreams" , "session-corpus" , "2026-04-16.txt" ),
"utf-8" ,
);
expect(corpus).toContain("User: What changed in the sync?" );
expect(corpus).toContain("Assistant: One new session was converted." );
expect(corpus).not.toContain("System (untrusted):" );
expect(corpus).toContain("Assistant: Handled internally." );
});
it("drops archive, cron, and heartbeat chatter from fresh session corpus output" , 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 });
await fs.writeFile(
path.join(sessionsDir, "archived.jsonl.deleted.2026-04-16T18-06-16.529Z" ),
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-16T18:01:00.000Z" ,
content: "[cron:job-1 Example] Run the nightly sync" ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-16T18:02:00.000Z" ,
content: "Running the nightly sync now." ,
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
await fs.writeFile(
path.join(sessionsDir, "ordinary.checkpoint.abc123.jsonl" ),
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-16T18:03:00.000Z" ,
content: "Checkpoint chatter should stay out." ,
},
}) + "\n" ,
"utf-8" ,
);
await fs.writeFile(
path.join(sessionsDir, "ordinary.jsonl" ),
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-16T18:04:00.000Z" ,
content:
"Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK." ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-16T18:05:00.000Z" ,
content: "HEARTBEAT_OK" ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-16T18:06:00.000Z" ,
content: "[cron:job-2 Example] Run the qmd sync" ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-16T18:07:00.000Z" ,
content: "Running the qmd sync now." ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-16T18:08:00.000Z" ,
content: "Document the Ollama provider setup." ,
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-16T18:09:00.000Z" ,
content: "I documented the Ollama provider setup in the workspace notes." ,
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
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,
);
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-04-16T19:00:00.000Z" ));
try {
await beforeAgentReply(
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
{ trigger: "heartbeat" , workspaceDir },
);
} finally {
vi.useRealTimers();
vi.unstubAllEnvs();
}
const corpus = await fs.readFile(
path.join(workspaceDir, "memory" , ".dreams" , "session-corpus" , "2026-04-16.txt" ),
"utf-8" ,
);
expect(corpus).toContain("User: Document the Ollama provider setup." );
expect(corpus).toContain(
"Assistant: I documented the Ollama provider setup in the workspace notes." ,
);
expect(corpus).not.toContain("Run the nightly sync" );
expect(corpus).not.toContain("Checkpoint chatter should stay out." );
expect(corpus).not.toContain("Read HEARTBEAT.md" );
expect(corpus).not.toContain("HEARTBEAT_OK" );
expect(corpus).not.toContain("Run the qmd sync" );
});
it("ignores chat scaffolding tags when building rem reflections" , () => {
const preview = __testing.previewRemDreaming({
entries: [
{
key: "memory:1" ,
path: "memory/.dreams/session-corpus/2026-04-16.txt" ,
startLine: 1 ,
endLine: 1 ,
source: "memory" ,
snippet: "Assistant: I documented the Ollama provider setup." ,
recallCount: 1 ,
dailyCount: 0 ,
groundedCount: 0 ,
totalScore: 0 .6 ,
maxScore: 0 .6 ,
firstRecalledAt: "2026-04-16T18:00:00.000Z" ,
lastRecalledAt: "2026-04-16T18:00:00.000Z" ,
queryHashes: ["q1" ],
recallDays: ["2026-04-16" ],
conceptTags: ["assistant" , "the" , "ollama" , "provider" ],
},
],
limit: 5 ,
minPatternStrength: 0 ,
});
expect(preview.reflections.join("\n" )).toContain("`ollama`" );
expect(preview.reflections.join("\n" )).toContain("`provider`" );
expect(preview.reflections.join("\n" )).not.toContain("`assistant`" );
expect(preview.reflections.join("\n" )).not.toContain("`the`" );
});
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]) => typeof 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" );
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-main.jsonl" );
const oldMessage = "Move backups to S3 Glacier." ;
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-05T18:01:00.000Z" ,
content: [{ type: "text" , text: oldMessage }],
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
const dayOne = new Date("2026-04-05T18:05:00.000Z" );
await fs.utimes(transcriptPath, dayOne, dayOne);
const { beforeAgentReply } = createHarness(
{
agents: {
defaults: {
workspace: workspaceDir,
},
},
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 7 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
try {
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
const resetPath = path.join(
sessionsDir,
"dreaming-main.jsonl.reset.2026-04-06T01-00-00.000Z" ,
);
await fs.writeFile(resetPath, await fs.readFile(transcriptPath, "utf-8" ), "utf-8" );
const newMessage = "Keep retention at 365 days." ;
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-05T18:01:00.000Z" ,
content: [{ type: "text" , text: oldMessage }],
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-06T01:02:00.000Z" ,
content: [{ type: "text" , text: newMessage }],
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
const dayTwo = new Date("2026-04-06T01:05:00.000Z" );
await fs.utimes(transcriptPath, dayTwo, dayTwo);
await fs.utimes(resetPath, dayTwo, dayTwo);
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 910 );
});
} finally {
vi.unstubAllEnvs();
}
const ranked = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse("2026-04-06T02:00:00.000Z" ),
});
const oldCandidate = ranked.find((candidate) => candidate.snippet.includes(oldMessage));
const newCandidate = ranked.find((candidate) => candidate.snippet.includes("retention at 365" ));
expect(oldCandidate?.dailyCount).toBe(1 );
expect(newCandidate?.dailyCount).toBe(1 );
const sessionCorpusDir = path.join(workspaceDir, "memory" , ".dreams" , "session-corpus" );
const corpusFiles = (await fs.readdir(sessionCorpusDir)).filter((name) =>
name.endsWith(".txt" ),
);
let combinedCorpus = "" ;
for (const fileName of corpusFiles) {
combinedCorpus += `${await fs.readFile(path.join(sessionCorpusDir, fileName), "utf-8" )}\n`;
}
const oldOccurrences = combinedCorpus.match(/Move backups to S3 Glacier\./g)?.length ?? 0 ;
const newOccurrences = combinedCorpus.match(/Keep retention at 365 days\./g)?.length ?? 0 ;
expect(oldOccurrences).toBe(1 );
expect(newOccurrences).toBe(1 );
});
it("buckets session snippets by per-message day rather than file mtime" , 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-main.jsonl" );
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-01T12:00:00.000Z" ,
content: [
{ type: "text" , text: "Old planning note that should stay out of lookback." },
],
},
}),
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-05T18:02:00.000Z" ,
content: [{ type: "text" , text: "Current reminder that should be in today corpus." }],
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
const freshMtime = new Date("2026-04-06T01:05:00.000Z" );
await fs.utimes(transcriptPath, freshMtime, freshMtime);
const { beforeAgentReply } = createHarness(
{
agents: {
defaults: {
workspace: workspaceDir,
},
},
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 2 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
try {
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
} finally {
vi.unstubAllEnvs();
}
const corpusDir = path.join(workspaceDir, "memory" , ".dreams" , "session-corpus" );
const corpusFiles = (await fs.readdir(corpusDir))
.filter((name) => name.endsWith(".txt" ))
.toSorted();
expect(corpusFiles).toEqual(["2026-04-05.txt" ]);
const dayCorpus = await fs.readFile(path.join(corpusDir, "2026-04-05.txt" ), "utf-8" );
expect(dayCorpus).toContain("Current reminder that should be in today corpus." );
expect(dayCorpus).not.toContain("Old planning note that should stay out of lookback." );
});
it("drains >80 unseen transcript messages across multiple unchanged sweeps" , 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-main.jsonl" );
const lines: string[] = [];
for (let index = 0 ; index < 160 ; index += 1 ) {
lines.push(
JSON.stringify({
type: "message" ,
message: {
role: index % 2 === 0 ? "user" : "assistant" ,
timestamp: "2026-04-05T18:00:00.000Z" ,
content: [{ type: "text" , text: `bulk-line-${index}` }],
},
}),
);
}
await fs.writeFile(transcriptPath, `${lines.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,
},
},
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 7 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
try {
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
await triggerLightDreaming(beforeAgentReply, workspaceDir, 6 );
await triggerLightDreaming(beforeAgentReply, workspaceDir, 7 );
});
} finally {
vi.unstubAllEnvs();
}
const corpusPath = path.join(
workspaceDir,
"memory" ,
".dreams" ,
"session-corpus" ,
"2026-04-05.txt" ,
);
const corpus = await fs.readFile(corpusPath, "utf-8" );
const persistedLines = corpus
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.length > 0 );
expect(persistedLines).toHaveLength(160 );
expect(corpus).toContain("bulk-line-0" );
expect(corpus).toContain("bulk-line-159" );
});
it("re-ingests rewritten session transcripts after truncate/reset" , 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-main.jsonl" );
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-05T18:01:00.000Z" ,
content: [{ type: "text" , text: "Move backups to S3 Glacier." }],
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
const dayOne = new Date("2026-04-05T18:05:00.000Z" );
await fs.utimes(transcriptPath, dayOne, dayOne);
const { beforeAgentReply } = createHarness(
{
agents: {
defaults: {
workspace: workspaceDir,
},
},
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 7 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
try {
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "message" ,
message: {
role: "assistant" ,
timestamp: "2026-04-06T01:02:00.000Z" ,
content: [{ type: "text" , text: "Retention policy stays at 365 days." }],
},
}),
].join("\n" ) + "\n" ,
"utf-8" ,
);
const dayTwo = new Date("2026-04-06T01:05:00.000Z" );
await fs.utimes(transcriptPath, dayTwo, dayTwo);
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 910 );
});
} finally {
vi.unstubAllEnvs();
}
const ranked = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse("2026-04-06T02:00:00.000Z" ),
});
expect(ranked.map((candidate) => candidate.snippet)).toEqual(
expect.arrayContaining([
expect.stringContaining("Move backups to S3 Glacier." ),
expect.stringContaining("Retention policy stays at 365 days." ),
]),
);
});
it("ingests sessions when dreaming is enabled even if memorySearch is disabled" , 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-main.jsonl" );
await fs.writeFile(
transcriptPath,
[
JSON.stringify({
type: "message" ,
message: {
role: "user" ,
timestamp: "2026-04-05T18:01:00.000Z" ,
content: [{ type: "text" , text: "Glacier archive migration is now complete." }],
},
}),
].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,
memorySearch: {
enabled: false ,
},
},
},
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 7 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
try {
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
} finally {
vi.unstubAllEnvs();
}
const ranked = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse("2026-04-05T19:00:00.000Z" ),
});
expect(ranked.map((candidate) => candidate.snippet)).toEqual(
expect.arrayContaining([
expect.stringContaining("Glacier archive migration is now complete." ),
]),
);
});
it("keeps section context when chunking durable daily notes" , async () => {
const workspaceDir = await createDreamingWorkspace();
await fs.writeFile(
path.join(workspaceDir, "memory" , "2026-04-05.md" ),
[
"# 2026-04-05" ,
"" ,
"## Emma Rees" ,
"- She asked for more space after the last exchange." ,
"- Better to keep messages short and low-pressure." ,
"- Re-engagement should be time-bounded and optional." ,
].join("\n" ),
"utf-8" ,
);
const { beforeAgentReply } = createHarness(
{
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 2 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
const after = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse("2026-04-05T10:05:00.000Z" ),
});
expect(after).toHaveLength(1 );
expect(after[0 ]?.startLine).toBe(4 );
expect(after[0 ]?.endLine).toBe(6 );
expect(after[0 ]?.snippet).toContain("Emma Rees:" );
expect(after[0 ]?.snippet).toContain("She asked for more space" );
expect(after[0 ]?.snippet).toContain("messages short and low-pressure" );
});
it("drops generic day headings but keeps meaningful section labels" , async () => {
const workspaceDir = await createDreamingWorkspace();
await fs.writeFile(
path.join(workspaceDir, "memory" , "2026-04-05.md" ),
[
"# Friday, April 5, 2026" ,
"" ,
"## Morning" ,
"- Reviewed travel timing and calendar placement." ,
"" ,
"## Emma Rees" ,
"- She prefers direct plans over open-ended maybes." ,
"- Better to offer one concrete time window." ,
].join("\n" ),
"utf-8" ,
);
const { beforeAgentReply } = createHarness(
{
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 2 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
const after = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse("2026-04-05T10:05:00.000Z" ),
});
expect(after).toHaveLength(2 );
expect(after.map((candidate) => candidate.snippet)).toEqual(
expect.arrayContaining([
"Reviewed travel timing and calendar placement." ,
expect.stringContaining("Emma Rees:" ),
]),
);
for (const candidate of after) {
expect(candidate.snippet).not.toContain("Friday, April 5, 2026:" );
expect(candidate.snippet).not.toContain("Morning:" );
}
});
it("splits noisy daily notes into a few coherent chunks instead of one line per item" , async () => {
const workspaceDir = await createDreamingWorkspace();
await fs.writeFile(
path.join(workspaceDir, "memory" , "2026-04-05.md" ),
[
"# 2026-04-05" ,
"" ,
"## Operations" ,
"- Restarted the gateway after auth drift." ,
"- Tokens now line up again." ,
"" ,
"## Bex" ,
"- She prefers direct plans over open-ended maybes." ,
"- Better to offer one concrete time window." ,
"" ,
"11:30" ,
"" ,
"## Travel" ,
"- Flight lands at 08:10." ,
].join("\n" ),
"utf-8" ,
);
const { beforeAgentReply } = createHarness(
{
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 2 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
const after = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: Date.parse("2026-04-05T10:05:00.000Z" ),
});
expect(after).toHaveLength(3 );
expect(after.map((candidate) => candidate.snippet)).toEqual(
expect.arrayContaining([
expect.stringContaining(
"Operations: Restarted the gateway after auth drift.; Tokens now line up again." ,
),
expect.stringContaining(
"Bex: She prefers direct plans over open-ended maybes.; Better to offer one concrete time window." ,
),
expect.stringContaining("Travel: Flight lands at 08:10." ),
]),
);
});
it("records light/rem signals that reinforce deep promotion ranking" , async () => {
const workspaceDir = await createDreamingWorkspace();
const nowMs = Date.parse("2026-04-05T10:00:00.000Z" );
await recordShortTermRecalls({
workspaceDir,
query: "glacier backup" ,
nowMs,
results: [
{
path: "memory/2026-04-03.md" ,
startLine: 1 ,
endLine: 2 ,
score: 0 .92 ,
snippet: "Move backups to S3 Glacier." ,
source: "memory" ,
},
],
});
await recordShortTermRecalls({
workspaceDir,
query: "cold storage retention" ,
nowMs,
results: [
{
path: "memory/2026-04-03.md" ,
startLine: 1 ,
endLine: 2 ,
score: 0 .9 ,
snippet: "Move backups to S3 Glacier." ,
source: "memory" ,
},
],
});
const baseline = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs,
});
expect(baseline).toHaveLength(1 );
const baselineScore = baseline[0 ].score;
const { beforeAgentReply } = createHarness(
{
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 10 ,
lookbackDays: 7 ,
},
rem: {
enabled: true ,
limit: 10 ,
lookbackDays: 7 ,
minPatternStrength: 0 ,
},
},
},
},
},
},
},
},
workspaceDir,
);
await withDreamingTestClock(async () => {
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
await withDreamingTestClock(async () => {
setDreamingTestTime(10 );
await beforeAgentReply(
{ cleanedBody: "__openclaw_memory_core_rem_sleep__" },
{ trigger: "heartbeat" , workspaceDir },
);
});
const reinforced = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs,
});
const reinforcedCandidate = reinforced.find((candidate) => candidate.key === baseline[0 ].key);
expect(reinforcedCandidate).toBeDefined();
expect(reinforcedCandidate!.score).toBeGreaterThan(baselineScore);
const phaseSignalPath = resolveShortTermPhaseSignalStorePath(workspaceDir);
const phaseSignalStore = JSON.parse(await fs.readFile(phaseSignalPath, "utf-8" )) as {
entries: Record<string, { lightHits: number; remHits: number }>;
};
expect(phaseSignalStore.entries[baseline[0 ].key]).toMatchObject({
lightHits: 1 ,
remHits: 1 ,
});
});
it("passes staged light-dreaming snippets into the narrative pipeline" , async () => {
const workspaceDir = await createDreamingWorkspace();
const subagent = createMockNarrativeSubagent("The backup plan glowed like cold storage." );
const { beforeAgentReply } = createHarness(LIGHT_DREAMING_TEST_CONFIG, workspaceDir, subagent);
await withDreamingTestClock(async () => {
await writeDailyNote(workspaceDir, [
`# ${DREAMING_TEST_DAY}`,
"" ,
"- Move backups to S3 Glacier." ,
"- Keep retention at 365 days." ,
]);
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5 );
});
expect(subagent.run).toHaveBeenCalledTimes(1 );
const firstRun = subagent.run.mock.calls[0 ]?.[0 ];
expect(firstRun?.message).toContain("Move backups to S3 Glacier." );
expect(firstRun?.message).toContain("Keep retention at 365 days." );
await expect(fs.readFile(path.join(workspaceDir, "DREAMS.md" ), "utf-8" )).resolves.toContain(
"The backup plan glowed like cold storage." ,
);
});
it("passes rem-dreaming snippets into the narrative pipeline" , async () => {
const workspaceDir = await createDreamingWorkspace();
const subagent = createMockNarrativeSubagent("The traces braided themselves into a map." );
const { beforeAgentReply } = createHarness(
{
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
rem: {
enabled: true ,
limit: 10 ,
lookbackDays: 7 ,
minPatternStrength: 0 ,
},
},
},
},
},
},
},
},
workspaceDir,
subagent,
);
await withDreamingTestClock(async () => {
await writeDailyNote(workspaceDir, [
`# ${DREAMING_TEST_DAY}`,
"" ,
"- Move backups to S3 Glacier." ,
"- Keep retention at 365 days." ,
"- Rotate access keys after the audit." ,
]);
setDreamingTestTime(5 );
await beforeAgentReply(
{ cleanedBody: "__openclaw_memory_core_rem_sleep__" },
{ trigger: "heartbeat" , workspaceDir },
);
});
expect(subagent.run).toHaveBeenCalledTimes(1 );
const firstRun = subagent.run.mock.calls[0 ]?.[0 ];
expect(firstRun?.message).toContain("Move backups to S3 Glacier." );
expect(firstRun?.message).toContain("Keep retention at 365 days." );
await expect(fs.readFile(path.join(workspaceDir, "DREAMS.md" ), "utf-8" )).resolves.toContain(
"The traces braided themselves into a map." ,
);
});
it("increments dailyCount when the same daily file is re-ingested on a later day" , async () => {
// Regression test for #67061: dayBucket used the file date instead of the
// ingestion date, so re-ingesting the same file on a different day was
// treated as a duplicate and dailyCount stayed at 1.
const workspaceDir = await createDreamingWorkspace();
// Write a daily note dated 2026-04-03 (two days before the base test time).
await fs.writeFile(
path.join(workspaceDir, "memory" , "2026-04-03.md" ),
["# 2026-04-03" , "" , "- Move backups to S3 Glacier." , "- Keep retention at 365 days." ].join(
"\n" ,
),
"utf-8" ,
);
const configForTest: OpenClawConfig = {
plugins: {
entries: {
"memory-core" : {
config: {
dreaming: {
enabled: true ,
phases: {
light: {
enabled: true ,
limit: 20 ,
lookbackDays: 7 ,
},
},
},
},
},
},
},
};
// First ingestion on 2026-04-05.
const day1Ms = Date.parse("2026-04-05T10:00:00.000Z" );
const { beforeAgentReply: reply1 } = createHarness(configForTest, workspaceDir);
await withDreamingTestClock(async () => {
vi.setSystemTime(new Date(day1Ms));
await reply1(
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
{ trigger: "heartbeat" , workspaceDir },
);
});
const after1 = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: day1Ms,
});
expect(after1).toHaveLength(1 );
expect(after1[0 ]?.dailyCount).toBe(1 );
// Clear the daily ingestion checkpoint so the file is re-read on the second
// sweep (simulating a new day where the same lookback window still covers
// this file).
const dailyStatePath = path.join(workspaceDir, "memory" , ".dreams" , "daily-ingestion.json" );
try {
await fs.unlink(dailyStatePath);
} catch {
// ignore if not created
}
// Second ingestion on 2026-04-06 (next day).
const day2Ms = Date.parse("2026-04-06T10:00:00.000Z" );
const { beforeAgentReply: reply2 } = createHarness(configForTest, workspaceDir);
await withDreamingTestClock(async () => {
vi.setSystemTime(new Date(day2Ms));
await reply2(
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
{ trigger: "heartbeat" , workspaceDir },
);
});
const after2 = await rankShortTermPromotionCandidates({
workspaceDir,
minScore: 0 ,
minRecallCount: 0 ,
minUniqueQueries: 0 ,
nowMs: day2Ms,
});
expect(after2).toHaveLength(1 );
// With the fix, dailyCount should be 2 because the ingestion date changed.
// Before the fix, it stayed at 1 because dayBucket was the file date.
expect(after2[0 ]?.dailyCount).toBe(2 );
});
});
Messung V0.5 in Prozent C=93 H=99 G=95
¤ Dauer der Verarbeitung: 0.44 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland