bug fix: unexpected vector store reset when Memory.delete_all()
Fix: Memory.delete_all() incorrectly wiping entire vector store collection
Problem Description
The Memory.delete_all() method had a critical bug where it was calling self.vector_store.reset() after deleting filtered memories. This caused the entire vector store collection to be wiped, not just the memories that matched the provided filters (user_id, agent_id, run_id).
Impact
- Data Loss: When users called
delete_all(user_id="user1"), it would delete user1's memories as expected, but then wipe ALL memories from ALL users - Unexpected Behavior: The method name suggests filtering, but the implementation caused a complete reset
- Production Risk: This could cause catastrophic data loss in production environments
Root Cause
In mem0/memory/main.py, line 1053, the delete_all() method contained:
# delete vector memories by filter
memories = self.vector_store.list(filters=filters)[0]
for memory in memories:
self._delete_memory(memory.id)
# self.vector_store.reset() # <-- This line was the problem
The self.vector_store.reset() call was incorrectly resetting the entire vector store collection after the filtered deletion.
Solution
Simple Fix: Comment out the problematic self.vector_store.reset() line.
Why This Fix Is Correct
-
Filtered Deletion Already Works: The code above the reset call correctly:
- Queries for memories matching the filters
- Deletes each memory individually by ID
- This preserves unfiltered memories as intended
-
Reset Is Unnecessary: The
reset()method is designed to clear ALL data, which contradicts the filtering behavior users expect fromdelete_all(user_id=...). According to the docs,delete_all()should only delete filtered memories (https://docs.mem0.ai/core-concepts/memory-operations/delete). Resetting the whole vector store is unexpected behavior. -
Consistent with Method Contract: The method signature and documentation indicate filtered deletion, not a complete reset
Changes Made
- File:
mem0/memory/main.py - Line: 1053
- Change: Commented out
# self.vector_store.reset()
How Has This Been Tested?
- [x] Test Script
def test_delete_all_does_not_call_vector_store_reset(mock_memory_instance):
"""
Test that delete_all() only deletes specific memories by ID and does not call vector_store.reset().
This test verifies the bug fix where delete_all() was incorrectly calling
self.vector_store.reset() which wiped the entire collection.
"""
# Setup: Create mock memories that would be returned by list()
mock_mem1 = MockMemory("mem_1", {"data": "User likes pizza", "user_id": "user1"})
mock_mem2 = MockMemory("mem_2", {"data": "User likes pasta", "user_id": "user1"})
mock_mem3 = MockMemory("mem_3", {"data": "Agent learned something", "user_id": "user2"})
# Mock vector_store.list to return only user1's memories
mock_memory_instance.vector_store.list.return_value = ([mock_mem1, mock_mem2], None)
# Execute: Call delete_all with user_id filter
with patch('mem0.memory.main.process_telemetry_filters') as mock_telemetry:
mock_telemetry.return_value = (["user_id"], ["encoded_user1"])
with patch('mem0.memory.main.capture_event') as mock_capture:
result = mock_memory_instance.delete_all(user_id="user1")
# Verify: Check that only specific memories were deleted by ID
assert mock_memory_instance._delete_memory.call_count == 2
mock_memory_instance._delete_memory.assert_any_call("mem_1")
mock_memory_instance._delete_memory.assert_any_call("mem_2")
# Critical: Verify that vector_store.reset() was NOT called
mock_memory_instance.vector_store.reset.assert_not_called()
# Verify the list was called with correct filters
mock_memory_instance.vector_store.list.assert_called_once_with(filters={"user_id": "user1"})
# Verify return message
assert result["message"] == "Memories deleted successfully!"
def test_delete_all_preserves_unfiltered_memories():
"""
Integration test to verify that delete_all() only removes filtered memories
and leaves other memories intact. This simulates the real-world scenario
where the bug would cause data loss.
"""
# This test would ideally use a real vector store, but we'll simulate it with mocks
with patch('mem0.utils.factory.EmbedderFactory.create'):
with patch('mem0.utils.factory.VectorStoreFactory.create') as mock_vector_factory:
with patch('mem0.utils.factory.LlmFactory.create'):
with patch('mem0.memory.storage.SQLiteManager'):
# Setup mock vector store
mock_vector_store = MagicMock()
mock_vector_factory.return_value = mock_vector_store
# Simulate vector store state with memories for different users
user1_memories = [
MockMemory("mem_1", {"data": "User1 memory 1", "user_id": "user1"}),
MockMemory("mem_2", {"data": "User1 memory 2", "user_id": "user1"})
]
user2_memories = [
MockMemory("mem_3", {"data": "User2 memory 1", "user_id": "user2"}),
MockMemory("mem_4", {"data": "User2 memory 2", "user_id": "user2"})
]
# Mock list method to return only user1 memories when filtered
def mock_list(filters=None):
if filters and filters.get("user_id") == "user1":
return (user1_memories, None)
elif filters and filters.get("user_id") == "user2":
return (user2_memories, None)
else:
# Return all memories (this simulates what reset would affect)
return (user1_memories + user2_memories, None)
mock_vector_store.list.side_effect = mock_list
# Track deletions
deleted_ids = []
def mock_delete(vector_id):
deleted_ids.append(vector_id)
mock_vector_store.delete.side_effect = mock_delete
# Create memory instance
from mem0.memory.main import Memory as MemoryClass
memory = MemoryClass()
# Mock _delete_memory to track what gets deleted
memory._delete_memory = MagicMock(side_effect=lambda mem_id: deleted_ids.append(mem_id))
# Execute: Delete all memories for user1
with patch('mem0.memory.main.process_telemetry_filters') as mock_telemetry:
mock_telemetry.return_value = (["user_id"], ["encoded"])
with patch('mem0.memory.main.capture_event'):
result = memory.delete_all(user_id="user1")
# Verify: Only user1's memories were deleted
assert "mem_1" in deleted_ids
assert "mem_2" in deleted_ids
assert "mem_3" not in deleted_ids # user2's memories should be preserved
assert "mem_4" not in deleted_ids # user2's memories should be preserved
# Verify reset was not called (critical for the bug fix)
mock_vector_store.reset.assert_not_called()
# Verify list was called with proper filter, not without filter (which reset would imply)
mock_vector_store.list.assert_called_with(filters={"user_id": "user1"})
assert result["message"] == "Memories deleted successfully!"
Test Results
Before/After Behavior
Before (Buggy)
memory.add("User1 likes pizza", user_id="user1")
memory.add("User2 likes pasta", user_id="user2")
memory.delete_all(user_id="user1")
# Result: ALL memories deleted (user1 AND user2)
After (Fixed)
memory.add("User1 likes pizza", user_id="user1")
memory.add("User2 likes pasta", user_id="user2")
memory.delete_all(user_id="user1")
# Result: Only user1's memory deleted, user2's memory preserved
Related Issues
This fix resolves the data loss issue where filtered deletions would unexpectedly wipe the entire memory collection.
related issue: https://github.com/mem0ai/mem0/issues/3746