From a9dbaa1124656f5ec32d6f3efd4c2e2ed9fdb992 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 5 Apr 2026 23:34:31 +0100 Subject: [PATCH] fix(memory): standardize DREAMS trail path --- CHANGELOG.md | 2 +- docs/cli/memory.md | 6 +-- docs/concepts/dreaming.md | 16 ++++---- docs/reference/memory-config.md | 6 +-- extensions/memory-core/openclaw.plugin.json | 2 +- .../memory-core/src/dreaming-markdown.test.ts | 26 ------------ .../memory-core/src/dreaming-markdown.ts | 41 +------------------ src/memory-host-sdk/host/internal.test.ts | 4 -- src/memory-host-sdk/host/internal.ts | 7 +--- 9 files changed, 18 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9edab45f6a9..924f03893d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ Docs: https://docs.openclaw.ai - Providers/CLI: remove bundled CLI text-provider backends and the `agents.defaults.cliBackends` surface, while keeping ACP harness sessions and Gemini media understanding on the native bundled providers. - Matrix/exec approvals: clarify unavailable-approval replies so Matrix no longer claims chat approvals are unsupported when native exec approvals are merely unconfigured. (#61424) Thanks @gumadeiras. - Docs/IRC: replace public IRC hostname examples with `irc.example.com` and recommend private servers for bot coordination while listing common public networks for intentional use. -- Memory/dreaming: write dreaming trail content to top-level `dreams.md` instead of daily memory notes, update `/dreaming` help text to point there, and keep `dreams.md` available for explicit reads without pulling it into default recall. Thanks @davemorin. +- Memory/dreaming: write dreaming trail content to top-level `DREAMS.md` instead of daily memory notes, update `/dreaming` help text to point there, and keep `DREAMS.md` available for explicit reads without pulling it into default recall. Thanks @davemorin. ### Fixes diff --git a/docs/cli/memory.md b/docs/cli/memory.md index c3a4de33266..3765f670c17 100644 --- a/docs/cli/memory.md +++ b/docs/cli/memory.md @@ -93,14 +93,14 @@ Full options: ## Dreaming (experimental) Dreaming is the background memory consolidation system with three cooperative -phases: **light** (organize into `dreams.md` in inline mode), **deep** +phases: **light** (organize into `DREAMS.md` in inline mode), **deep** (promote into `MEMORY.md`), and **REM** (reflect and find patterns in -`dreams.md` in inline mode). +`DREAMS.md` in inline mode). - Enable with `plugins.entries.memory-core.config.dreaming.enabled: true`. - Toggle from chat with `/dreaming on|off` or `/dreaming enable|disable light|deep|rem`. - Each phase runs on its own cron schedule, managed automatically by `memory-core`. -- Only the deep phase writes durable memory to `MEMORY.md`. With default inline storage, Light and REM write to `dreams.md`. +- Only the deep phase writes durable memory to `MEMORY.md`. With default inline storage, Light and REM write to `DREAMS.md`. - Ranking uses weighted signals: recall frequency, retrieval relevance, query diversity, temporal recency, cross-day consolidation, and derived concept richness. - Promotion re-reads the live daily note before writing to `MEMORY.md`, so edited or deleted short-term snippets do not get promoted from stale recall-store snapshots. - Scheduled and manual `memory promote` runs share the same deep phase defaults unless you pass CLI threshold overrides. diff --git a/docs/concepts/dreaming.md b/docs/concepts/dreaming.md index 6bb51cf3689..9730f430d69 100644 --- a/docs/concepts/dreaming.md +++ b/docs/concepts/dreaming.md @@ -22,7 +22,7 @@ a distinct job, writes to a distinct target, and runs on its own schedule. Light dreaming sorts the recent mess. It scans recent memory traces, dedupes them by Jaccard similarity, clusters related entries, and stages candidate -memories into the shared dreaming trail file (`dreams.md`) when inline storage +memories into the shared dreaming trail file (`DREAMS.md`) when inline storage is enabled. Light does **not** write anything into `MEMORY.md`. It only organizes and @@ -42,19 +42,19 @@ threshold). Think: "what is true enough to keep?" REM dreaming looks for patterns and reflection. It examines recent material, identifies recurring themes through concept tag clustering, and writes -higher-order notes and reflections into `dreams.md` when inline storage is +higher-order notes and reflections into `DREAMS.md` when inline storage is enabled. -REM writes to `dreams.md` in inline mode, **not** `MEMORY.md`. +REM writes to `DREAMS.md` in inline mode, **not** `MEMORY.md`. Its output is interpretive, not canonical. Think: "what pattern am I noticing?" ## Hard boundaries | Phase | Job | Writes to | Does NOT write to | | ----- | --------- | ------------------------- | ----------------- | -| Light | Organize | `dreams.md` (inline mode) | MEMORY.md | +| Light | Organize | `DREAMS.md` (inline mode) | MEMORY.md | | Deep | Preserve | MEMORY.md | -- | -| REM | Interpret | `dreams.md` (inline mode) | MEMORY.md | +| REM | Interpret | `DREAMS.md` (inline mode) | MEMORY.md | ## Quick start @@ -112,7 +112,7 @@ for the full key list. | `enabled` | `boolean` | `true` | Master switch for all phases | | `timezone` | `string` | unset | Timezone for schedule evaluation and dreaming date bucketing | | `verboseLogging` | `boolean` | `false` | Emit detailed per-run dreaming logs | -| `storage.mode` | `string` | `"inline"` | Inline `dreams.md`, separate reports, or both | +| `storage.mode` | `string` | `"inline"` | Inline `DREAMS.md`, separate reports, or both | ### Light phase config @@ -233,7 +233,7 @@ See [memory CLI](/cli/memory) for the full flag reference. 2. Filter entries within `lookbackDays` of the current time. 3. Deduplicate by Jaccard similarity (configurable threshold). 4. Sort by average recall score, take up to `limit` entries. -5. Write staged candidates into `dreams.md` under a `## Light Sleep` block when +5. Write staged candidates into `DREAMS.md` under a `## Light Sleep` block when inline storage is enabled. ### Deep phase pipeline @@ -252,7 +252,7 @@ See [memory CLI](/cli/memory) for the full flag reference. 1. Read recent memory traces within `lookbackDays`. 2. Cluster concept tags by co-occurrence. 3. Filter patterns by `minPatternStrength`. -4. Write themes and reflections into `dreams.md` under a `## REM Sleep` block +4. Write themes and reflections into `DREAMS.md` under a `## REM Sleep` block when inline storage is enabled. ## Scheduling diff --git a/docs/reference/memory-config.md b/docs/reference/memory-config.md index f6dcd4de467..70e60bf1ff1 100644 --- a/docs/reference/memory-config.md +++ b/docs/reference/memory-config.md @@ -387,12 +387,12 @@ conceptual details and chat commands, see [Dreaming](/concepts/dreaming). | `enabled` | `boolean` | `true` | Master switch for all phases | | `timezone` | `string` | unset | Timezone for schedule evaluation and dreaming date bucketing | | `verboseLogging` | `boolean` | `false` | Emit detailed per-run dreaming logs | -| `storage.mode` | `string` | `"inline"` | Inline `dreams.md`, separate reports, or both | +| `storage.mode` | `string` | `"inline"` | Inline `DREAMS.md`, separate reports, or both | | `storage.separateReports` | `boolean` | `false` | Write separate report files per phase | ### Light phase (`phases.light`) -Scans recent traces, dedupes, and stages candidates into `dreams.md` when +Scans recent traces, dedupes, and stages candidates into `DREAMS.md` when inline storage is enabled. Does **not** write to `MEMORY.md`. @@ -435,7 +435,7 @@ writes durable facts. Also owns recovery when memory is thin. ### REM phase (`phases.rem`) -Writes themes, reflections, and pattern notes into `dreams.md` when inline +Writes themes, reflections, and pattern notes into `DREAMS.md` when inline storage is enabled. Does **not** write to `MEMORY.md`. diff --git a/extensions/memory-core/openclaw.plugin.json b/extensions/memory-core/openclaw.plugin.json index 29880bebc0b..21c75a263cb 100644 --- a/extensions/memory-core/openclaw.plugin.json +++ b/extensions/memory-core/openclaw.plugin.json @@ -15,7 +15,7 @@ "dreaming.storage.mode": { "label": "Dreaming Storage Mode", "placeholder": "inline", - "help": "Write inline to dreams.md, to separate reports, or both." + "help": "Write inline to DREAMS.md, to separate reports, or both." }, "dreaming.phases.light.cron": { "label": "Light Sleep Cron", diff --git a/extensions/memory-core/src/dreaming-markdown.test.ts b/extensions/memory-core/src/dreaming-markdown.test.ts index 86f2de7bf68..e5fdbfd1a96 100644 --- a/extensions/memory-core/src/dreaming-markdown.test.ts +++ b/extensions/memory-core/src/dreaming-markdown.test.ts @@ -138,32 +138,6 @@ describe("dreaming markdown storage", () => { expect(content.match(/## 2026-04-05 - REM Sleep/g)).toHaveLength(1); }); - it("migrates legacy dreams.md to canonical DREAMS.md on the next inline write", async () => { - const workspaceDir = await createTempWorkspace(); - const legacyPath = path.join(workspaceDir, "dreams.md"); - await fs.writeFile(legacyPath, "## 2026-04-04 - Light Sleep\nlegacy\n", "utf-8"); - - await writeDailyDreamingPhaseBlock({ - workspaceDir, - phase: "light", - bodyLines: ["- Candidate: migrated forward"], - nowMs: Date.parse("2026-04-05T10:00:00Z"), - timezone: "UTC", - storage: { - mode: "inline", - separateReports: false, - }, - }); - - const canonicalPath = path.join(workspaceDir, "DREAMS.md"); - const content = await fs.readFile(canonicalPath, "utf-8"); - expect(content).toContain("## 2026-04-04 - Light Sleep"); - expect(content).toContain("## 2026-04-05 - Light Sleep"); - const workspaceEntries = await fs.readdir(workspaceDir); - expect(workspaceEntries).toContain("DREAMS.md"); - expect(workspaceEntries).not.toContain("dreams.md"); - }); - it("still writes deep reports to the per-phase report directory", async () => { const workspaceDir = await createTempWorkspace(); diff --git a/extensions/memory-core/src/dreaming-markdown.ts b/extensions/memory-core/src/dreaming-markdown.ts index 1dbbbb4235b..9262f3912d7 100644 --- a/extensions/memory-core/src/dreaming-markdown.ts +++ b/extensions/memory-core/src/dreaming-markdown.ts @@ -12,7 +12,6 @@ const DAILY_PHASE_LABELS: Record, strin }; const DREAMS_FILENAME = "DREAMS.md"; -const LEGACY_DREAMS_FILENAME = "dreams.md"; function resolvePhaseMarkers( phase: Exclude, @@ -62,44 +61,6 @@ function resolveDreamsPath(workspaceDir: string): string { return path.join(workspaceDir, DREAMS_FILENAME); } -function resolveLegacyDreamsPath(workspaceDir: string): string { - return path.join(workspaceDir, LEGACY_DREAMS_FILENAME); -} - -async function pathExists(targetPath: string): Promise { - try { - await fs.access(targetPath); - return true; - } catch { - return false; - } -} - -async function listWorkspaceEntries(workspaceDir: string): Promise> { - try { - return new Set(await fs.readdir(workspaceDir)); - } catch { - return new Set(); - } -} - -async function prepareDreamsPathForWrite(workspaceDir: string): Promise { - const dreamsPath = resolveDreamsPath(workspaceDir); - const workspaceEntries = await listWorkspaceEntries(workspaceDir); - if (workspaceEntries.has(DREAMS_FILENAME)) { - return dreamsPath; - } - const legacyDreamsPath = resolveLegacyDreamsPath(workspaceDir); - if (dreamsPath !== legacyDreamsPath && workspaceEntries.has(LEGACY_DREAMS_FILENAME)) { - await fs.rename(legacyDreamsPath, dreamsPath); - return dreamsPath; - } - if (await pathExists(dreamsPath)) { - return dreamsPath; - } - return dreamsPath; -} - function resolveDreamsBlockHeading( phase: Exclude, isoDay: string, @@ -140,7 +101,7 @@ export async function writeDailyDreamingPhaseBlock(params: { let reportPath: string | undefined; if (shouldWriteInline(params.storage)) { - inlinePath = await prepareDreamsPathForWrite(params.workspaceDir); + inlinePath = resolveDreamsPath(params.workspaceDir); const original = await fs.readFile(inlinePath, "utf-8").catch((err: unknown) => { if ((err as NodeJS.ErrnoException)?.code === "ENOENT") { return ""; diff --git a/src/memory-host-sdk/host/internal.test.ts b/src/memory-host-sdk/host/internal.test.ts index 0d43487269a..cfcdb4e38c3 100644 --- a/src/memory-host-sdk/host/internal.test.ts +++ b/src/memory-host-sdk/host/internal.test.ts @@ -162,10 +162,6 @@ describe("isMemoryPath", () => { it("allows explicit access to top-level DREAMS.md", () => { expect(isMemoryPath("DREAMS.md")).toBe(true); }); - - it("keeps explicit access compatible with legacy dreams.md", () => { - expect(isMemoryPath("dreams.md")).toBe(true); - }); }); describe("buildFileEntry", () => { diff --git a/src/memory-host-sdk/host/internal.ts b/src/memory-host-sdk/host/internal.ts index 80262ab5bcf..f402578ac2b 100644 --- a/src/memory-host-sdk/host/internal.ts +++ b/src/memory-host-sdk/host/internal.ts @@ -77,12 +77,7 @@ export function isMemoryPath(relPath: string): boolean { if (!normalized) { return false; } - if ( - normalized === "MEMORY.md" || - normalized === "memory.md" || - normalized === "DREAMS.md" || - normalized === "dreams.md" - ) { + if (normalized === "MEMORY.md" || normalized === "memory.md" || normalized === "DREAMS.md") { return true; } return normalized.startsWith("memory/");