fix(memory): warn when MEMORY.md / USER.md exceed their char limit on load

The cap only fires on add/replace, so an externally written over-budget
file rode silently in the system prompt while every later add was refused
with no visible cause. Warn at load; entries stay loaded (never truncate
a user's memories).

Salvage of #10886 (original hunk targeted memory_tool.py before the
store split); authored by @easyvibecoding.

Refs #10877
This commit is contained in:
easyvibecoding
2026-09-12 06:16:11 -07:00
committed by Teknium
parent df8b548797
commit 4e1b3daa86
2 changed files with 27 additions and 0 deletions

View File

@@ -270,6 +270,26 @@ class TestMemoryStorePersistence:
assert len(store.memory_entries) == 2
class TestMemoryStoreCharLimitOnLoad:
@pytest.mark.parametrize("filename, target", [("MEMORY.md", "memory"), ("USER.md", "user")])
def test_over_limit_file_loads_but_warns(self, tmp_path, monkeypatch, caplog, filename, target):
"""An externally written over-budget file is kept (no silent data loss) and named in a
warning; an in-budget file loads quietly (#10877)."""
import logging
monkeypatch.setattr("tools.memory_tool.get_memory_dir", lambda: tmp_path)
(tmp_path / filename).write_text("x" * 600, encoding="utf-8")
with caplog.at_level(logging.WARNING):
store = MemoryStore(memory_char_limit=500, user_char_limit=300)
store.load_from_disk()
assert filename in caplog.text and "exceeds" in caplog.text
assert len(store._entries_for(target)) == 1
caplog.clear()
(tmp_path / filename).write_text("short", encoding="utf-8")
with caplog.at_level(logging.WARNING):
MemoryStore(memory_char_limit=500, user_char_limit=300).load_from_disk()
assert "exceeds" not in caplog.text
class TestMemoryStoreSnapshot:
def test_snapshot_frozen_at_load(self, store):
assert store.format_for_system_prompt("memory") is None # empty store

View File

@@ -130,6 +130,13 @@ class MemoryStore:
# Deduplicate (order-preserving, first occurrence wins).
entries = list(dict.fromkeys(self._read_file(path)))
self._set_entries(target, entries)
# External writers (MCP bridges, hand edits) can exceed the cap; the limit only fires on
# add/replace, so the oversized block would silently ride in the prompt while every later
# add is refused with no visible cause (#10877). Warn; never truncate a user's memories.
if (count := self._char_count(target)) > (limit := self._char_limit(target)):
logger.warning("%s exceeds its char limit on load: %d/%d chars. Entries stay loaded; "
"further additions are blocked until it is back under the limit.",
path.name, count, limit)
self._system_prompt_snapshot[target] = self._render_block(target, [_sanitize(e, path.name) for e in entries])
@staticmethod