Skip to main content
Glama
review.py6.3 kB
"""Natural spaced repetition for memories. This module implements natural reinforcement through conversation rather than explicit quizzing. Memories due for review are blended into search results, creating the "Maslow effect" - natural repetition across contexts. """ import time from cortexgraph.config import get_config from cortexgraph.core.decay import calculate_score from cortexgraph.storage.models import Memory def calculate_review_priority(memory: Memory) -> float: """Calculate review priority for a memory. Priority is based on the "danger zone" - memories that are fading but not yet forgotten. These benefit most from natural reinforcement. Args: memory: The memory to evaluate Returns: Priority score from 0.0 (not needed) to 1.0 (urgent) Algorithm: - Score < danger_zone_min (0.15): Too far gone, priority = 0.0 - Score > danger_zone_max (0.35): Still fresh, priority = 0.0 - Score in danger zone: Linear mapping to [0.0, 1.0] - Peak priority at midpoint of danger zone (score ~0.25) """ config = get_config() score = calculate_score( use_count=memory.use_count, last_used=memory.last_used, strength=memory.strength, ) danger_min = config.review_danger_zone_min danger_max = config.review_danger_zone_max # Outside danger zone if score < danger_min or score > danger_max: return 0.0 # Map score to priority using inverted parabola # Peak priority at midpoint, tapering off at edges midpoint = (danger_min + danger_max) / 2 range_half = (danger_max - danger_min) / 2 # Normalize to [-1, 1] around midpoint normalized = (score - midpoint) / range_half # Inverted parabola: 1 - x^2 priority = 1.0 - (normalized**2) return max(0.0, min(1.0, priority)) def get_memories_due_for_review( all_memories: list[Memory], min_priority: float = 0.3, limit: int = 50, ) -> list[Memory]: """Get memories that would benefit from natural reinforcement. Args: all_memories: All active memories to consider min_priority: Minimum priority threshold (default 0.3) limit: Maximum number to return Returns: List of memories sorted by priority (highest first) """ # Calculate priority for each memory and filter candidates = [] for mem in all_memories: priority = calculate_review_priority(mem) if priority >= min_priority: # Update the memory's review_priority field mem.review_priority = priority candidates.append(mem) # Sort by priority (highest first) candidates.sort(key=lambda m: m.review_priority, reverse=True) return candidates[:limit] def blend_search_results( primary_results: list[Memory], review_candidates: list[Memory], blend_ratio: float = 0.3, ) -> list[Memory]: """Blend search results with review candidates. Creates natural spaced repetition by injecting memories due for review into search results when they're relevant. Args: primary_results: Main search results (by relevance) review_candidates: Memories due for review blend_ratio: Fraction of results that should be review candidates (0.0-1.0) Returns: Blended list maintaining roughly the target ratio Example: If top_k=10 and blend_ratio=0.3, return ~7 primary + ~3 review """ if not review_candidates or blend_ratio <= 0: return primary_results total_slots = len(primary_results) review_slots = int(total_slots * blend_ratio) primary_slots = total_slots - review_slots # Interleave them for better distribution result = [] primary_iter = iter(primary_results[:primary_slots]) review_iter = iter(review_candidates[:review_slots]) # Alternate between primary and review try: while True: # Add 2-3 primary results for _ in range(2): result.append(next(primary_iter)) # Add 1 review candidate result.append(next(review_iter)) except StopIteration: # Exhaust remaining result.extend(primary_iter) result.extend(review_iter) return result[:total_slots] def reinforce_memory(memory: Memory, cross_domain: bool = False) -> Memory: """Reinforce a memory through natural usage. This is called when a memory is actually used in conversation, not just retrieved. Updates usage stats and potentially boosts strength. Args: memory: Memory to reinforce cross_domain: Whether this usage was in a different context than usual Returns: Updated memory with reinforcement applied """ now = int(time.time()) # Update basic usage stats memory.last_used = now memory.use_count += 1 memory.last_review_at = now memory.review_count += 1 # Cross-domain usage is particularly valuable if cross_domain: memory.cross_domain_count += 1 # Small strength boost for cross-domain reinforcement (max 2.0) memory.strength = min(2.0, memory.strength + 0.05) # Reset review priority (will be recalculated next time) memory.review_priority = 0.0 return memory def detect_cross_domain_usage( memory: Memory, current_context_tags: list[str], ) -> bool: """Detect if memory is being used in a different context than usual. Cross-domain usage (like seeing "Maslow's hierarchy" in history, econ, and sociology classes) is particularly valuable for retention. Args: memory: The memory being used current_context_tags: Tags representing current conversation context Returns: True if this appears to be cross-domain usage """ memory_tags = set(memory.meta.tags) context_tags = set(current_context_tags) # If no tags, can't determine if not memory_tags or not context_tags: return False # Calculate tag overlap overlap = len(memory_tags & context_tags) total = len(memory_tags | context_tags) if total == 0: return False jaccard_similarity = overlap / total # Low similarity = cross-domain usage # Threshold: <30% tag overlap return jaccard_similarity < 0.3

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prefrontalsys/mnemex'

If you have feedback or need assistance with the MCP directory API, please join our Discord server