fix: clean up graph store data on Memory.delete()#4505
Merged
whysosaket merged 2 commits intomainfrom Mar 23, 2026
Merged
Conversation
When deleting a memory via Memory.delete(), the graph store (Neo4j, Memgraph, Kuzu, Neptune, Apache AGE) was not cleaned up — only the vector store and history DB were. This led to orphaned graph nodes and relationships accumulating over time. Add a delete(data, filters) method to every graph backend that reuses the existing entity extraction pipeline (_retrieve_nodes_from_data → _establish_nodes_relations_from_data → _delete_entities) to identify and remove graph relationships associated with the deleted memory. Hook this into Memory.delete() and AsyncMemory.delete(), with try/except so graph failures never block vector store deletion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
|
hey @utkarsh240799 please address the failing ci |
…assertion - Docker e2e tests now do a fast TCP socket check before attempting database connections, so they skip instantly in CI where no Docker containers are running (previously the driver-level timeout was too slow or the connection returned a Mock) - Fix test_main.py::test_delete to expect the new existing_memory second argument passed to _delete_memory() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
whysosaket
approved these changes
Mar 23, 2026
12 tasks
jamebobob
pushed a commit
to jamebobob/mem0-vigil-recall
that referenced
this pull request
Mar 29, 2026
Co-authored-by: utkarsh240799 <utkarsh240799@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked Issue
Closes #3245
Description
When deleting a memory via
Memory.delete(memory_id), the graph store (Neo4j, Memgraph, Kuzu, Neptune, Apache AGE) was not cleaned up — only the vector store and history DB were updated. This led to orphaned graph nodes and relationships accumulating over time, causing data inconsistency, storage bloat, and stale query results.Root cause:
_delete_memory()only handled vector store deletion + history recording. No graph backend had adelete()method for single-memory cleanup.Fix: Add a
delete(data, filters)method to every graph backend that reuses the existing entity extraction pipeline (_retrieve_nodes_from_data→_establish_nodes_relations_from_data→_delete_entities) to identify and remove graph relationships associated with the deleted memory text. Hook this intoMemory.delete()andAsyncMemory.delete().Key design decisions
_delete_entities()method, preserving per-backend semantics (Neo4j soft-deletes viar.valid=false, all others hard-delete viaDELETE r)delete()fetches the memory once and passes it to_delete_memory()via a new optionalexisting_memoryparameter (backward-compatible defaultNone)delete_all()unchanged — continues to use bulkgraph.delete_all(), no per-memory graph cleanup (avoids N×LLM calls)add()pipeline —_delete_memory()(called during implicit LLM-driven deletes in_add_to_vector_store) does NOT do graph cleanup, because the graph pipeline (_add_to_graph) runs in parallel and manages its own conflict resolution independentlyFiles changed
mem0/memory/graph_memory.pydelete(data, filters)— Neo4j, soft-deletemem0/memory/memgraph_memory.pydelete(data, filters)— Memgraph, hard-deletemem0/memory/kuzu_memory.pydelete(data, filters)— Kuzu, hard-deletemem0/graphs/neptune/base.pydelete(data, filters)— Neptune (both DB and Analytics), hard-deletemem0/memory/apache_age_memory.pydelete(data, filters)— Apache AGE, hard-deletemem0/memory/main.pyMemory.delete()andAsyncMemory.delete()now perform graph cleanup;_delete_memory()accepts optional pre-fetched memoryType of Change
Breaking Changes
N/A
Test Coverage
Unit tests (
tests/test_graph_delete.py— 15 tests, always run in CI)test_delete_calls_graph_cleanup_when_graph_enabledgraph.delete()called with correct text and filterstest_delete_skips_graph_when_not_enabledenable_graph=Falsetest_delete_continues_if_graph_cleanup_failstest_delete_skips_graph_when_no_user_iduser_idtest_delete_skips_graph_when_no_memory_texttest_delete_passes_all_filters_to_graphuser_id,agent_id,run_idall forwardedtest_async_delete_calls_graph_cleanuptest_async_delete_continues_if_graph_cleanup_failstest_delete_raises_for_nonexistent_memory_with_graph_enabledValueErrorraised, graph untouchedtest_async_delete_raises_for_nonexistent_memory_with_graph_enabledtest_delete_all_does_not_trigger_per_memory_graph_cleanupdelete_all()uses bulkgraph.delete_all()onlytest_internal_delete_memory_does_not_trigger_graph_cleanup_delete_memory()does NOT callgraph.delete()(safe for paralleladd()pipeline)test_graph_memory_delete_calls_internal_methodsMemoryGraph.delete()calls the correct internal pipelinetest_graph_memory_delete_skips_when_no_entitiestest_graph_memory_delete_handles_exceptionKuzu e2e tests (
tests/test_graph_delete_e2e.py— 14 tests, skipped ifkuzunot installed)Real Kuzu embedded database with mocked LLM/embedder:
test_add_creates_nodes_and_edgesadd()creates real graph datatest_delete_removes_edges_created_by_adddelete()removes relationships from real DBtest_delete_only_removes_matching_edgestest_delete_with_different_user_idtest_delete_nonexistent_relationship_is_safetest_delete_with_llm_failure_does_not_raisetest_delete_with_empty_entity_extractiontest_delete_all_removes_everything_for_usertest_add_delete_add_cycletest_memory_delete_triggers_graph_cleanupMemory.delete()stack cleans graphtest_memory_delete_with_graph_preserves_other_users_datatest_memory_delete_graph_failure_still_deletes_vectortest_memory_delete_all_uses_bulk_not_per_memorydelete_all()usesgraph.delete_all()test_memory_delete_nonexistent_raises_without_graph_side_effectsValueError, graph untouchedDocker e2e tests (
tests/test_graph_delete_docker.py— 23 tests, skipped if Docker DBs unavailable)Tests against real database instances with mocked LLM/embedder. All auto-skip in CI.
neo4j:5.23r.valid=false) confirmedmemgraph/memgraphDELETE r) confirmedapache/ageDELETE r) confirmeduser_idstring type assertionEach backend tests: add, delete, selective delete, user isolation, delete_all, add-delete-add cycle.
Manual testing
All 23 Docker tests were run locally against real Neo4j 5.23, Memgraph, and Apache AGE containers (all passed). Neptune was tested via Neo4j since it uses the same OpenCypher query language.
Checklist
🤖 Generated with Claude Code