How Zep stores facts, entities, and user preferences — and how it differs from Mem0 and LangGraph memory
The memory problem
Most AI agents are stateless within a session and completely amnesiac across sessions. LangGraph checkpoints solve the within-session problem. But when a user returns a week later and says 'remember what we discussed about my budget?', a checkpointer cannot help — it only knows about the current thread.
Zep is a dedicated memory server that stores facts, entities, and conversation history across sessions, extracts structured knowledge from conversations automatically, and provides semantic search over that knowledge.
What Zep stores
| Memory type | What it is | Example |
|---|---|---|
| Facts | Discrete facts extracted from conversations | 'User prefers monthly billing' |
| Entities | Named entities with relationships | 'Alice' is a 'Customer' with 'Account #12345' |
| Summaries | Auto-generated summaries of past sessions | Rolling summary of last 20 exchanges |
| Raw messages | Original conversation messages | Full message history for a session |
| Custom data | Arbitrary key-value data you add manually | User's subscription tier, onboarding status |
Core data model: Users and Threads
Zep organises memory around two concepts: Users (persistent identities) and Threads (individual conversations). A user can have many threads. Memories at the user level persist forever; thread memories are associated with a specific conversation.
from zep_cloud.client import AsyncZep
import asyncio
client = AsyncZep(api_key='YOUR_ZEP_API_KEY')
async def setup_user_and_session():
# Create a user (idempotent — safe to call on every login)
await client.user.add(
user_id='user-123',
email='alice@example.com',
first_name='Alice',
metadata={'plan': 'pro', 'signup_date': '2026-01-15'}
)
# Create a thread for this conversation
await client.thread.create(
thread_id='session-abc',
user_id='user-123',
)
asyncio.run(setup_user_and_session())
Adding messages to memory
from zep_cloud.types import Message
async def add_exchange(thread_id: str, user_msg: str, ai_msg: str):
await client.thread.add_messages(
thread_id,
messages=[
Message(name='User', role='user', content=user_msg),
Message(name='Assistant', role='assistant', content=ai_msg),
]
)
# Zep automatically:
# - Extracts facts ('user wants to cancel subscription')
# - Identifies entities ('subscription', 'user')
# - Updates rolling summary
# - Builds/updates knowledge graph edges
Retrieving memory
async def get_context_for_agent(thread_id: str) -> str:
memory = await client.thread.get_user_context(thread_id=thread_id)
# memory.context is a pre-formatted string — ready to inject into system prompt
print(memory.context)
# Example output:
# Facts about Alice:
# - Prefers monthly billing
# - Account number is 12345
# - Last session: discussed Q1 budget review
# For structured facts, search the user's knowledge graph:
# edges = await client.graph.search(user_id=user_id, query='...', scope='edges')
return memory.context
Use memory.context directly as your system prompt suffix — it is already formatted for LLM injection. Do not try to reconstruct it from individual facts unless you need custom formatting.Semantic search across all memories
async def search_user_memory(user_id: str, query: str) -> list:
results = await client.graph.search(
user_id=user_id,
query=query,
scope='edges', # 'edges' (facts) or 'nodes' (entities)
limit=5,
)
for edge in results.edges:
print(f'Score: {edge.score:.3f} | {edge.fact}')
return results.edges
# Example: find memories relevant to a current query
relevant = await search_user_memory('user-123', 'budget preferences')
Zep vs Mem0 vs LangGraph Store
| Feature | Zep | Mem0 | LangGraph Store |
|---|---|---|---|
| Automatic fact extraction | Yes — from messages | Yes — from messages | No — manual put() |
| Knowledge graph | Yes — entity relationships | Yes | No |
| Self-hostable | Yes (open source) | Yes (open source) | Yes |
| Cloud managed | Yes (Zep Cloud) | Yes (Mem0 Platform) | No |
| LangChain integration | First-class | First-class | Via LangGraph |
| Semantic search | Yes | Yes | Yes (with embeddings) |
| Best for | Conversational apps, CRM enrichment | Agent memory, multi-framework | LangGraph-native apps |
When NOT to use Zep
- You are building with LangGraph and want to stay in the same ecosystem — use LangGraph Store
- You only need within-session memory — a simple list of messages is enough
- Your conversations are short and do not accumulate meaningful facts over time
- You cannot justify the extra dependency or hosting cost for your use case
Zep Cloud vs self-hosted
| Aspect | Zep Cloud | Self-hosted |
|---|---|---|
| Setup | API key only | Docker compose or Kubernetes |
| Knowledge graph | Yes | Yes |
| Data residency | US/EU regions | Fully controlled |
| Pricing | Usage-based (free tier available) | Infrastructure costs only |
| Maintenance | None | You manage updates |