Mem0 setup and configuration
Install and configure Mem0 with a chosen extractor model, embedder and vector store, then use it to add, search, inspect and delete memories for a Claude agent.
Words in this lessonMem0 · Memory extraction · Memory scope · Long-term memory · Embedding · Vector database
- Mem0
- An open-source memory layer that extracts facts from conversations and stores them for an agent to search later. Like a clerk who sits in on every meeting, writes down what matters on index cards and files them under the right name.
- Memory extraction
- Using an LLM to turn a conversation into a few short, self-contained facts worth remembering. Like turning a long meeting into three clear bullet points in the minutes.
- Memory scope
- The label that says whose memory something is, such as a particular user, agent or session. Like the name on the front of a file folder: it decides which drawer the folder lives in and who can pull it.
- Long-term memory
- Information an agent keeps outside the model, between conversations, and brings back when it is relevant. Like a filing cabinet behind the counter: the assistant pulls out the right card when a regular customer walks in.
- Embedding
- A list of numbers that captures what a piece of text means, so similar meanings get similar numbers. Like a GPS coordinate for meaning: "cashew allergy" and "contains tree nuts" land close together on the map.
- Vector database
- A database built to store huge numbers of vectors and quickly find the ones closest to a given vector. Like a librarian who shelves books by topic so well she can hand you the five most related books in a second.
On this page
- The clerk who listens and files
- Following one conversation through Mem0
- Opening the hood: what Mem0 is made of
- The real code, step by step
- Step 1: install and set keys
- Step 2: configure it properly
- Step 3: add and search
- Step 4: wire it into the agent
- Step 5: look, fix and forget
- Where it broke for Arjun
- Try it yourself
- Common mistakes
- Recap
- Go deeper
Arjun’s dictionary of customer facts had worked beautifully for ten customers. Crumb had 4,100. He was not going to type them in by hand.
So on Monday he did what a backend developer does: he wrote a parser. If a customer says “I’m allergic to X”, store “Allergic to X”. He was rather pleased with the regular expression.
import re
ALLERGY = re.compile(r"allergic to (\w+)", re.IGNORECASE)
def extract_facts(message: str) -> list[str]:
return [f"Allergic to {m}" for m in ALLERGY.findall(message)]
Priya handed him twenty real messages from the support inbox and asked him to run it. It caught 12 of the 20 allergy mentions. It missed “I can’t have groundnuts”, “no nuts please, doctor’s orders” and “peanuts make my throat itch”. Worse, on “my son is allergic to cashews” it confidently stored “Allergic to cashews” for the mother. And it had nothing at all to say about delivery preferences, addresses, birthdays or the dozen other things people mention in passing.
“You are trying to write rules for language,” Priya said. “That is the one job language models are actually good at.”
Important
The idea in one sentence: Mem0 is a memory layer that uses a small LLM to pull short, self-contained facts out of conversations, stores them as embeddings in a vector database tagged with who they belong to, and lets your agent search them by meaning before it replies.
The clerk who listens and files
Priya used the image from last week. “You have a desk (the context window) and a filing cabinet (long-term storage). What you are missing is a clerk. Someone who sits in on every conversation with a notebook, writes down the few things worth remembering in clean, complete sentences, checks the cabinet so they do not file the same card twice, and files each card under the right customer’s name.”
“And when a customer writes in?”
“The clerk hears the question, pulls the three or four most relevant cards, and slides them onto the desk before you answer.”
That clerk is what Mem0 (pronounced “mem-zero”) does. It is an open-source Python library, with an optional hosted service, that gives any agent long-term memory with two main calls:
memory.add(conversation, user_id=...): listen and file.memory.search(question, filters={"user_id": ...}): find the relevant cards.
Under the hood it combines three things you already know: an LLM (to read and extract), an embedding model (to turn each memory into a list of numbers that captures its meaning, Module 07), and a vector database (to store those numbers and find the closest ones, Module 08).
The difference from Arjun’s regex is that the extractor understands the sentence. “I can’t have groundnuts” and “peanuts make my throat itch” both mean the same thing to an LLM, and “my son is allergic to cashews” is clearly about the son.
Following one conversation through Mem0
Priya picked a real conversation and traced exactly what Mem0 would do with it, step by step. Here is what Sunita said one Tuesday:
Sunita: Hi, can I change my delivery slot to after 6 pm? I’m only home in the evenings on weekdays. Bot: Done, your order A1001 will arrive after 6 pm. Sunita: Thanks. Also, my son Aarav can’t have groundnuts either, so please no chikki in anything.
They called memory.add(...) with those three messages and user_id="sunita". Mem0 then did six things.
Step 1. Look in the cabinet first. Mem0 embeds the new messages and searches Sunita’s existing memories for the ten most similar. Sunita already had one memory: “User is allergic to peanuts.” That comes back. Mem0 relabels it with a short ID, "0", instead of its long UUID (a random 36-character identifier). Small IDs are easier for an LLM to copy correctly, which avoids a whole class of mistakes.
Step 2. One LLM call to extract. Mem0 sends a carefully written extraction prompt to its LLM: here are the new messages, here are the existing memories, write down every new fact worth remembering as a self-contained sentence, and do not repeat what is already known. The model returns JSON:
{"memory": [
{"id": "0", "text": "User prefers deliveries after 6 pm on weekdays because they are only home in the evenings"},
{"id": "1", "text": "User's son Aarav cannot eat groundnuts (peanuts), so the user wants no chikki in their orders"}
]}
Look at what the model did that the regex could not. It turned “after 6 pm, I’m only home in the evenings” into a clean preference with its reason. It understood “groundnuts” means peanuts. It attributed the allergy to Aarav, not to Sunita. And it did not re-extract “User is allergic to peanuts”, because that card already exists.
Step 3. Embed the new memories. Each sentence becomes a vector. With OpenAI’s text-embedding-3-small, that is a list of 1,536 numbers per memory, something like [0.012, -0.044, 0.031, ...].
Step 4. Skip exact duplicates. Mem0 computes a short fingerprint (an MD5 hash) of each sentence and drops any that exactly match an existing one.
Step 5. Store. Each memory goes into the vector database with metadata: user_id: "sunita", a created_at timestamp, the hash, and the text itself.
Step 6. Keep a history. A small SQLite database (a single-file database) records “memory X was added at time T”, so you can audit what the agent learned and when.
What did it cost? The extraction call sent about 1,400 tokens (the prompt is long and careful) and got back about 90. With a small, cheap model like the default gpt-5-mini, that is a fraction of a rupee, roughly ₹0.05 to ₹0.10 at the time of writing. The two embeddings cost almost nothing. The bigger cost is time: the extraction call took about two seconds.
Then a week later, Sunita asked: “What sweets can I order for Aarav’s birthday?” The agent called memory.search with that question, filtered to Sunita. Here is what came back (your exact scores will differ):
| Score | Memory |
|---|---|
| 0.58 | User’s son Aarav cannot eat groundnuts (peanuts), so the user wants no chikki in their orders |
| 0.41 | User is allergic to peanuts |
| 0.19 | User prefers deliveries after 6 pm on weekdays because they are only home in the evenings |
The agent put the top results into its system prompt, and Claude replied with peanut-free sweets for Aarav and offered an evening delivery slot. Nobody typed a single fact by hand.
Note
Pause and think. In step 2, the extractor saw the existing memory “User is allergic to peanuts”. Why does Mem0 bother to search existing memories before extracting, instead of just extracting and storing everything?
Show the answer
Without it, every conversation that mentions the allergy would add another near-identical card: “User is allergic to peanuts”, “User has a peanut allergy”, “User cannot eat peanuts”. The cabinet fills with duplicates, searches return five versions of one fact and crowd out everything else. Showing the extractor what is already known lets it add only what is new. The hash check in step 4 only catches exact duplicates; the LLM catches the reworded ones.
Opening the hood: what Mem0 is made of
flowchart LR
C["Conversation"] --> X["Extractor LLM"]
S[("Vector store")] --> X
X --> E["Embedder"]
E --> S
X --> H[("History DB")]
Q["New question"] --> E2["Embedder"]
E2 --> S
S --> R["Top memories"]
R --> A["Your agent's prompt"]
Each box is configurable, and that is what “Mem0 setup and configuration” really means:
- LLM (the extractor). Default: OpenAI’s
gpt-5-mini. You can swap in other providers such as Ollama for a fully local setup. This model does not talk to your customers, so a small, cheap one is usually fine. - Embedder. Default: OpenAI
text-embedding-3-small. Whatever you choose, keep it fixed: vectors from two different embedding models cannot be compared. - Vector store. Default: a local Qdrant database. Mem0 supports many others, including Chroma, pgvector, Pinecone and Milvus, all from Module 08.
- History database. A SQLite file, by default in a
.mem0folder in your home directory. - Scopes. Every memory belongs to a
user_id, anagent_id, arun_id(one session or task), or a combination. These scopes are how you keep Sunita’s memories away from Rohan’s.
Note
Pause and think. Crumb’s support bot uses Claude (an expensive, careful model) to talk to customers. Why might Arjun configure Mem0 to use a different, cheaper model for extraction?
Show the answer
Extraction runs on every conversation, often several times, and its job is narrow: turn messages into short sentences. A small model does that well at a fraction of the cost and latency. Save the expensive model for the part customers see. This “big model talks, small model does the paperwork” split is common in production agents.
The real code, step by step
Step 1: install and set keys
uv add mem0ai chromadb anthropic python-dotenv
(With pip, that is pip install mem0ai chromadb anthropic python-dotenv.)
Mem0’s default extractor and embedder use OpenAI, and Crumb’s agent uses Claude, so put both keys in .env:
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
MEM0_TELEMETRY=False
That last line turns off Mem0’s anonymous usage statistics, which are on by default. It is a good habit to check what any library sends home, especially one that handles customer data.
Step 2: configure it properly
You can write Memory() with no arguments and it will work, but the default local vector store lives in /tmp/qdrant, and many operating systems wipe /tmp on restart. Arjun learned this when Sunita’s memories vanished after a laptop reboot. Configure real paths from day one:
import os
from dotenv import load_dotenv
from mem0 import Memory
load_dotenv()
config = {
# The extractor: a small, cheap model. Model names change; see the provider's models page.
"llm": {
"provider": "openai",
"config": {"model": os.getenv("OPENAI_MODEL", "gpt-5-mini")},
},
# The embedder: pick one and never change it for this store.
"embedder": {
"provider": "openai",
"config": {"model": "text-embedding-3-small"},
},
# The filing cabinet: a Chroma database in a folder you control.
"vector_store": {
"provider": "chroma",
"config": {"collection_name": "crumb_memories", "path": "./memory_db"},
},
# The audit trail.
"history_db_path": "./memory_db/history.db",
# House rules for the clerk.
"custom_instructions": (
"Remember food allergies and dietary needs (and whose they are), "
"delivery preferences, addresses, family names and special occasions. "
"Never store payment details, card numbers, OTPs or passwords."
),
}
memory = Memory.from_config(config)
The custom_instructions line is your first taste of a write policy: rules about what is worth remembering. Lesson 3 builds this out properly.
Step 3: add and search
conversation = [
{"role": "user", "content": "Can I change my delivery slot to after 6 pm? I'm only home in the evenings on weekdays."},
{"role": "assistant", "content": "Done, your order A1001 will arrive after 6 pm."},
{"role": "user", "content": "Thanks. Also, my son Aarav can't have groundnuts, so please no chikki in anything."},
]
result = memory.add(conversation, user_id="sunita")
for item in result["results"]:
print(item["event"], "->", item["memory"])
hits = memory.search(
"What sweets can I order for Aarav's birthday?",
filters={"user_id": "sunita"},
top_k=3,
)
for hit in hits["results"]:
print(round(hit["score"], 2), hit["memory"])
You should see something like:
ADD -> User prefers deliveries after 6 pm on weekdays because they are only home in the evenings
ADD -> User's son Aarav cannot eat groundnuts (peanuts), so the user wants no chikki in their orders
0.58 User's son Aarav cannot eat groundnuts (peanuts), so the user wants no chikki in their orders
0.19 User prefers deliveries after 6 pm on weekdays because they are only home in the evenings
Notice the asymmetry: add takes user_id= directly, but search takes it inside filters=. That is deliberate in current Mem0 versions, and it is exactly where Arjun tripped (more below).
Step 4: wire it into the agent
Now the loop from Lesson 1 becomes real: retrieve, inject, reply, write.
import anthropic
client = anthropic.Anthropic()
RULES = (
"You are Crumb's friendly support assistant. "
"For allergy questions never guess; check and ask if unsure."
)
def reply(user_id: str, message: str) -> str:
# 1. Retrieve: the few memories most relevant to this message.
hits = memory.search(message, filters={"user_id": user_id}, top_k=5)["results"]
known = "\n".join(f"- {h['memory']}" for h in hits) or "- Nothing yet."
# 2. Inject them into the system prompt, clearly labelled.
system = f"{RULES}\n\nWhat you remember about this customer:\n{known}"
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=600,
system=system,
messages=[{"role": "user", "content": message}],
)
answer = next(b.text for b in response.content if b.type == "text")
# 3. Write: let Mem0 extract anything new from this exchange.
memory.add(
[{"role": "user", "content": message}, {"role": "assistant", "content": answer}],
user_id=user_id,
)
return answer
print(reply("sunita", "What sweets can I order for Aarav's birthday?"))
For brevity this sends only the latest message. In a real bot you would combine it with the short-term window from Lesson 1.
Step 5: look, fix and forget
A memory system you cannot inspect is a memory system you cannot trust. Mem0 gives you the tools:
everything = memory.get_all(filters={"user_id": "sunita"})["results"]
for m in everything:
print(m["id"], m["memory"])
memory.update(everything[0]["id"], text="User prefers deliveries after 7 pm on weekdays")
print(memory.history(everything[0]["id"])) # what changed, and when
memory.delete(everything[0]["id"]) # forget one memory
memory.delete_all(user_id="sunita") # forget a person entirely
You can also give a memory an expiry date when you add it, for things that are only true for a while, such as memory.add(conversation, user_id="sunita", expiration_date="2026-12-31"). Expired memories stop appearing in searches.
If you would rather not run any of this yourself, Mem0 also offers a hosted platform: from mem0 import MemoryClient, with a MEM0_API_KEY. The ideas are the same; you trade control for convenience, and your customers’ data now lives with a third party, which is a decision for Meera as much as for Arjun.
Library APIs change quickly. If a name has moved, check the docs linked below.
Where it broke for Arjun
The copied blog post. Arjun’s first version used memory.search(query, user_id="sunita"), copied from an older tutorial. Current Mem0 rejects that with a ValueError and asks for filters={"user_id": ...}. Older guides use the old form; trust the docs for the version you installed.
The two-second pause. Calling memory.add before replying made every answer two seconds slower. Customers do not need to wait for the clerk to finish filing. The fix: reply first, then add (as in the code above), or hand the add call to a background task or queue. Search must stay in the hot path; adding usually should not.
Contradictions pile up. Mem0’s current extractor adds new facts and skips duplicates, but it does not reliably delete the old version of a changed fact. (Earlier Mem0 versions asked the LLM to choose between add, update and delete for every fact, which cost an extra model call per conversation; newer versions simplified this to adding.) When Rohan moved from Baner to Kothrud, both addresses were stored. A search for “delivery address” returned both, ranked by similarity, not by date. For facts that change, you need a policy: update the old memory by ID, or attach timestamps and prefer the newest. Lesson 3 builds this.
The clerk can mishear. One customer said “I used to be vegan.” The extractor stored “User is vegan.” LLM extraction is very good, not perfect. Spot-check what gets stored, especially for safety-critical facts like allergies, and never let a memory override a hard check (an allergy question should still consult the real ingredient list).
Warning
Memory makes mistakes sticky. A wrong answer in one chat disappears when the chat ends. A wrong memory is repeated in every future chat until someone finds and deletes it. Log every memory write, review a sample each week, and give support staff a way to correct them.
Try it yourself
Set up Mem0 with the config above and add these three separate conversations for user_id="rohan":
- “I just moved to Kothrud, please update my address.”
- “Can you make my brownies extra gooey? I love them that way.”
- “My card ending 4421 was charged twice, can you check?”
Then call memory.get_all(filters={"user_id": "rohan"}). Before you look, predict: which of these should be stored, and which should not?
Show the answer
You should see the new Kothrud address and the gooey-brownie preference. The card number should not be stored, thanks to the custom_instructions rule. If it was stored, your instructions were not followed: delete it, tighten the wording, and remember that instructions to an LLM are a soft guard. Lesson 3 adds a hard guard, a check in your own code that rejects anything that looks like a card number before it is saved. The double-charge is an episode; storing “User reported a double charge on (date)” without the card digits would be reasonable.
Common mistakes
- Leaving the default
/tmpvector store. It can vanish on restart. Configure a real path or a real database. - Passing
user_idtosearchdirectly. Current versions wantfilters={"user_id": ...}. - Adding memories in the hot path. Reply first; file afterwards.
- Changing the embedding model on an existing store. Old and new vectors are not comparable. Re-embed everything or start a new collection.
- Trusting extraction blindly. Read a sample of stored memories every week, especially early on.
- Forgetting scopes. A memory added without the right
user_idbelongs to nobody, or worse, to everybody.
Recap
- Writing extraction rules by hand fails because people say the same thing in endless ways. An LLM extractor understands meaning and who a fact is about.
- Mem0 is a memory layer: an extractor LLM, an embedder and a vector store, with a history log, scoped by
user_id,agent_idorrun_id. addsearches existing memories, extracts new self-contained facts, embeds them and stores them.searchfinds the most relevant memories by meaning.- Configure it deliberately: a cheap extractor, a fixed embedder, a real storage path, and house rules about what to remember.
- Search in the hot path, add in the background, and keep a way to inspect, correct and delete.
That evening Arjun showed Meera the get_all output for Sunita. “It wrote all of these itself. It even knew ‘groundnuts’ means peanuts, and that it’s her son who is allergic, not her.”
Meera read the list slowly. “And if it writes down something it shouldn’t? A card number, or something private about her family?” Arjun opened his mouth to say “the instructions forbid it”, then closed it again. He had just watched an LLM misread “used to be vegan”. In the next lesson, he builds the memory store himself on a vector database, with real rules for what gets written, what gets read, what gets forgotten, and who is allowed to see what.
Go deeper
- Mem0 documentation, including the configuration reference for LLMs, embedders and vector stores.
- Chroma documentation, the vector store used in this lesson’s config.
- OpenAI models and pricing, to pick and price the extractor model.
- Anthropic documentation, for the Claude side of the agent.