An agent that forgets everything between calls forces the user to repeat context on every turn, and it pays to resend that history as tokens each time. Mem0 fixes both problems by storing durable facts about a user and returning only the relevant ones on demand. The short version: pip install mem0ai, call m.add() to save a conversation as extracted facts, call m.search() to pull back the few that matter, and paste those into your system prompt before you hit the model.
Key Takeaways
- The short answer: install
mem0ai, write memories withm.add(messages, user_id=...), read them withm.search(query, user_id=...), and inject the results into your prompt. - Mem0 does not store raw transcripts. Its LLM extracts discrete facts ("prefers window seats"), deduplicates them, and updates ones that changed.
- Every memory is scoped by a
user_id, and optionally anagent_idorrun_id, so one store can serve many users without leaking context between them. - The open-source package runs fully local by default: a local Qdrant vector store on disk plus a SQLite history file. Only the extraction and embedding calls go to your LLM provider.
- Recalling three or four relevant facts instead of resending a full transcript cuts prompt tokens on long-running conversations.
What Do You Need Before You Start?
Keep the prerequisites short so you can get to running code fast.
- Python 3.9 or newer and a virtual environment.
- An OpenAI API key exported as
OPENAI_API_KEY. Mem0 uses it to extract facts and embed them; you can swap the provider later. - Optional: a running Qdrant instance if you want a shared vector store instead of the default on-disk one. You do not need it for the first run.
How Do You Add Memory to an Agent With Mem0?
Follow these steps in order. By the end you will store a memory, recall it in a later session, and feed it to a model. This section is self-contained; the code runs as written.
- Install the package.
pip install mem0ai - Set your API key. Mem0 reads it from the environment.
export OPENAI_API_KEY="sk-..." - Initialize the memory store. With no arguments,
Memory()writes to a local Qdrant store on disk and a SQLite history file, so nothing external is required yet.from mem0 import Memory m = Memory() - Write a memory from a conversation. Pass the messages and a
user_id. Mem0's LLM pulls out the durable facts and stores them; it does not keep the raw text.messages = [ {"role": "user", "content": "Hi, I'm Alex. I always fly window and I'm vegetarian."}, {"role": "assistant", "content": "Noted, Alex. Window seat and vegetarian meals."}, ] m.add(messages, user_id="alex") - Recall relevant memories later. In a brand-new session, search by
user_id. You get back only the facts that match the query, ranked by relevance.results = m.search("What are this traveler's preferences?", user_id="alex") for r in results["results"]: print(r["memory"], "-", round(r["score"], 3)) - Inject the memories into your prompt. This is the step that makes the agent behave as if it remembers. Fetch, format, and prepend before you call the model.
from openai import OpenAI client = OpenAI() def answer(user_id, question): hits = m.search(question, user_id=user_id, limit=4)["results"] facts = "\n".join(f"- {h['memory']}" for h in hits) system = f"You are a travel assistant. Known facts about the user:\n{facts}" resp = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system}, {"role": "user", "content": question}, ], ) reply = resp.choices[0].message.content # persist this exchange so the agent keeps learning m.add( [{"role": "user", "content": question}, {"role": "assistant", "content": reply}], user_id=user_id, ) return reply print(answer("alex", "Book me a flight to Lisbon and suggest a meal."))
The pattern is always the same three moves: search before the model call, prepend the results, then add the new exchange after. Everything else is configuration.
Why Extracted Facts Beat Stuffing the Whole Transcript
The naive approach to memory is to keep the entire chat history and resend it every turn. That works until the history outgrows the context window or the token bill outgrows the budget. Vector search over raw turns helps, but it retrieves whole messages full of filler, and it never reconciles a fact that changed.
Mem0 sits between those options. It runs an extraction pass that turns messy dialogue into short, deduplicated statements, and it updates a fact in place when the user contradicts an earlier one.
| Approach | What gets stored | Token cost at turn 50 | Handles changed facts |
|---|---|---|---|
| Full transcript in context | Every message, verbatim | Grows every turn, unbounded | No, old and new both linger |
| Vector RAG over raw turns | Embedded message chunks | Moderate, retrieves full messages | No, returns both versions |
| Mem0 extracted facts | Deduplicated fact statements | Low, a handful of short lines | Yes, updates the stored fact |
That reconciliation step is the part people underestimate. If a user says "actually I moved to Berlin," you want the agent to overwrite "lives in Munich," not carry both and pick one at random.
How Do You Scope Memory Per User and Per Agent?
Every call accepts identifiers that partition the store. Use user_id for the person, agent_id when several distinct agents share one backend, and run_id for a single session's working memory.
m.add(messages, user_id="alex", agent_id="travel-bot")
m.search("dietary needs", user_id="alex", agent_id="travel-bot")
m.get_all(user_id="alex") # everything you hold for this user
m.delete_all(user_id="alex") # honor a delete request
Scoping is not optional in a real deployment. Without a user_id, one person's preferences bleed into another's answers.
How Do You Point Mem0 at a Shared Vector Store?
The on-disk default is fine for a prototype. For anything multi-process or production-bound, pass a config dict to Memory.from_config() so every worker reads and writes the same store. Out of the box Mem0 embeds with text-embedding-3-small at 1536 dimensions, so keep that in mind when you size a collection.
from mem0 import Memory
config = {
"llm": {
"provider": "openai",
"config": {"model": "gpt-4o-mini", "temperature": 0.1},
},
"vector_store": {
"provider": "qdrant",
"config": {"host": "localhost", "port": 6333},
},
}
m = Memory.from_config(config)
The llm block controls the model that does fact extraction, which is a separate choice from the model that writes your user-facing replies. A cheaper model for extraction keeps per-turn cost down without touching answer quality.
Troubleshooting Common Mem0 Errors
- Search returns nothing right after add(). Extraction is an LLM call and takes a moment; more often the query and stored facts share no meaning. Broaden the query, or call
m.get_all(user_id=...)to confirm the fact actually landed. - AuthenticationError on the first call. The default setup needs a valid
OPENAI_API_KEYin the same shell that runs Python. Export it before launching, or load it withpython-dotenv. - Facts pile up as near-duplicates. Feed both sides of the exchange, not just the user line. The assistant turn gives the extractor the context it needs to merge related statements instead of storing each phrasing separately.
- Qdrant connection refused. The host and port in your config must match a running instance. With the default (no config), Mem0 uses a local on-disk store and needs no server at all.
- Old preferences still surface. Extraction updates facts on contradiction, but only if the new statement is explicit. Prefer "I no longer eat meat" over an oblique aside, or delete the stale memory by id.
What Should You Do Next?
Once the loop works, the useful extensions are cheap to add.
- Swap the extraction and embedding providers in the config dict to run the whole layer on a local model. Pair it with a local stack from building a stateful agent with LangGraph.
- Decide what belongs in memory versus what belongs in the live prompt. The trade-offs are laid out in AI agent memory in production and context engineering for long-horizon agents.
- Measure the token savings. Recalling four facts instead of a 50-turn transcript is one of the levers covered in cutting LLM API costs.
- Read the primary docs for the full API surface, including async clients and metadata filters, at the Mem0 open-source quickstart.