Agent Memory
Memory is what makes agents stateful — it's how they remember past interactions, learn from experience, and maintain context across sessions. Without memory, every conversation starts from zero. This covers the memory types, storage patterns, and implementation strategies.
Table of Contents
Installation / Setup
pip install langchain-core langchain-community
Core Syntax
Memory types
| Type |
What it stores |
Example |
| Short-term |
Current conversation |
Chat history (messages list) |
| Long-term |
Persistent facts across sessions |
User preferences, learned facts |
| Episodic |
Past experiences |
"Last time I solved X, I used Y" |
| Semantic |
General knowledge |
Facts, concepts, relationships |
graph TD
A[Agent Memory] --> B[Short-term]
A --> C[Long-term]
B --> D[Conversation history]
B --> E[Working memory]
C --> F[User preferences]
C --> G[Facts & knowledge]
C --> H[Episodic memories]
Short-term memory (conversation history)
# simplest form: maintain a messages list
messages = [
{"role": "system", "content": "You are a helpful assistant."},
]
def chat(user_input, llm):
messages.append({"role": "user", "content": user_input})
response = llm.invoke(messages)
messages.append({"role": "assistant", "content": response.content})
return response.content
Conversation windowing (prevent context overflow)
def chat_with_window(user_input, llm, max_messages=20):
messages.append({"role": "user", "content": user_input})
# keep only the last N messages
trimmed = messages[-max_messages:]
response = llm.invoke(trimmed)
messages.append({"role": "assistant", "content": response.content})
return response.content
Summarization memory (compress old messages)
def summarize_old_messages(messages, llm, keep_last=10):
if len(messages) <= keep_last:
return messages
old = messages[:-keep_last]
recent = messages[-keep_last:]
summary = llm.invoke([
{"role": "system", "content": "Summarize this conversation in 2-3 sentences."},
{"role": "user", "content": str(old)},
]).content
return [{"role": "system", "content": f"Previous conversation summary: {summary}"}] + recent
Long-term memory (persistent)
import json
class LongTermMemory:
def __init__(self, path="memory.json"):
self.path = path
self.facts = self._load()
def _load(self):
try:
return json.load(open(self.path))
except FileNotFoundError:
return {}
def _save(self):
json.dump(self.facts, open(self.path, "w"), indent=2)
def remember(self, key, value):
self.facts[key] = value
self._save()
def recall(self, key):
return self.facts.get(key)
def search(self, query):
"""Simple keyword search over facts."""
results = {}
for k, v in self.facts.items():
if query.lower() in str(k).lower() or query.lower() in str(v).lower():
results[k] = v
return results
Common Operations
Vector-based long-term memory
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
class VectorMemory:
def __init__(self):
self.texts = []
self.vectorstore = None
def add(self, text, metadata=None):
self.texts.append({"text": text, "metadata": metadata or {}})
if self.vectorstore is None:
self.vectorstore = FAISS.from_texts([text], OpenAIEmbeddings())
else:
self.vectorstore.add_texts([text], metadatas=[metadata])
def search(self, query, k=5):
if self.vectorstore is None:
return []
return self.vectorstore.similarity_search(query, k=k)
Graph-based memory (knowledge graph)
class GraphMemory:
def __init__(self):
self.entities = {} # name -> properties
self.relations = [] # (subject, predicate, object)
def add_entity(self, name, properties=None):
self.entities[name] = properties or {}
def add_relation(self, subject, predicate, obj):
self.relations.append((subject, predicate, obj))
def query(self, entity):
"""Get all facts about an entity."""
facts = []
for s, p, o in self.relations:
if s == entity:
facts.append(f"{s} {p} {o}")
elif o == entity:
facts.append(f"{o} is {p} of {s}")
return facts
Practical Examples
Example 1 — Agent with conversation + long-term memory
class AgentWithMemory:
def __init__(self, llm):
self.llm = llm
self.conversation = []
self.long_term = LongTermMemory()
def chat(self, user_input):
# check for facts to recall
relevant_facts = self.long_term.search(user_input)
context = "\n".join(f"- {k}: {v}" for k, v in relevant_facts.items())
# build prompt with context
system = "You are a helpful assistant."
if context:
system += f"\n\nKnown facts:\n{context}"
self.conversation.append({"role": "user", "content": user_input})
messages = [{"role": "system", "content": system}] + self.conversation[-20:]
response = self.llm.invoke(messages)
self.conversation.append({"role": "assistant", "content": response.content})
# extract facts to remember
if "my name is" in user_input.lower():
name = user_input.split("my name is")[-1].strip()
self.long_term.remember("user_name", name)
return response.content
Example 2 — Episodic memory for task learning
class EpisodicMemory:
def __init__(self):
self.episodes = []
def record(self, task, action, result, success):
self.episodes.append({
"task": task, "action": action,
"result": result, "success": success,
})
def search_similar(self, task, k=3):
"""Find past episodes with similar tasks."""
scored = []
for ep in self.episodes:
# simple word overlap scoring
overlap = len(set(task.lower().split()) & set(ep["task"].lower().split()))
scored.append((overlap, ep))
scored.sort(reverse=True)
return [ep for _, ep in scored[:k]]
Example 3 — Summarization memory with LangChain
from langchain_core.messages import SystemMessage
def smart_history(messages, llm, max_tokens=4000):
"""Summarize history when it gets too long."""
# rough token estimate
total = sum(len(str(m)) // 4 for m in messages)
if total < max_tokens:
return messages
# summarize old messages
old = messages[1:-10] # skip system, keep last 10
summary = llm.invoke(
[SystemMessage(content="Summarize this conversation concisely.")] + old
).content
return [messages[0], SystemMessage(content=f"Summary: {summary}")] + messages[-10:]
Common Patterns
Pattern: memory retrieval before response
def agent_with_memory(query, llm, memory):
# retrieve relevant memories
relevant = memory.search(query, k=3)
context = "\n".join(str(m) for m in relevant)
# include in prompt
system = f"You are a helpful assistant.\nRelevant memories:\n{context}"
messages = [{"role": "system", "content": system}, {"role": "user", "content": query}]
response = llm.invoke(messages)
# store new memory
memory.add(f"User asked: {query}; I answered: {response.content}")
return response.content
Pattern: memory decay
def decay_memory(memories, decay_rate=0.9):
"""Reduce relevance of older memories."""
for i, mem in enumerate(memories):
mem["relevance"] *= decay_rate ** i
return sorted(memories, key=lambda x: x["relevance"], reverse=True)
Best Practices
- Start with conversation history — it's the most important memory type.
- Window old messages — keep last 10–20; summarize the rest.
- Persist long-term memory — save to disk/database.
- Use vector search for semantic memory retrieval.
- Extract facts automatically — don't rely on users to "tell" the agent.
- Memory should be queryable — not just append-only.
- Limit memory size — unbounded growth = slower responses.
Common Mistakes
| Mistake |
Fix |
| Storing entire conversation forever |
Window or summarize old messages |
| No persistent memory |
Save long-term facts to disk/database |
| Memory retrieval too slow |
Use vector search, not keyword scan |
| Ignoring memory relevance |
Rank by similarity, not just recency |
| Memory leaks between users |
Use separate memory stores per user |
Troubleshooting
| Symptom |
Cause / Fix |
| Context length exceeded |
Too many messages — window or summarize |
| Agent forgets user info |
No long-term memory — add persistent storage |
| Slow memory retrieval |
Use vector search; limit results |
| Memory confusion |
Separate short-term and long-term stores |
Official Documentation
Last Updated
2026-09-06 — Initial version.