Introduction to Agent Memory with Mem0
Building AI agents that can hold meaningful, ongoing conversations requires more than just a powerful language model. Without memory, every interaction starts from scratch, and the agent forgets everything it learned about the user, their preferences, and the context of previous exchanges. Mem0 is an open-source memory layer designed specifically for AI applications, enabling agents to store, retrieve, and manage contextual information across sessions.
Mem0 acts as an intelligent memory management system that sits between your application and your LLM. It automatically extracts salient facts from conversations, stores them efficiently, and surfaces relevant memories when needed. This tutorial walks through everything you need to know to implement agent memory with Mem0, from basic setup to advanced production patterns.
Why Memory Matters for AI Agents
Traditional LLM applications suffer from several limitations that memory directly addresses:
- Context window limits: Even models with large context windows cannot realistically hold an entire user history, and stuffing everything into the prompt is expensive and noisy.
- Statelessness: Each API call is independent. Without external memory, the agent has no way to recall prior interactions.
- Personalization: Users expect agents to remember their preferences, past decisions, and ongoing projects.
- Cost efficiency: Retrieving only relevant memories is far cheaper than resending full conversation histories.
- Multi-session continuity: Real applications span days, weeks, or months. Memory bridges the gap between disconnected sessions.
Mem0 solves these problems by extracting structured facts from conversations, storing them in a vector database, and retrieving the most relevant ones based on the current query. The result is an agent that feels attentive, personalized, and contextually aware.
Installation and Setup
Mem0 is distributed as a Python package. You can install it via pip along with the dependencies you need. The package supports multiple vector stores, LLM providers, and embedding models.
Basic Installation
pip install mem0ai
For most local development workflows, Mem0 ships with sensible defaults: it uses OpenAI for both the LLM and embeddings, and stores vectors in an in-memory or local Qdrant instance. To use OpenAI, you need an API key:
import os
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"
Initializing the Memory Client
Mem0 provides a simple Memory class that handles all operations. The simplest initialization uses default configuration:
from mem0 import Memory
# Initialize with default configuration
m = Memory()
For production use, you will want to configure the underlying vector store, LLM, and embedder explicitly. Here is an example using Qdrant as the vector store:
from mem0 import Memory
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333,
"collection_name": "agent_memories"
}
},
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4o-mini",
"temperature": 0.1
}
},
"embedder": {
"provider": "openai",
"config": {
"model": "text-embedding-3-small"
}
}
}
m = Memory.from_config(config)
Adding Memories
The core operation in Mem0 is adding memories. When you add a message, Mem0 uses an LLM to extract relevant facts, deduplicates against existing memories, and updates the store intelligently. This means you do not need to manually decide what is worth remembering — Mem0 handles that for you.
Basic Memory Addition
from mem0 import Memory
m = Memory()
# Add a memory from a user message
m.add(
messages="I prefer Python over JavaScript for backend development.",
user_id="alice"
)
# Add memories from a conversation
m.add(
messages=[
{"role": "user", "content": "I'm working on a fintech startup focused on small business lending."},
{"role": "assistant", "content": "That sounds exciting! Small business lending is a growing space."}
],
user_id="alice"
)
Notice the user_id parameter. Mem0 uses metadata to scope memories to specific users, sessions, or agents. This is essential for multi-tenant applications where you must keep each user's memories isolated.
Working with Metadata
You can attach additional metadata to memories to enable more granular filtering during retrieval:
m.add(
messages="The user completed the onboarding flow and selected the Pro plan.",
user_id="alice",
metadata={
"session_id": "session_123",
"agent_id": "support_bot",
"category": "onboarding"
}
)
Retrieving Memories
Retrieval is where Mem0 shines. Rather than returning all memories for a user, it performs semantic search to find only the memories relevant to the current query or conversation context.
Search Memories
# Search for memories relevant to a query
results = m.search(
query="What programming languages does Alice like?",
user_id="alice"
)
for result in results:
print(f"Memory: {result['memory']}")
print(f"Score: {result.get('score', 'N/A')}")
print(f"Created: {result.get('created_at', 'N/A')}")
print("---")
The search method returns a list of memory objects, each containing the extracted fact, a relevance score, timestamps, and any associated metadata. You can then inject these memories into your agent's prompt.
Retrieving All Memories for a User
# Get all memories for a user
all_memories = m.get_all(user_id="alice")
for memory in all_memories:
print(memory["memory"])
Filtering by Metadata
# Retrieve memories filtered by metadata
results = m.search(
query="onboarding status",
user_id="alice",
filters={
"category": "onboarding"
}
)
Integrating Memory with an AI Agent
Now let's build a complete agent that uses Mem0 for memory. The pattern is straightforward: before generating a response, retrieve relevant memories and include them in the system prompt. After the conversation, add the new exchange to memory.
A Complete Memory-Enabled Agent
import os
from openai import OpenAI
from mem0 import Memory
# Initialize clients
llm_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
memory = Memory()
SYSTEM_PROMPT = """You are a helpful, personalized AI assistant.
You have access to memories about the user that you should use to
provide contextually relevant responses.
Relevant memories:
{memories}
"""
def chat(user_id: str, user_message: str) -> str:
# Step 1: Retrieve relevant memories
memories = memory.search(query=user_message, user_id=user_id)
memory_text = "\n".join([f"- {m['memory']}" for m in memories])
# Step 2: Build the system prompt with memories
system_prompt = SYSTEM_PROMPT.format(memories=memory_text or "No relevant memories found.")
# Step 3: Generate a response
response = llm_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
)
assistant_message = response.choices[0].message.content
# Step 4: Store the conversation in memory
memory.add(
messages=[
{"role": "user", "content": user_message},
{"role": "assistant", "content": assistant_message}
],
user_id=user_id
)
return assistant_message
# Example usage
print(chat("alice", "Hi, I'm Alice and I love hiking on weekends."))
print(chat("alice", "Can you recommend a good weekend activity for me?"))
In the second call, the agent will reference Alice's love of hiking because Mem0 retrieved that memory and included it in the prompt. This is the fundamental pattern for building memory-enabled agents.
Updating and Deleting Memories
Memories are not static. Users change their preferences, correct mistakes, and provide new information that supersedes old facts. Mem0 handles much of this automatically during the add operation, but you can also manage memories manually.
Updating a Memory
# First, find the memory you want to update
memories = m.get_all(user_id="alice")
target = memories[0]
# Update the memory content
m.update(
memory_id=target["id"],
text="Alice now prefers Rust over Python for systems programming."
)
Deleting a Memory
# Delete a specific memory
m.delete(memory_id=target["id"])
# Delete all memories for a user
m.delete_all(user_id="alice")
Viewing Memory History
Mem0 tracks the history of each memory, including when it was created, updated, and what previous versions looked like. This is valuable for debugging and auditing:
history = m.history(memory_id=target["id"])
for entry in history:
print(f"Event: {entry['event']}, Time: {entry['created_at']}")
if entry.get("old_memory"):
print(f" Old: {entry['old_memory']}")
if entry.get("new_memory"):
print(f" New: {entry['new_memory']}")
Using Mem0 with Different Providers
One of Mem0's strengths is its provider-agnostic architecture. You can swap out the LLM, embedding model, and vector store without changing your application code.
Using Anthropic Claude as the LLM
config = {
"llm": {
"provider": "anthropic",
"config": {
"model": "claude-3-5-sonnet-20241022",
"temperature": 0.1
}
},
"embedder": {
"provider": "openai",
"config": {
"model": "text-embedding-3-small"
}
},
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
}
}
m = Memory.from_config(config)
Using Ollama for Local Models
config = {
"llm": {
"provider": "ollama",
"config": {
"model": "llama3.1:8b",
"ollama_base_url": "http://localhost:11434"
}
},
"embedder": {
"provider": "ollama",
"config": {
"model": "nomic-embed-text"
}
},
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
}
}
m = Memory.from_config(config)
Supported Vector Stores
Mem0 supports several vector stores out of the box, including Qdrant, Chroma, Pinecone, Weaviate, pgvector, and Redis. Switching between them is a matter of changing the configuration block. For example, to use Pinecone:
config = {
"vector_store": {
"provider": "pinecone",
"config": {
"api_key": "your-pinecone-api-key",
"environment": "us-east-1-aws",
"index_name": "agent-memories"
}
}
}
m = Memory.from_config(config)
Using Mem0 with Popular Agent Frameworks
Mem0 integrates with several popular agent frameworks, including LangChain, CrewAI, and AutoGen. This allows you to add memory to existing agent architectures with minimal code changes.
Integration with CrewAI
from crewai import Agent, Task, Crew
from mem0 import Memory
# Initialize Mem0
memory = Memory()
# Create an agent
support_agent = Agent(
role="Customer Support Agent",
goal="Provide personalized support based on user history",
backstory="You are an experienced support agent who remembers
every detail about your customers.",
verbose=True
)
# Create a task that uses memory
def memory_enhanced_task(user_id: str, query: str):
# Retrieve memories
memories = memory.search(query=query, user_id=user_id)
memory_context = "\n".join([m["memory"] for m in memories])
task = Task(
description=f"""
User query: {query}
Known information about this user:
{memory_context}
Provide a personalized response.
""",
agent=support_agent,
expected_output="A personalized support response"
)
crew = Crew(agents=[support_agent], tasks=[task])
result = crew.kickoff()
# Store the interaction
memory.add(
messages=f"User asked: {query}. Agent responded: {result}",
user_id=user_id
)
return result
Best Practices
1. Always Scope Memories with User IDs
Never store memories without a user_id or equivalent scoping metadata. In multi-tenant applications, unscoped memories can leak between users, creating both poor experiences and serious privacy issues.
2. Be Selective About What You Store
Mem0's extraction LLM is good at identifying salient facts, but you can improve quality by being thoughtful about what conversations you feed it. Avoid dumping raw logs or irrelevant chatter into add(). Instead, add meaningful exchanges:
# Good: meaningful exchange
m.add(
messages=[
{"role": "user", "content": "I just got promoted to Engineering Manager!"},
{"role": "assistant", "content": "Congratulations! That's a big step in your career."}
],
user_id="alice"
)
# Avoid: trivial exchange with no lasting value
# m.add(messages="User said 'ok'", user_id="alice") # Don't do this
3. Use Custom Prompts for Domain-Specific Extraction
Mem0 allows you to customize the extraction prompt to better suit your domain. This is particularly useful for specialized applications like healthcare, legal, or financial agents:
config = {
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4o-mini"
}
},
"custom_prompt": """
Extract key facts from the conversation that would be useful for
a financial advisory agent. Focus on:
- Income and financial goals
- Risk tolerance
- Investment preferences
- Important life events affecting finances
Ignore casual greetings and small talk.
"""
}
m = Memory.from_config(config)
4. Implement Memory Cleanup Strategies
Over time, memories accumulate. Implement periodic cleanup to remove outdated or irrelevant memories. You can use the history feature to identify stale entries or implement a TTL-based approach:
from datetime import datetime, timedelta
# Example: Clean up memories older than 90 days
cutoff = datetime.now() - timedelta(days=90)
all_memories = m.get_all(user_id="alice")
for memory in all_memories:
created = datetime.fromisoformat(memory["created_at"].replace("Z", "+00:00"))
if created.replace(tzinfo=None) < cutoff:
m.delete(memory_id=memory["id"])
print(f"Deleted stale memory: {memory['memory']}")
5. Monitor and Log Memory Operations
In production, log memory additions and retrievals to understand what your agent is remembering and whether retrieval quality meets expectations. This helps you tune search parameters and identify when the extraction LLM is missing important facts.
6. Choose the Right Vector Store for Your Scale
For development and small projects, the default in-memory store or local Qdrant is fine. For production at scale, consider managed solutions like Pinecone or Qdrant Cloud, which handle replication, sharding, and high availability automatically.
7. Test Memory Retrieval Quality
Build a test suite that verifies your agent retrieves the right memories for common queries. This is especially important as your user base grows and the memory store becomes larger and noisier:
def test_memory_retrieval():
m = Memory()
# Seed memories
m.add(messages="I am allergic to peanuts.", user_id="test_user")
m.add(messages="I love Italian food.", user_id="test_user")
# Test retrieval
results = m.search(query="What foods should I avoid?", user_id="test_user")
memories = [r["memory"] for r in results]
assert any("peanut" in m.lower() for m in memories), \
"Should retrieve peanut allergy memory"
print("Memory retrieval test passed!")
test_memory_retrieval()
Using Mem0 Cloud for Managed Memory
If you do not want to manage infrastructure, Mem0 offers a hosted cloud version. This handles vector storage, embedding, and extraction as a service, which simplifies deployment significantly:
from mem0 import MemoryClient
# Initialize with your API key
client = MemoryClient(api_key="your-mem0-cloud-api-key")
# Add a memory
client.add(
messages="I prefer dark mode for all applications.",
user_id="alice"
)
# Search memories
results = client.search(
query="What are Alice's UI preferences?",
user_id="alice"
)
for result in results:
print(result["memory"])
The cloud API mirrors the local API closely, so you can start with the local version during development and migrate to the cloud for production with minimal code changes.
Conclusion
Memory is the bridge between stateless language models and truly intelligent, persistent agents. Mem0 provides a clean, flexible, and production-ready memory layer that handles the hard parts — fact extraction, deduplication, semantic retrieval, and multi-provider support — so you can focus on building great agent experiences. By following the patterns and best practices in this tutorial, you can build agents that remember user preferences, maintain context across sessions, and deliver personalized interactions at scale. Start with the default configuration to prototype quickly, then tune your vector store, LLM provider, and extraction prompts as your application grows. With Mem0 handling memory management, your agents can finally stop forgetting and start truly learning about the people they serve.