feat(memory): add auto_archive opt-in to escape oversize drift (#26045) - #42899
feat(memory): add auto_archive opt-in to escape oversize drift (#26045)#42899veawho wants to merge 1 commit into
Conversation
Problem: - Drift signal #2 (any single entry larger than the char_limit) blocks ALL mutations with no in-tool recovery path. The operator has to hand-edit MEMORY.md (which itself causes further drift) or bump memory_char_limit in config and restart the gateway. - Real-world hit 2026-06-09: a 4,937-char entry (1.7x the 2,200-char limit) blocked all memory adds for the rest of the session. Solution: - Add opt-in auto_archive=True to memory(action=add). - When drift signal #2 fires AND auto_archive=True, move the oversize entry to a sidecar file (MEMORY-archive-<ts>.md or USER-archive-<ts>.md) with an envelope header, then re-serialize the primary file without it. - The original .bak snapshot is preserved as audit trail. - Drift signal #1 (round-trip mismatch / structural corruption) is NOT auto-fixed -- that needs human eyes. auto_archive only repairs the oversize case. Backward compatibility: - Default is auto_archive=False -- every existing call site behaves identically. - The drift guard continues to refuse mutations by default. - 7 new tests in TestAutoArchiveOversize; full 76-test suite still passes. Refs #26045, complements PR #30993 (which adds SHA-256 on-disk-change guard).
Cross-reference: real-world reproduction from issue #26045I'm filing this comment to make the link to #26045 explicit and to share the concrete reproduction that motivated this PR. In a Hermes session on 2026-06-09, a single memory entry grew to 4,937 chars (1.7x the 2,200-char ) — likely the result of a manual edit / tool operation that swallowed the leading separator between v17 and v18. From that moment on:
This PR adds the missing escape hatch: automatically moves the oversize entry to and lets the add succeed on retry. Default behaviour is unchanged (), so the drift guard still does its job for the typical case. Why this complements (rather than competes with) the existing fix surface
Notes from the #26045 discussion thread@jrhouston-trilogy in #26045 recommended a split-file architecture and an explicit side store for content that doesn't fit the tool's mental model. This PR's is a pragmatic implementation of that side-store idea — we don't split the file (UX cost too high), but we do route oversize content to a clearly-marked sibling that the tool never touches, so it survives any future drift events. Worth noting: the rotation in already captures the pre-drift-detected state (not post-write like the issue thread suggested for #26045's replace case), so this PR preserves both the and the new archive as an audit trail. Reproducer for this PR specificallyBefore this PR: # MEMORY.md contains a single 4500-char entry
result = store.add("memory", "new entry")
# => {"success": False, "error": "Refusing to write MEMORY.md: file on disk has content that wouldn't round-trip...", "drift_backup": "/path/MEMORY.md.bak.1234"}After this PR (with opt-in): result = store.add("memory", "new entry", auto_archive=True)
# => {"success": True, "message": "Auto-archived oversize entries to /path/MEMORY-archive-20260609-225500.md. Primary file now has 0 entries (0/2200 chars). Original file preserved at /path/MEMORY.md.bak.1234. Retry your add."}
# Then the next add (without auto_archive) succeeds normally.Test plan (already passing locally)cc anyone who's been watching the #26045 thread — happy to iterate on the API shape if there's a preferred direction. |
Problem
Drift signal #2 () blocks all mutations with no in-tool recovery path. The operator must hand-edit (which itself causes further drift because manual edits don't go through the tool) or bump in and restart the gateway.
Real-world hit on 2026-06-09: a single 4,937-char entry (1.7x the 2,200-char limit) blocked every memory add for the rest of the session. The fix at the time required raising to 12,000 in config + gateway restart + manual file rewrite.
Solution
Add an opt-in parameter to :
Backward compatibility
Relationship to other PRs
Test plan
Files changed
tools/memory_tool.py: +107/-4 — new method, accepts , top-level and plumb the flag, registry handler forwards it.tests/tools/test_memory_tool.py: +110 — new class with 7 tests.Refs #26045