Memory Retrieval Strategies
Duration: 15 min
Memory Retrieval Strategies
Duration: 15 min
Overview
Storing memories is only half the problem - retrieving the right memories at the right time is what makes an agent effective. A poor retrieval strategy means the agent either misses critical context or floods its working memory with irrelevant information. This module covers the core retrieval strategies: recency-based, relevance-based, importance-based, and hybrid approaches that combine multiple signals.
Key Concepts & Foundations
- What: Memory retrieval is the process of selecting which stored memories to inject into the agent's active context
- Why: Retrieving wrong or irrelevant memories degrades response quality; retrieving too many wastes context space
- How: Score memories by multiple signals (recency, relevance, importance, frequency) and select top candidates
Retrieval Signals
| Signal | Measures | Example | |--------|----------|---------| | Recency | How recently the memory was created/accessed | Prefer today's memories over last month's | | Relevance | Semantic similarity to current query | Prefer memories about the current topic | | Importance | How critical the memory is | User's name > random fact mentioned once | | Frequency | How often the memory has been accessed | Frequently recalled memories are likely useful |
The Retrieval Pipeline
1. Generate query - Convert current context into a retrieval query 2. Candidate retrieval - Get initial candidates (fast, broad) 3. Re-ranking - Score candidates by multiple signals (slower, precise) 4. Selection - Pick top-K memories within token budget 5. Formatting - Structure retrieved memories for the LLM prompt
Hands-On Implementation
import math
from dataclasses import dataclass, field
from datetime import datetime, timedelta
@dataclass
class Memory:
content: str
created_at: datetime = field(default_factory=datetime.now)
last_accessed: datetime = field(default_factory=datetime.now)
importance: float = 0.5
access_count: int = 0
tags: list[str] = field(default_factory=list)
def keyword_relevance(query: str, content: str) -> float:
"""Simple keyword overlap relevance score."""
query_words = set(query.lower().split())
content_words = set(content.lower().split())
if not query_words:
return 0.0
overlap = len(query_words & content_words)
return overlap / len(query_words)
def recency_score(memory: Memory, decay_hours: float = 24.0) -> float:
"""Exponential decay based on time since last access."""
hours_ago = (datetime.now() - memory.last_accessed).total_seconds() / 3600
return math.exp(-hours_ago / decay_hours)
class MemoryRetriever:
"""Multi-signal memory retrieval system."""
def __init__(self, weights: dict = None):
self.memories: list[Memory] = []
self.weights = weights or {
"relevance": 0.4,
"recency": 0.3,
"importance": 0.2,
"frequency": 0.1
}
def add(self, content: str, importance: float = 0.5, tags: list[str] = None):
self.memories.append(Memory(
content=content, importance=importance, tags=tags or []
))
def retrieve(self, query: str, top_k: int = 5, min_score: float = 0.1) -> list[tuple[Memory, float, dict]]:
"""Retrieve memories ranked by combined score."""
max_access = max((m.access_count for m in self.memories), default=1) or 1
scored = []
for memory in self.memories:
signals = {
"relevance": keyword_relevance(query, memory.content),
"recency": recency_score(memory),
"importance": memory.importance,
"frequency": memory.access_count / max_access
}
combined = sum(
self.weights[k] * signals[k] for k in self.weights
)
if combined >= min_score:
scored.append((memory, combined, signals))
scored.sort(key=lambda x: x[1], reverse=True)
# Update access metadata for retrieved memories
for memory, _, _ in scored[:top_k]:
memory.last_accessed = datetime.now()
memory.access_count += 1
return scored[:top_k]
def retrieve_by_tag(self, tag: str) -> list[Memory]:
return [m for m in self.memories if tag in m.tags]
Demo: Multi-signal retrieval
retriever = MemoryRetriever()Add memories with varying importance and timing
retriever.add("User's name is Alex", importance=0.9, tags=["identity"])
retriever.add("User prefers Python for data science", importance=0.7, tags=["preference"])
retriever.add("User asked about decorators yesterday", importance=0.3, tags=["topic"])
retriever.add("User is building a RAG pipeline", importance=0.8, tags=["project"])
retriever.add("User mentioned they have a demo on Friday", importance=0.6, tags=["schedule"])
retriever.add("User likes concise code examples", importance=0.7, tags=["preference"])
retriever.add("User struggled with async Python", importance=0.5, tags=["struggle"])Simulate some access patterns
for m in retriever.memories[:3]:
m.access_count = 5 # frequently accessedRetrieve for current query
query = "help with Python async code for my RAG pipeline"
results = retriever.retrieve(query, top_k=4)print(f"Query: '{query}'\n")
print("Retrieved memories (ranked):")
for memory, score, signals in results:
print(f" [{score:.3f}] {memory.content}")
print(f" relevance={signals['relevance']:.2f} recency={signals['recency']:.2f} "
f"importance={signals['importance']:.2f} frequency={signals['frequency']:.2f}")
Advanced Techniques
1. Learned Retrieval Weights - Tune signal weights based on user feedback (did the retrieved memory help?) 2. Query Expansion - Use the LLM to rephrase the retrieval query for better recall 3. Negative Retrieval - Explicitly exclude memories marked as outdated or corrected 4. Contextual Re-ranking - Re-rank candidates based on the full conversation, not just the last message 5. Adaptive Top-K - Dynamically adjust how many memories to retrieve based on available context space
Quiz
Q1: Why is pure relevance-based retrieval insufficient?
- A) It is too fast
- B) It ignores recency, importance, and usage patterns that affect memory utility ✓
- C) It requires too much storage
- D) It only works with short memories
Q2: What does exponential decay accomplish in recency scoring?
- A) It makes all memories equally important
- B) It gradually reduces the score of older memories, preferring recent information ✓
- C) It deletes old memories permanently
- D) It increases the score over time
Q3: When should retrieval weights be adjusted?
- A) Never - default weights always work
- B) When the agent consistently retrieves unhelpful memories for certain query types ✓
- C) Only during initial setup
- D) Only when adding new memory types