โ† Back to Table of Contents

Chapter 6 โ€” Memory Systems

โ€œWithout memory, there is no learning. Without learning, there is no intelligence.โ€

Why Agents Need Memory

An LLM by itself is stateless โ€” each API call is independent, with no knowledge of previous calls. Agents need memory systems to:

  • Maintain conversation context across turns
  • Learn from past mistakes and successes
  • Access large knowledge bases that donโ€™t fit in the context window
  • Remember user preferences across sessions
  • Build up working state during complex multi-step tasks

The Memory Hierarchy

Agent Memory Architecture
๐Ÿง  Working Memory (Context Window) 32Kโ€“1M tokens โ€” the LLM's "RAM". Everything it can see right now.
๐Ÿ’ฌ Short-Term Memory Current conversation history. Persists within a session.
๐Ÿ“š Long-Term Memory (Vector Store) Persistent facts, past conversations, learned knowledge. Survives across sessions.
๐ŸŒ External Memory (RAG) Documents, databases, APIs โ€” retrieved on demand at query time.

Working Memory: The Context Window

The context window is the most immediate form of memory. Everything the LLM can โ€œseeโ€ right now lives here:

  • System prompt
  • Conversation history
  • Tool definitions
  • Tool call results
  • Retrieved documents

The Context Window Problem

As an agent runs, the context window fills up. A typical agent interaction:

Content Token Estimate
System prompt ~500 tokens
Tool definitions (10 tools) ~2,000 tokens
User message ~100 tokens
Per loop iteration (LLM response + tool result) ~1,000โ€“5,000 tokens
After 10 iterations ~15,000โ€“55,000 tokens

With a 128K context window, you have room. But at $2.50/M input tokens, those tokens get expensive fast.

Context Management Strategies

class ContextManager:
    def __init__(self, max_tokens: int = 100_000):
        self.max_tokens = max_tokens
        self.messages = []
    
    def add(self, message: dict):
        self.messages.append(message)
        self._trim_if_needed()
    
    def _trim_if_needed(self):
        """Sliding window: keep system prompt + recent messages."""
        while self._estimate_tokens() > self.max_tokens:
            # Never remove system prompt (index 0) or last 5 messages
            if len(self.messages) > 6:
                self.messages.pop(1)  # Remove oldest non-system message
    
    def _summarize_old_context(self):
        """Alternative: summarize old messages instead of dropping them."""
        old = self.messages[1:-5]  # Everything except system + recent
        summary = llm.summarize(old)
        self.messages = [
            self.messages[0],  # system prompt
            {"role": "system", "content": f"Previous context summary: {summary}"},
            *self.messages[-5:],  # recent messages
        ]

Short-Term Memory: Conversation History

Short-term memory is the conversation buffer โ€” the growing list of messages exchanged between the user, the agent, and tools within a single session.

Short-Term Memory Flow
๐Ÿ‘ค User: "Find quarterly revenue for Apple"
๐Ÿค– Agent: Calls web_search("Apple Q4 2025 revenue")
๐Ÿ”ง Tool result: "$94.9B in Q4 2025"
๐Ÿค– Agent: "Apple reported $94.9B in Q4 2025"
๐Ÿ‘ค User: "How does that compare to last year?"
๐Ÿค– Agent recalls the context and searches for Q4 2024 data

The agent understands โ€œthatโ€ and โ€œlast yearโ€ because the previous turns are in short-term memory.

Long-Term Memory: Vector Stores

For knowledge that persists across sessions, agents use vector databases. The pattern:

  1. Store: Convert text into embeddings and save them
  2. Retrieve: When needed, find relevant memories by semantic similarity
  3. Inject: Add retrieved memories into the context window
from openai import OpenAI
import chromadb

client = OpenAI()
db = chromadb.Client()
collection = db.create_collection("agent_memory")

