fix(memory): address critical bugs from multi-agent review
Bug Fixes: - Remove singleton pattern from consolidation/reflection services to prevent stale database session bugs (session is now passed per-request) - Add LRU eviction to MemoryToolService._working dict (max 1000 sessions) to prevent unbounded memory growth - Replace O(n) list.remove() with O(1) OrderedDict.move_to_end() in RetrievalCache for better performance under load - Use deque with maxlen for metrics histograms to prevent unbounded memory growth (circular buffer with 10k max samples) - Use full UUID for checkpoint IDs instead of 8-char prefix to avoid collision risk at scale (birthday paradox at ~50k checkpoints) Test Updates: - Update checkpoint test to expect 36-char UUID - Update reflection singleton tests to expect new factory behavior - Add reset_memory_reflection() no-op for backwards compatibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,6 @@ Implements pattern detection, success/failure analysis, anomaly detection,
|
||||
and insight generation.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import statistics
|
||||
from collections import Counter, defaultdict
|
||||
@@ -1426,36 +1425,27 @@ class MemoryReflection:
|
||||
)
|
||||
|
||||
|
||||
# Singleton instance with async-safe initialization
|
||||
_memory_reflection: MemoryReflection | None = None
|
||||
_reflection_lock = asyncio.Lock()
|
||||
|
||||
|
||||
# Factory function - no singleton to avoid stale session issues
|
||||
async def get_memory_reflection(
|
||||
session: AsyncSession,
|
||||
config: ReflectionConfig | None = None,
|
||||
) -> MemoryReflection:
|
||||
"""
|
||||
Get or create the memory reflection service (async-safe).
|
||||
Create a memory reflection service for the given session.
|
||||
|
||||
Note: This creates a new instance each time to avoid stale session issues.
|
||||
The service is lightweight and safe to recreate per-request.
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
session: Database session (must be active)
|
||||
config: Optional configuration
|
||||
|
||||
Returns:
|
||||
MemoryReflection instance
|
||||
"""
|
||||
global _memory_reflection
|
||||
if _memory_reflection is None:
|
||||
async with _reflection_lock:
|
||||
# Double-check locking pattern
|
||||
if _memory_reflection is None:
|
||||
_memory_reflection = MemoryReflection(session=session, config=config)
|
||||
return _memory_reflection
|
||||
return MemoryReflection(session=session, config=config)
|
||||
|
||||
|
||||
async def reset_memory_reflection() -> None:
|
||||
"""Reset the memory reflection singleton (async-safe)."""
|
||||
global _memory_reflection
|
||||
async with _reflection_lock:
|
||||
_memory_reflection = None
|
||||
"""No-op for backwards compatibility (singleton pattern removed)."""
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user