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:
2026-01-05 18:55:32 +01:00
parent 35aea2d73a
commit 3edce9cd26
8 changed files with 86 additions and 78 deletions

View File

@@ -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