def store_memory(text: str, metadata: dict = None):
    """Store a fact in long-term memory."""
    embedding = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    ).data[0].embedding
    
    collection.add(
        ids=[str(hash(text))],
        embeddings=[embedding],
        documents=[text],
        metadatas=[metadata or {}],
    )

def recall_memories(query: str, k: int = 5) -> list[str]:
    """Retrieve relevant memories."""
    embedding = client.embeddings.create(
        model="text-embedding-3-small",
        input=query
    ).data[0].embedding
    
    results = collection.query(
        query_embeddings=[embedding],
        n_results=k,
    )
    return results["documents"][0]

What to Store in Long-Term Memory

Memory Type Example Why
User preferences โ€œUser prefers Python over JavaScriptโ€ Personalization
Past task outcomes โ€œLast time I searched for X, the best source was Yโ€ Learning
Extracted facts โ€œCompany X was founded in 2019โ€ Knowledge accumulation
Conversation summaries โ€œIn our last session, we planned the API architectureโ€ Cross-session continuity
Error lessons โ€œUsing endpoint X requires auth header Yโ€ Self-improvement

Episodic vs. Semantic Memory

Inspired by human cognition, agent memory can be split into:

๐Ÿ“– Episodic Memory
  • "What happened" โ€” specific events and experiences
  • "Last time you asked about stocks, you wanted tech sector"
  • Stores conversation transcripts, task logs
  • Good for: personalization, learning from mistakes
๐Ÿง  Semantic Memory
  • "What I know" โ€” general facts and knowledge
  • "Apple is a tech company headquartered in Cupertino"
  • Stores extracted facts, documentation chunks
  • Good for: answering questions, reasoning about domains

RAG: Memory From External Sources

Retrieval-Augmented Generation lets agents access vast knowledge bases without storing everything in the context window:

RAG Pipeline
๐Ÿ“„ Documents
โœ‚๏ธ Chunk
๐Ÿ”ข Embed
๐Ÿ’พ Vector DB
๐Ÿ” Retrieve
๐Ÿง  Generate
# RAG as an agent tool
def search_knowledge_base(query: str) -> str:
    """Search the company knowledge base for relevant information."""
    # 1. Embed the query
    query_embedding = embed(query)
    
    # 2. Search the vector database
    results = vector_db.search(query_embedding, top_k=5)
    
    # 3. Format results for the LLM
    context = "\n\n".join([
        f"[Source: {r.metadata['source']}]\n{r.text}" 
        for r in results
    ])
    
    return context

Memory in Practice: The Mem0 Pattern

Mem0 popularized a pattern for agent memory that auto-extracts and stores important facts:

from mem0 import Memory

memory = Memory()

# After each conversation, auto-extract and store memories
memory.add("I prefer dark mode and vim keybindings", user_id="user_123")
memory.add("My project uses FastAPI with PostgreSQL", user_id="user_123")

# Before generating a response, retrieve relevant memories
relevant = memory.search("What framework should I use?", user_id="user_123")
# Returns: ["My project uses FastAPI with PostgreSQL"]

Memory Architecture Summary

โšก
Fast Path
Context window โ†’ immediate access, limited size, expensive per token
๐Ÿ”
Retrieval Path
Vector DB โ†’ unlimited size, semantic search, cheap storage, small latency cost
๐Ÿ“
Write Path
Auto-extract facts from conversations โ†’ embed โ†’ store in vector DB for future use
๐Ÿ—‘๏ธ
Forget Path
Summarize old context โ†’ evict from working memory โ†’ keep in long-term if important

Whatโ€™s Next

Memory gives agents knowledge. But an agent also needs to reason โ€” to plan, decompose problems, and correct its own mistakes. Thatโ€™s planning.

Next: Chapter 7 โ€” Planning & Reasoning โ†’


โ† Previous: Chapter 5 โ€” Tools & Function Calling ยท Next: Chapter 7 โ€” Planning & Reasoning โ†’

Last updated: April 2026