mirror of https://github.com/openclaw/openclaw.git
fix(memory): standardize DREAMS trail path
This commit is contained in:
parent
367f52f483
commit
a9dbaa1124
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ const DAILY_PHASE_LABELS: Record<Exclude<MemoryDreamingPhaseName, "deep">, strin
|
|||
};
|
||||
|
||||
const DREAMS_FILENAME = "DREAMS.md";
|
||||
const LEGACY_DREAMS_FILENAME = "dreams.md";
|
||||
|
||||
function resolvePhaseMarkers(
|
||||
phase: Exclude<MemoryDreamingPhaseName, "deep">,
|
||||
|
|
@ -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<boolean> {
|
||||
try {
|
||||
await fs.access(targetPath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function listWorkspaceEntries(workspaceDir: string): Promise<Set<string>> {
|
||||
try {
|
||||
return new Set(await fs.readdir(workspaceDir));
|
||||
} catch {
|
||||
return new Set<string>();
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareDreamsPathForWrite(workspaceDir: string): Promise<string> {
|
||||
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<MemoryDreamingPhaseName, "deep">,
|
||||
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 "";
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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/");
|
||||
|
|
|
|||
Loading…
Reference in New Issue