Every agent framework promises memory.
"Persistent memory across sessions." "Long-term learning." "Agents that remember."
Then you try to use it in production.
Your agent forgets critical context mid-conversation. Memory bloats until responses slow to a crawl. The agent "remembers" things that never happened. Retrieval pulls irrelevant memories while missing obvious ones.
I've built memory systems for dispute resolution agents handling 15,000+ cases, marketing AI that maintains brand voice across thousands of interactions, and financial advisors tracking complex client portfolios.
Here's what I've learned: agent memory is a systems problem, not a feature toggle. The frameworks give you primitives. Making them work requires architecture.
Why Memory Is Hard
First, let's understand why this is harder than it looks.
The Context Window Trap
LLMs have context windows. Big ones now—128K, 200K tokens. So why not just stuff everything in?
Because attention degrades. At 128K tokens, the model literally can't attend to everything equally. Information in the middle gets lost. Relevant details drown in noise.
More context ≠ better memory. It often means worse.
The Retrieval Problem
"Just use RAG for memory" sounds reasonable until you try it.
Memory retrieval is different from document retrieval:
- • Queries are implicit (what does the agent need to know right now?)
- • Relevance is contextual (same memory is relevant in one situation, not another)
- • Recency matters (recent interactions often trump older ones)
- • Relationships matter (memory A is relevant because of its connection to memory B)
Standard vector similarity doesn't capture this.
The Staleness Problem
Memories become stale. User preferences change. Facts update. Relationships evolve.
An agent that "remembers" your favorite restaurant from two years ago isn't helpful—it's annoying. Memory systems need decay, updates, and contradiction resolution.
The Consistency Problem
In multi-agent systems, multiple agents might access and update shared memory simultaneously. Without coordination, you get inconsistencies. Agent A thinks the user prefers X. Agent B thinks they prefer Y. Chaos ensues.
The Memory Architecture That Works
After building several production systems, I've converged on a layered architecture:
4-Layer Memory Architecture
Layer 1: Working Memory
Current conversation context. Lives in the prompt.
- • Recent messages (last 5-10 turns)
- • Active task state
- • Scratchpad for reasoning
Layer 2: Short-Term Memory
Session and recent interaction history.
- • Conversation summaries
- • Recent decisions and their rationale
- • Temporary preferences and corrections
Layer 3: Long-Term Memory
Persistent knowledge about users, entities, patterns.
- • User profiles and preferences
- • Entity relationships
- • Learned patterns and insights
Layer 4: Episodic Memory
Specific past interactions that might be relevant.
- • Notable conversations
- • Key decisions and outcomes
- • Important events
Each layer serves a different purpose. Each has different storage, retrieval, and lifecycle characteristics.
Layer 1: Working Memory
Working memory is what's in the prompt right now. It's the agent's immediate awareness.
What Goes Here
- • Last N conversation turns (typically 5-10)
- • Current task description and state
- • Relevant context retrieved from other layers
- • Scratchpad for chain-of-thought reasoning
The Pattern: Sliding Window + Summarization
class WorkingMemory:
def __init__(self, max_turns: int = 10, max_tokens: int = 4000):
self.max_turns = max_turns
self.max_tokens = max_tokens
self.turns = []
self.summary = ""
def add_turn(self, role: str, content: str):
self.turns.append({"role": role, "content": content})
# If exceeding limits, summarize older turns
if len(self.turns) > self.max_turns:
self._compress()
def _compress(self):
# Summarize oldest turns
old_turns = self.turns[:5]
new_summary = llm.summarize(old_turns)
# Update summary and remove old turns
self.summary = f"{self.summary}\n{new_summary}"
self.turns = self.turns[5:]Key insight: Don't just truncate old messages—summarize them. You lose detail but preserve the important bits.
Anti-Pattern: Stuffing Everything
I've seen systems that dump entire conversation histories into the context. At 50 turns, you're burning 20K+ tokens and drowning the model in noise.
More is not better. Be selective.
Layer 2: Short-Term Memory
Short-term memory bridges sessions. It's what the agent remembers from earlier today, yesterday, this week.
What Goes Here
- • Conversation summaries from recent sessions
- • Temporary user corrections ("Actually, I prefer X")
- • Recent decisions and their context
- • Active tasks and their state
The Pattern: Time-Decayed Cache
class ShortTermMemory:
def __init__(self, redis_client, decay_hours: int = 72):
self.redis = redis_client
self.decay_hours = decay_hours
def store(self, user_id: str, key: str, value: dict):
memory_key = f"stm:{user_id}:{key}"
value["timestamp"] = time.time()
value["access_count"] = 0
self.redis.setex(
memory_key,
timedelta(hours=self.decay_hours),
json.dumps(value)
)
def retrieve(self, user_id: str, key: str) -> dict | None:
data = self.redis.get(f"stm:{user_id}:{key}")
if data:
value = json.loads(data)
# Boost TTL on access (frequently accessed = more important)
value["access_count"] += 1
self.redis.setex(...) # Extend TTL
return value
return NoneKey insight: Use TTLs and access patterns to naturally decay irrelevant memories. Memories that aren't accessed fade away.
Promotion Pattern
Important short-term memories should promote to long-term storage:
- • Frequently accessed (access_count > 5) → Promote
- • Type is "preference" → Promote
- • Explicitly marked important → Promote
Layer 3: Long-Term Memory
Long-term memory is persistent knowledge. User profiles, learned preferences, entity relationships.
What Goes Here
- • User profile (name, preferences, history summary)
- • Entity relationships (user → company, user → projects)
- • Learned patterns (user tends to prefer X over Y)
- • Important facts (user is allergic to shellfish)
The Pattern: Structured + Unstructured Hybrid
Long-term memory needs both structured storage (for reliable retrieval) and unstructured storage (for semantic search).
Graph Database
For explicit relationships and structured queries
Neo4j, Amazon Neptune
Vector Store
For semantic similarity search
Pinecone, Weaviate, Qdrant
Key insight: Use the graph for explicit relationships and structured queries. Use vectors for semantic similarity. You need both.
Update Pattern: Contradiction Resolution
What happens when new information contradicts old memories?
- • Check for contradictions against existing facts
- • If newer timestamp wins → Deprecate old fact as "superseded"
- • Track provenance → You might need to debug why the agent "changed its mind"
Don't just overwrite. Track provenance.
Layer 4: Episodic Memory
Episodic memory stores specific past interactions—memorable conversations, key decisions, important events.
What Goes Here
- • Notable conversations (not all, just significant ones)
- • Key decisions and their outcomes
- • Important events (user complained, user praised, user changed preferences)
- • Exceptions and edge cases
The Pattern: Selective Storage with Importance Scoring
You can't store every interaction. You need to decide what's worth remembering.
Importance Signals
- • User expressed strong emotion → High importance
- • Decision was made → High importance
- • User corrected the agent → High importance
- • Conversation was long/engaged → Medium importance
- • User explicitly said "remember this" → Highest importance
Key insight: Not all interactions deserve to be memories. Score importance and be selective.
Common Pitfalls (and How to Avoid Them)
Pitfall 1: Remembering Everything
Problem: Memory bloats. Retrieval slows. Irrelevant memories crowd out relevant ones.
Fix: Be aggressive about what you store. Score importance. Let things decay.
Pitfall 2: No Memory Retrieval Strategy
Problem: You store memories but retrieve poorly. Agents miss relevant context or retrieve noise.
Fix: Combine semantic search with structured queries. Use metadata filtering. Weight by recency.
Pitfall 3: Treating Memory as Append-Only
Problem: Memories become stale or wrong. Contradictions accumulate.
Fix: Build update and contradiction resolution logic. Track provenance. Allow deprecation.
Pitfall 4: One Memory Store for Everything
Problem: Different memory types have different characteristics. One-size-fits-all doesn't work.
Fix: Use the layered architecture. Different storage for different purposes.
Pitfall 5: No Memory in Prompts
Problem: You retrieve memories but don't include them effectively in prompts.
Fix: Structure memory context clearly. Separate what the agent knows from what's happening now.
Performance Considerations
Memory adds latency. Here's how to manage it:
Parallel Retrieval
Run all retrievals in parallel with asyncio.gather()
Caching
Cache user profile (changes infrequently) with TTL
Lazy Loading
Only retrieve episodic memory if the query requires historical context
Agent memory isn't a checkbox feature. It's a system design challenge.
The frameworks give you building blocks—vector stores, conversation buffers, entity extractors. But making memory work—reliably, performantly, correctly—requires architecture.
The layered approach addresses the core challenges:
- • Finite context windows (working memory with summarization)
- • Session continuity (short-term memory with decay)
- • Persistent knowledge (long-term memory with structure)
- • Relevant recall (episodic memory with importance scoring)
Build memory as a system. Test it like you test retrieval.
Your agents will thank you. Or at least, they'll remember to.
Building agents that need to remember?
Let's talkMemory architecture is one of the trickiest parts of production agent systems.
