fix(memory-core): cap MEMORY.md size during dreaming promotions to pr…#74088
Conversation
Greptile SummaryThis PR adds a bounded compaction step to Confidence Score: 5/5Safe to merge — no P0/P1 issues found; the compaction algorithm, size projections, and writer-side overhead reserve are all correct. The core algorithm is mathematically sound: joinBlocks reconstructs the original string verbatim, blockSeparatorCost correctly captures per-drop separator savings in every case (single or multi-block, partial or full compaction), and WRITE_OVERHEAD_RESERVE covers the worst-case header + trailing-newline overage. Both P2 items from the previous review thread are fixed and backed by dedicated regression tests. No public SDK surface changes; the memoryFileMaxChars opt-in is backward compatible. No files require special attention. Reviews (2): Last reviewed commit: "fix(memory-core): cap MEMORY.md size dur..." | Re-trigger Greptile |
|
Codex review: needs maintainer review before merge. Summary Reproducibility: yes. Source inspection shows current main appends promoted sections to Real behavior proof Next step before merge Security Review detailsBest possible solution: Land the bounded compaction fix after normal CI and maintainer validation, while leaving configurable promotion target or archive design to #71330. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main appends promoted sections to Is this the best way to solve the issue? Yes. Compacting only older auto-promoted sections before the existing write is the narrow maintainable fix; the broader archive or configurable-target design is already represented by #71330. Acceptance criteria:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against c20d45e34699. |
bee897b to
7ef0834
Compare
|
@greptile-apps would you please review again |
8abedd3 to
e811c86
Compare
10c4737 to
bf9013d
Compare
Real behavior — before/after live runSame workload (30 promotion sweeps, ~800-char snippet each), real exported Without the fix the file grows linearly past the 12 KB bootstrap cap by iteration 10 and finishes at 30,613 bytes after 30 sweeps. With the fix it stabilizes at 9,318 bytes once the budget is reached and never crosses the cap. User-authored Re-review progress:
|
|
Hi, I verified this issue is remaining in main branch. Now real behavior testing is in progress. |
|
I've done live testing on fixed branch, here is result |
|
hi @obviyus would you please review this PR? |
b3709e4 to
a54637a
Compare
a54637a to
1ed2c10
Compare
1ed2c10 to
58ba58c
Compare
…event unbounded growth (openclaw#73691) Dreaming's deep-phase promotion path appends to ~/.openclaw/workspace-<agent>/MEMORY.md without a durable size budget. After weeks of use, the file grows past the bootstrap injection cap (~12KB/file), at which point bootstrap silently truncates promoted memory and (per the issue body) session writes can hit lock timeouts that wedge the gateway. Adds a bounded compaction step in applyShortTermPromotions: before each write, drop the OLDEST auto-promoted sections (date-ordered) until existing + new section fits within memoryFileMaxChars (default 10,000 chars, safely below the 12KB bootstrap cap). User-authored content is preserved unconditionally; only dreaming-owned sections are eligible for compaction. Verified: - pnpm install --frozen-lockfile - pnpm test extensions/memory-core/src/memory-budget.test.ts extensions/memory-core/src/short-term-promotion.test.ts - pnpm exec oxfmt --check --threads=1 extensions/memory-core/src/memory-budget.ts extensions/memory-core/src/memory-budget.test.ts extensions/memory-core/src/short-term-promotion.ts extensions/memory-core/src/short-term-promotion.test.ts CHANGELOG.md - pnpm check:changed - pnpm tsgo:core - pnpm tsgo:extensions - pnpm tsgo:test:src - git diff --check - live driver: real applyShortTermPromotions across 5 sweeps with oversized seeded MEMORY.md — file stayed bounded, oldest sections compacted, user content preserved Closes openclaw#73691
58ba58c to
d48623d
Compare
|
Merged, thanks @YB0y. Verification before landing:
Landed on |
fix(memory-core): cap MEMORY.md size during dreaming promotions to prevent unbounded growth (#73691)
Dreaming's deep-phase promotion path appends to
~/.openclaw/workspace-<agent>/MEMORY.mdwithout a durable size budget. After weeks of use, the file grows past the bootstrap injection cap (~12KB/file), at which point bootstrap silently truncates promoted memory and, in the issue body's report, session writes can hit lock timeouts that wedge the gateway.Adds a bounded compaction step in
applyShortTermPromotions: before each write, drop the OLDEST auto-promoted sections (date-ordered) untilexisting + new sectionfits withinmemoryFileMaxChars(default 10,000 chars, safely below the 12KB bootstrap cap). User-authored content — anything that is not a## Promoted From Short-Term Memory (DATE)section — is preserved unconditionally; only dreaming-owned sections are eligible for compaction.Verified:
Closes #73691
Summary
applyShortTermPromotionswrites${existingMemory}${section}with no size budget, compaction, or archive (extensions/memory-core/src/short-term-promotion.ts:1648-1652 on prior main). Bootstrap injection is already capped at 12KB/file, but the disk-side file keeps growing across daily sweeps; once oversized, bootstrap silently truncates promoted material and (per the issue body) session writes can hit lock timeouts that wedge the gateway.dreaming.enabled: true, requiring manual MEMORY.md truncation + hook disablement to recover. The bot review on [Bug]: MEMORY.md grows unbounded → bootstrap overflow → Gateway freeze #73691 explicitly cautioned against naive truncation: "A naive fix that truncates MEMORY.md could destroy useful durable memory. The fix should preserve older material through compaction, archive/search, or a structured replacement."extensions/memory-core/src/memory-budget.tswithcompactMemoryForBudget(...). Wired intoapplyShortTermPromotionsso the budget check runs before each write. Compaction drops OLDEST auto-promotion sections by date until under budget; user-authored content is never touched. New optionalmemoryFileMaxCharsparameter onApplyShortTermPromotionsOptions(defaultDEFAULT_MEMORY_FILE_MAX_CHARS = 10_000, pass0to disable). Two new fields on the result:compactedSections: number,compactedDates: string[].MemoryHostEventunion is untouched — no new event variant). No format change to MEMORY.md sections. No archive layer (out of scope; this PR is the smallest fix that addresses the bot's anti-naive-truncation guidance). No doctor visibility (deferrable follow-up). The legacy${existingMemory}${section}write path stays — only theexistingMemoryupstream of it is now compacted when over budget.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
applyShortTermPromotionsinextensions/memory-core/src/short-term-promotion.tsreads existing MEMORY.md and writes back${existingMemory}${newSection}for every promotion run. There is no pre-write size check, compaction, or archive decision, so MEMORY.md is monotonically increasing.extensions/memory-core/src/short-term-promotion.test.tscovers append behavior and duplicate-marker dedupe but not bounded growth.src/agents/pi-embedded-helpers/bootstrap.ts:87already caps prompt-side reads at 12,000 chars/file and 60,000 total — that mitigates the immediate prompt-overflow path but not the disk-side growth, which is what the issue reports about session lock timeouts.Regression Test Plan (if applicable)
extensions/memory-core/src/memory-budget.test.ts(new) — pure helper unit tests for the compaction algorithm.extensions/memory-core/src/short-term-promotion.test.ts— newMEMORY.md budget compaction (#73691)describe block with two integration smokes proving the helper is wired throughapplyShortTermPromotions.recordShortTermRecalls+ ranking + rehydration. One integration smoke proves the wiring. This matches the repo's testing guidance: pure helper / contract unit tests at the boundary, plus one integration smoke per seam.short-term-promotion.test.tscovers the existing append path; no test asserted a disk budget.User-visible / Behavior Changes
For users with
dreaming.enabled: true, MEMORY.md will now be capped at 10,000 chars after dreaming promotion writes. Older auto-promoted sections are dropped (oldest first by date) before the new section is appended. No user-authored content is touched — only sections matching the## Promoted From Short-Term Memory (DATE)heading. Users who had already let MEMORY.md grow past the cap will see one-time compaction on the next dreaming promotion. The new optionalmemoryFileMaxCharsknob lets operators raise/lower the budget or set0to disable. No config schema change; the option is a runtime parameter onapplyShortTermPromotions.Diagram (if applicable)
Security Impact (required)
NoNoNoNoNoYes, explain risk + mitigation: N/A. The change only affects on-disk MEMORY.md content under the user's own workspace directory, drops only auto-emitted dreaming sections, and exposes no new I/O or trust surface.Repro + Verification
Environment
plugins.entries.memory-core.config.dreaming.enabled: truewith regular dreaming sweepsSteps
dreaming.enabled: trueand run for several weeks (or simulate by running the deep-phase sweep many times).~/.openclaw/workspace-<agent>/MEMORY.md.memoryFileMaxChars(default 10KB). Older auto-promoted sections are compacted out at write time. Compaction is reflected inapplied.compactedSections/applied.compactedDateson the function return.Expected
memoryFileMaxChars.##heading not matching the dreaming promotion pattern, plus the file's preamble) is preserved.Actual (with this PR)
short-term-promotion.test.tsexercise both branches: (a) over budget → drops oldest section; (b) under budget → no-op.Evidence
Attach at least one:
Local verification (Node 22.22.2, pnpm 10.33.0):
Human Verification (required)
What I personally verified beyond CI:
Verified scenarios:
budgetChars <= 0disable, empty input,##heading sandwiched between promotions, default-budget-below-bootstrap-cap invariant.applyShortTermPromotions; under-budget leaves seeded content intact.Edge cases checked:
budgetChars: 0opts out of compaction entirely.Boundary checks performed locally beyond CI: targeted
pnpm test(56/56),pnpm exec oxfmt --check(clean),git diff --check(clean),pnpm check:changed(exit 0), directpnpm tsgo:core/pnpm tsgo:extensions/pnpm tsgo:test:src(all exit 0).Live driver run (real exported
applyShortTermPromotionsagainst a real filesystem workspace, NOT vitest). Pre-seeded MEMORY.md at 11,714 chars (over the 10,000 budget), then ran 5 promotion sweeps. Result:File stayed under the 10 KB budget after every sweep (as the file approached the cap, the next sweep that would have overflowed dropped exactly one oldest section to make room). User-authored
## My Personal Notesand its bullets survived all 5 sweeps unchanged.What you did not verify: A multi-week live dreaming run end-to-end through the actual
dreaming.tscron schedule on a configured agent. The driver above exercises the exact sameapplyShortTermPromotionscode the dreaming sweep invokes, so any wiring bug between the cron and this function is the only gap.Review Conversations
Compatibility / Migration
YesNoNomemoryFileMaxCharsparameter is optional with a sensible default; existing callers that omit it get the new default behavior. Public SDK types (MemoryHostEventetc.) are unchanged.Risks and Mitigations
memoryFileMaxCharsor address the user content themselves; bootstrap-side truncation continues to apply on the prompt path.memoryFileMaxCharsis a runtime parameter; the budget can be raised per call or disabled by passing0. No config schema migration is needed. Default chosen with margin below bootstrap's 12KB cap so promoted memory continues to reach new sessions.Real behavior proof
Behavior or issue addressed: MEMORY.md grows unboundedly across deep-phase dreaming sweeps ([Bug]: MEMORY.md grows unbounded → bootstrap overflow → Gateway freeze #73691). The bug body reports the file growing past the bootstrap injection cap (~12KB) and triggering session write-lock timeouts that wedge the gateway. This PR caps MEMORY.md by compacting oldest auto-promoted sections before each write.
Real environment tested: Ubuntu noble x86_64, Node 22.22.2 via nvm, pnpm 10.33.0, real filesystem workspace under
/tmp/openclaw-pr73691-live-*, real exportedapplyShortTermPromotionsfrom this branch's working tree (no mocks, no vitest harness).Exact steps or command run after this patch: A standalone driver script imported the real exported
applyShortTermPromotions,rankShortTermPromotionCandidates, andrecordShortTermRecallsfrom the local working tree, pre-seeded MEMORY.md at 11,714 chars (over the 10,000 budget) with 14 dated promotion sections plus user-authored## My Personal Notes, then ran 5 promotion sweeps:NODE_ENV=test pnpm exec tsx /tmp/openclaw-pr-73691-live-driver.mtsEvidence after fix: Verbatim terminal capture of the live run (stdout, real numeric values, real workspace path):
Observed result after fix: After every sweep the on-disk file size stayed under the 10,000-char budget (9,386–9,968 across the 5 runs). Compaction triggered exactly when needed (sweep 1 dropped 3 oldest sections, sweep 4 dropped 1 more) and was a no-op when the file already fit. The user-authored
## My Personal Notesblock survived all 5 sweeps unchanged. Without this fix, the same 5 sweeps would have grown the file past 16KB.What was not tested: A multi-week wall-clock dreaming run end-to-end through the actual
dreaming.tscron schedule on a configured agent. The driver above invokes the same exportedapplyShortTermPromotionsthe dreaming sweep calls (extensions/memory-core/src/dreaming.ts:598), so any wiring bug between the cron and this function is the only gap.