Your agent is an amnesiac. Every conversation starts from zero: the user tells it they're vegetarian, it helps, the session ends, and next week it cheerfully suggests a steakhouse. The fix isn't a bigger context window — it's a place to keep facts between sessions and a way to pull back the few that matter. Mem0 is the most-installed open-source library for exactly that, and you can wire it into an existing agent in an afternoon. Here's the working code.
Install and store your first memory#
pip install mem0ai
The open-source Memory class runs on your own infrastructure. Out of the box it uses an OpenAI model to extract facts and a local vector store to hold them, so a default instance needs only your OPENAI_API_KEY:
from mem0 import Memory
memory = Memory()
messages = [
{"role": "user", "content": "I'm vegetarian and I hate cilantro."},
{"role": "assistant", "content": "Got it — no meat, no cilantro."},
]
memory.add(messages, user_id="alice")
That single add() call is doing the interesting work. It sends the exchange to an LLM that distills durable facts — Vegetarian, Dislikes cilantro — and stores just those, deduplicated against anything it already knew about Alice. It is not saving the transcript; it's saving what's worth remembering.
Recall the relevant facts before you answer#
On the next turn — even in a brand-new session days later — search Alice's memories and fold them into your prompt:
results = memory.search("what should I cook for her?", user_id="alice", limit=5)
for m in results["results"]:
print(m["memory"], "→", m["score"])
# Vegetarian → 0.91
# Dislikes cilantro → 0.88
search() returns a dict with a results list, each item carrying the memory text and a similarity score. Concatenate the top hits into your system prompt and your model now cooks a cilantro-free vegetarian meal without being told twice. The whole pattern is two calls: add() after a conversation, search() before the next one.
The remaining primitives round it out. memory.get_all(user_id="alice") lists everything you've stored about a user — indispensable for a debug view or a "what do you remember about me?" screen. memory.delete(memory_id) removes one, and memory.update(memory_id, data) edits it, which matters when a jurisdiction hands your users a right to be forgotten.
The one setting that decides your bill#
Here is the non-obvious part. Because add() calls an LLM to extract and reconcile facts, memory's cost is paid at write time, not read time — the opposite of most people's mental model. Reads are cheap embedding lookups; writes are the LLM calls. The lever is the infer flag:
# default: LLM extracts durable facts (costs a model call)
memory.add(messages, user_id="alice")
# raw: store the text verbatim, no extraction, no LLM call
memory.add("Ticket #4021: refund approved", user_id="alice", infer=False)
Leave infer=True for conversational memory where you want distilled preferences. Flip it to infer=False for log-style facts you want stored exactly, and to skip the extraction cost entirely. If your add() calls sit on a latency-sensitive path, move them off the hot path — we broke down the write-time-vs-read-time economics in Mem0's token-efficient algorithm.
Scope memories with the three IDs#
Every call takes at least one of user_id, agent_id, or run_id. They're filters, and mixing them is how you keep a multi-agent system from cross-contaminating:
memory.add(msgs, user_id="alice", agent_id="support-bot", run_id="ticket-4021")
Search with user_id alone to see everything about Alice; add agent_id to see only what the support bot remembers; add run_id to isolate a single session. user_id is the person, agent_id isolates one agent, run_id isolates one task.
Self-host or hand it off#
Everything above runs entirely on your infrastructure — no Mem0 account, nothing leaving your network. Point the config at your own LLM, embedder, and vector store:
config = {
"llm": {"provider": "openai", "config": {"model": "gpt-4.1-nano", "temperature": 0.1}},
"embedder": {"provider": "openai", "config": {"model": "text-embedding-3-small"}},
"vector_store": {"provider": "qdrant", "config": {"host": "localhost", "port": 6333}},
}
memory = Memory.from_config(config)
If you'd rather not operate that stack, the hosted MemoryClient is the identical four-method API pointed at Mem0's platform — swap Memory() for MemoryClient(api_key=...) and the rest of your code is unchanged. Self-host when you need data residency or already run a vector DB; hand it off when you want memory working today.
That's the whole surface: add to remember, search to recall, get_all/update/delete to manage, three IDs to scope, and infer to control the bill. If you're still deciding whether you even need a dedicated memory layer versus plain retrieval, start with agent memory vs RAG — then come back and wire this in.



