GenAIHub
← Back to Technical Section

Agent Memory

Short-term, Long-term, and Persistent Memory for AI Agents

Why Agents Need Memory

LLMs are inherently stateless—each API call is independent with no memory of previous interactions. For agents to maintain context across conversations, learn from past experiences, and build relationships with users, they need explicit memory systems.

Memory enables agents to: remember user preferences, track task progress across sessions, learn from past mistakes, and provide personalized, contextual responses.

Types of Agent Memory

Working Memory (Short-term) Current conversation Context window Episodic Memory (Medium-term) Past conversations Session summaries Semantic Memory (Long-term) User preferences Learned facts

Working Memory

  • Current conversation context
  • Lives in the LLM context window
  • Lost when session ends
  • Limited by token limits

Episodic Memory

  • Past conversation summaries
  • Stored in vector DB or database
  • Retrieved when relevant
  • Enables "I remember when..."

Semantic Memory

  • User facts and preferences
  • Structured knowledge base
  • Persists indefinitely
  • "You prefer Python over Java"

Working Memory (Context Window)

The simplest form of memory is the LLM's context window itself. Messages are accumulated and passed to the model with each request.

# Simple conversation memory
from openai import OpenAI
client = OpenAI()

messages = [
    {"role": "system", "content": "You are a helpful assistant."}
]

def chat(user_message):
    messages.append({"role": "user", "content": user_message})
    
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages  # Full history passed each time
    )
    
    assistant_message = response.choices[0].message.content
    messages.append({"role": "assistant", "content": assistant_message})
    
    return assistant_message

# Conversation
chat("My name is Alex")
chat("What's my name?")  # → "Your name is Alex"
            

⚠️ Limitations: Context windows have limits (4K–128K+ tokens). Long conversations need summarization or truncation strategies.

Summarization Memory

Instead of keeping full history, periodically summarize older messages to compress context while preserving key information.

# Summarization memory pattern
from langchain.memory import ConversationSummaryMemory
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o")

memory = ConversationSummaryMemory(
    llm=llm,
    return_messages=True
)

# After many exchanges, memory.buffer contains:
# "The user Alex discussed Python best practices, asked about
#  async programming, and requested help with FastAPI routing."
            
✓ Unlimited conversation length
✓ Preserves key information
✓ Reduces token usage
✗ Loses fine details
✗ Summarization costs tokens
✗ May introduce errors

Vector-Based Memory (Episodic)

Store conversation chunks or summaries as embeddings. Retrieve relevant memories based on semantic similarity to the current context.

from langchain.memory import VectorStoreRetrieverMemory
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

# Initialize vector store for memories
embeddings = OpenAIEmbeddings()
vectorstore = Chroma(
    collection_name="agent_memory",
    embedding_function=embeddings,
    persist_directory="./memory_db"
)

memory = VectorStoreRetrieverMemory(
    retriever=vectorstore.as_retriever(search_kwargs={"k": 5})
)

# Save a memory
memory.save_context(
    {"input": "I'm working on a machine learning project"},
    {"output": "That sounds interesting! What kind of ML are you focusing on?"}
)

# Later: retrieve relevant memories
relevant = memory.load_memory_variables(
    {"input": "Help me with my ML project"}
)
# → Returns the earlier conversation about ML
            

Structured Memory (Semantic)

Store explicit facts and user preferences in a structured format (JSON, database, knowledge graph) for reliable retrieval.

# Structured user profile memory
import json

class UserMemory:
    def __init__(self, user_id):
        self.user_id = user_id
        self.profile = self._load_or_create()
    
    def _load_or_create(self):
        try:
            with open(f"memories/{self.user_id}.json") as f:
                return json.load(f)
        except FileNotFoundError:
            return {
                "name": None,
                "preferences": {},
                "facts": [],
                "past_tasks": []
            }
    
    def remember(self, key, value):
        """Store a fact or preference"""
        if key == "name":
            self.profile["name"] = value
        elif key.startswith("prefers_"):
            self.profile["preferences"][key] = value
        else:
            self.profile["facts"].append({"key": key, "value": value})
        self._save()
    
    def recall(self, key):
        """Retrieve a specific memory"""
        if key == "name":
            return self.profile["name"]
        return self.profile["preferences"].get(key)
    
    def get_context(self):
        """Generate context string for LLM"""
        ctx = []
        if self.profile["name"]:
            ctx.append(f"User's name: {self.profile['name']}")
        for k, v in self.profile["preferences"].items():
            ctx.append(f"User {k.replace('_', ' ')}: {v}")
        return "\n".join(ctx)
            

Persistent Memory with LangGraph

LangGraph provides built-in checkpointing for persistent agent state across sessions.

from langgraph.graph import StateGraph
from langgraph.checkpoint.sqlite import SqliteSaver

# Define agent state
class AgentState(TypedDict):
    messages: list
    user_preferences: dict
    task_history: list

# Create persistent checkpointer
checkpointer = SqliteSaver.from_conn_string("./agent_memory.db")

# Build graph with checkpointing
graph = StateGraph(AgentState)
# ... add nodes and edges ...
app = graph.compile(checkpointer=checkpointer)

# Each thread_id gets its own persistent memory
config = {"configurable": {"thread_id": "user_123"}}

# State automatically persists and resumes
result = app.invoke({"messages": [user_message]}, config)
            

Production Memory Architecture

Memory Type Storage Retrieval Use Case
Working In-memory (messages array) Full context Current conversation
Buffer Redis / In-memory Last N messages Recent context window
Summary Database Session summaries Compress long conversations
Episodic Vector DB (Pinecone, Chroma) Semantic search Relevant past interactions
Semantic Postgres / Knowledge Graph Structured query User facts, preferences

Best Practices

  • Layer your memory: Combine buffer + summary + vector for different time horizons
  • Scope memories: Separate user-specific, session-specific, and global memories
  • Prune regularly: Remove outdated or low-value memories to reduce noise
  • Include metadata: Timestamp, topic, importance score for better retrieval
  • Handle conflicts: When memories contradict, prefer more recent ones
  • Privacy first: Allow users to view and delete their memories
  • Test retrieval: Verify relevant memories are actually retrieved for queries

Related Topics