Summary
On days where the REM dreaming phase finds zero candidate truths, the pipeline still writes a placeholder bullet into the REM dream file. That placeholder is subsequently ingested by light sleep, emitted as a - Candidate: line into the daily memory file, and then promoted by applyShortTermPromotions into the long-term workspace/MEMORY.md. Each empty-REM day permanently bloats MEMORY.md with a meaningless entry.
Version
[email protected] (npm global install).
Reproduction
Run the dreaming pipeline on a day where REM produces no candidate truths above the 0.45 confidence threshold (e.g. a low-activity day).
-
workspace/memory/dreaming/rem/YYYY-MM-DD.md ends up containing:
### Possible Lasting Truths
- No strong candidate truths surfaced.
-
The next light sleep pass ingests that placeholder bullet as a memory entry. buildLightDreamingBody emits it as a Candidate line into the daily memory file workspace/memory/YYYY-MM-DD.md:
- Candidate: Possible Lasting Truths: No strong candidate truths surfaced.
- confidence: 0.00
- evidence: memory/YYYY-MM-DD.md:443-443
- recalls: 0
- status: staged
-
applyShortTermPromotions (in dist/short-term-promotion-*.js) treats it as a valid candidate. It passes isContaminatedDreamingSnippet (no confidence/evidence/status/recalls keys in the snippet itself) and accumulates enough score to be promoted. workspace/MEMORY.md gains:
<!-- openclaw-memory-promotion:memory:memory/YYYY-MM-DD.md:443:443 -->
- - Candidate: Possible Lasting Truths: No strong candidate truths surfaced. [score=0.832 recalls=0 avg=0.620 source=memory/YYYY-MM-DD.md:13-13]
In my workspace this produced three identical noise entries (2026-04-19, 2026-04-20, 2026-04-21) under three separate ## Promoted From Short-Term Memory (YYYY-MM-DD) headers.
Expected behavior
Days with zero candidate truths should add nothing to long-term MEMORY.md. Empty results are not findings.
Actual behavior
Every empty-REM day produces three pieces of bloat — a placeholder bullet in the REM dream file, a Candidate line in the daily memory file, and a promoted entry in MEMORY.md.
Root cause
In dist/dreaming-phases-*.js, previewRemDreaming:
const bodyLines = [
"### Reflections",
...reflections,
"",
"### Possible Lasting Truths",
...candidateTruths.length > 0
? candidateTruths.map((entry) => `- ${entry.snippet} [confidence=${entry.confidence.toFixed(2)} evidence=${entry.evidence}]`)
: ["- No strong candidate truths surfaced."]
];
The else branch unconditionally emits a human-readable placeholder line into the REM file even when there is nothing to promote. Downstream the placeholder becomes indistinguishable from a real candidate.
Proposed fix
1. dist/dreaming-phases-*.js — skip the entire ### Possible Lasting Truths section when candidateTruths.length === 0:
const bodyLines = [
"### Reflections",
...reflections,
...(candidateTruths.length > 0 ? [
"",
"### Possible Lasting Truths",
...candidateTruths.map((entry) => `- ${entry.snippet} [confidence=${entry.confidence.toFixed(2)} evidence=${entry.evidence}]`)
] : [])
];
2. dist/short-term-promotion-*.js — defensive filter in applyShortTermPromotions so historical placeholders in already-written daily memory files don't get promoted on future runs:
const selected = options.candidates.filter((candidate) => {
if (isContaminatedDreamingSnippet(candidate.snippet)) return false;
if (/no strong candidate truths surfaced/i.test(candidate.snippet || "")) return false;
if (candidate.promotedAt) return false;
// …
});
Both edits applied locally; node -e "require('…dreaming-phases….js'); require('…short-term-promotion….js')" loads both modules cleanly.
Workaround
Patching the two dist/ files in /usr/lib/node_modules/openclaw/ as above and stripping the existing placeholder entries from workspace/MEMORY.md. The patch is lost on npm install -g openclaw upgrade.
Summary
On days where the REM dreaming phase finds zero candidate truths, the pipeline still writes a placeholder bullet into the REM dream file. That placeholder is subsequently ingested by light sleep, emitted as a
- Candidate:line into the daily memory file, and then promoted byapplyShortTermPromotionsinto the long-termworkspace/MEMORY.md. Each empty-REM day permanently bloatsMEMORY.mdwith a meaningless entry.Version
[email protected](npm global install).Reproduction
Run the dreaming pipeline on a day where REM produces no candidate truths above the 0.45 confidence threshold (e.g. a low-activity day).
workspace/memory/dreaming/rem/YYYY-MM-DD.mdends up containing:The next light sleep pass ingests that placeholder bullet as a memory entry.
buildLightDreamingBodyemits it as a Candidate line into the daily memory fileworkspace/memory/YYYY-MM-DD.md:applyShortTermPromotions(indist/short-term-promotion-*.js) treats it as a valid candidate. It passesisContaminatedDreamingSnippet(noconfidence/evidence/status/recallskeys in the snippet itself) and accumulates enough score to be promoted.workspace/MEMORY.mdgains:In my workspace this produced three identical noise entries (2026-04-19, 2026-04-20, 2026-04-21) under three separate
## Promoted From Short-Term Memory (YYYY-MM-DD)headers.Expected behavior
Days with zero candidate truths should add nothing to long-term
MEMORY.md. Empty results are not findings.Actual behavior
Every empty-REM day produces three pieces of bloat — a placeholder bullet in the REM dream file, a Candidate line in the daily memory file, and a promoted entry in
MEMORY.md.Root cause
In
dist/dreaming-phases-*.js,previewRemDreaming:The
elsebranch unconditionally emits a human-readable placeholder line into the REM file even when there is nothing to promote. Downstream the placeholder becomes indistinguishable from a real candidate.Proposed fix
1.
dist/dreaming-phases-*.js— skip the entire### Possible Lasting Truthssection whencandidateTruths.length === 0:2.
dist/short-term-promotion-*.js— defensive filter inapplyShortTermPromotionsso historical placeholders in already-written daily memory files don't get promoted on future runs:Both edits applied locally;
node -e "require('…dreaming-phases….js'); require('…short-term-promotion….js')"loads both modules cleanly.Workaround
Patching the two
dist/files in/usr/lib/node_modules/openclaw/as above and stripping the existing placeholder entries fromworkspace/MEMORY.md. The patch is lost onnpm install -g openclawupgrade.