Memory Deduplication System¶
Date: January 8, 2026 Status: Implemented Inspired By: Tolan Voice Agent Architecture
Overview¶
The Memory Deduplication System is a nightly background job that removes redundant memory entries to maintain high memory quality. This is inspired by Tolan's approach of running "a nightly compression job that removes low-value or redundant entries."
Problem Solved¶
Over time, similar memories accumulate: - "User mentioned wife Sarah likes hiking" - "User's wife Sarah enjoys hiking outdoors" - "Sarah (user's wife) is into hiking"
These duplicates waste storage, slow searches, and can cause inconsistent recall. The deduplication job consolidates them into a single, authoritative memory.
Architecture¶
Nightly Job (3am)
│
▼
┌─────────────────────────────────────┐
│ Get Active Users (last 7 days) │
│ SQL query on conversations │
└─────────────────────────────────────┘
│
▼
For each user:
┌─────────────────────────────────────┐
│ Fetch All Memories │
│ Multiple broad queries │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Find Duplicate Groups │
│ Similarity score > 0.92 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Merge Groups │
│ Keep most recent, delete rest │
└─────────────────────────────────────┘
Components¶
1. Memory Maintenance Service (app/memory/memory_maintenance.py)¶
Core deduplication logic.
from app.memory.memory_maintenance import get_maintenance_service
maintenance = get_maintenance_service()
stats = await maintenance.run_deduplication(
user_id="+1234567890",
persona_id="sage",
dry_run=False # Set True to preview without deleting
)
# Returns:
# {
# "deduplicated": 5,
# "total_checked": 42,
# "duplicate_groups_found": 2,
# "kept_memories": [...],
# "deleted_memories": [...]
# }
2. Scheduled Job (app/scheduler/background_service.py)¶
Runs nightly at 3am.
# In SchedulerService.start():
self.scheduler.add_job(
self.run_memory_deduplication,
CronTrigger(hour=3, minute=0),
id='memory_deduplication',
name='Nightly Memory Deduplication'
)
Key Features: - Distributed lock prevents duplicate execution across workers - Lock TTL: 20 hours (job runs once daily) - Processes users active in last 7 days only - Graceful error handling per user
Deduplication Algorithm¶
Step 1: Fetch All Memories¶
Since Supermemory doesn't have a "get all" endpoint, we use multiple broad queries: - Empty query (returns recent) - "user", "mentioned", "likes", "wants", "trip", "family", "work"
Results are deduplicated by memory ID during collection.
Step 2: Find Duplicate Groups¶
For each memory: 1. Search Supermemory using the memory's text as query 2. Find results with similarity score >= 0.92 3. Group together as duplicates
Step 3: Merge Groups¶
For each duplicate group: 1. Sort by timestamp (most recent first) 2. Keep the most recent memory 3. Delete all others
Configuration¶
| Setting | Value | Location |
|---|---|---|
| Schedule | 3:00 AM daily | background_service.py:125 |
| Similarity threshold | 0.92 | memory_maintenance.py:44 |
| User activity window | 7 days | background_service.py:470 |
| Lock TTL | 20 hours | background_service.py:412 |
Monitoring¶
Logs¶
[MemDedup] +1234567890: removed 3 duplicates
Memory deduplication complete: 15 duplicates removed from 8 users
Metrics (in stats dict)¶
deduplicated: Number of memories deletedtotal_checked: Total memories analyzedduplicate_groups_found: Number of duplicate clusters foundkept_memories: List of memories that were keptdeleted_memories: List of memories that were removed
Dry Run Mode¶
Test deduplication without actually deleting:
stats = await maintenance.run_deduplication(
user_id="+1234567890",
persona_id="sage",
dry_run=True # Preview only
)
print(f"Would delete: {stats['deduplicated']} memories")
Safety Features¶
- Distributed Lock - Only one worker runs the job
- Per-User Error Isolation - One user's failure doesn't stop others
- Keep Most Recent - Always preserves the newest memory
- Logging - Full audit trail of kept/deleted memories
- Dry Run - Test without side effects
Manual Execution¶
For debugging or immediate cleanup:
from app.memory.memory_maintenance import run_maintenance_for_user
# Run for a specific user
stats = await run_maintenance_for_user(
user_id="+1234567890",
persona_id="sage",
dry_run=True
)
Future Enhancements¶
- Low-value cleanup - Remove trivial memories ("user said hi")
- Contradiction resolution - Handle conflicting facts ("lives in NYC" vs "lives in LA")
- Memory compression - Merge related facts into single memories
- Configurable thresholds - Per-user or per-memory-type settings
- Metrics dashboard - Track deduplication effectiveness over time