Skip to content

fix(memory): keep max_msg_id valid when an overwrite rollback empties the cache - #994

Open
Linxiushen wants to merge 1 commit into
modelscope:mainfrom
Linxiushen:fix-memory-overwrite-rollback-empty-cache
Open

Linxiushen wants to merge 1 commit into
modelscope:mainfrom
Linxiushen:fix-memory-overwrite-rollback-empty-cache

Conversation

@Linxiushen

Copy link
Copy Markdown

Change Summary

DefaultMemory._delete_single() (ms_agent/memory/default_memory.py) recomputes max_msg_id with max(self.cache_messages.keys()) after popping the entry that held the maximum. With history_mode='overwrite', add() rolls back every cached block starting from the first one whose hash no longer matches the incoming history. When the very first block mismatches — a second conversation reusing the same memory directory, or an edited/cleared history — the last cached entry is deleted as well, the dict is empty, and max() raises:

File "ms_agent/memory/default_memory.py", line 449, in add
    self._delete_single(msg_id=msg_id)
File "ms_agent/memory/default_memory.py", line 181, in _delete_single
    self.max_msg_id = max(self.cache_messages.keys())
ValueError: max() iterable argument is empty

The exception leaves add() uncaught, reaches run_loop's handler as ErrorRaised(recoverable=False) and terminates the whole agent run. It is not hit with the default history_mode='add'; it needs history_mode: overwrite in the memory config (the mode has its own test, test_overwrite_with_tool, which only covers a partial rollback where block 0 still matches).

Fix: max(self.cache_messages.keys(), default=-1). -1 is the sentinel load_cache() already uses for an empty store, so add_single()'s self.max_msg_id += 1 numbers the next block from 0 exactly as on a fresh store; verified that "full rollback, then add a block" ends in the same state as "add the same block to a new store" (cache keys, max_msg_id, block hash, memory_snapshot).

Tests: tests/memory/test_default_memory_rollback.py — two cases (second session with a different first turn; rollback that empties the cache and re-adds), with mem0 stubbed since only the cache bookkeeping is under test. Both fail on main with the ValueError above and pass with the change. A differential over the well-formed overwrite/add scenarios (build 3 blocks, resubmit same history, partial rollback, tail rollback, reload from disk, delete a non-max id) produces identical state dumps before and after; pytest tests/memory goes from 176 to 178 passed with the same pre-existing environment failures.

flake8 / isort / yapf (repo setup.cfg) clean on the changed lines (E126/W604 reported elsewhere in the file pre-exist on main).

Related issue number

None found (searched max_msg_id, _delete_single, history_mode, overwrite, max() iterable).

Checklist

  • The PR title is a changelog-ready summary of the change
  • Unit tests added for the change
  • pre-commit checks (flake8 / isort / yapf from setup.cfg) pass on the changed files
  • Documentation updated — not needed, no user-facing behaviour change beyond not crashing

Written with Claude Code (AI-assisted) and submitted under the account owner's authorization.

… the cache

DefaultMemory._delete_single() recomputes max_msg_id with
max(self.cache_messages.keys()) after popping the entry that held it.
In history_mode='overwrite', add() rolls back every cached block from
the first mismatching one; when the very first block mismatches (a new
conversation reusing the same store, or an edited history) the last
entry is deleted too, the dict is empty and max() raises ValueError,
which propagates out of add() and ends the agent run as unrecoverable.

Use the same -1 empty-store sentinel load_cache() sets, so the next
block is numbered from 0 exactly as on a fresh store.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant