How to Add Long-Term Memory to AI Agents with Mem0: A Practical Guide
Purpose

I was building a small AI assistant for my own projects, and I kept hitting the same wall.
I told the assistant that I prefer Python examples and PostgreSQL. Then I opened a new session a few days later and asked how I should implement a backend service.
It suggested JavaScript and MySQL.
The model was not “stupid.” The problem was simpler:
Most LLM API calls are stateless.
A model only knows the context I send with the current request. If I want it to remember something from yesterday, I need to send that information again or store it somewhere that can be retrieved later.
That is the problem Mem0 is designed to solve.
Mem0 is an open-source memory layer for AI applications. Instead of sending an entire conversation history on every request, it can extract useful memories from past interactions, store them, and retrieve the relevant ones when the user asks something later.
In this post, I will use the current Mem0 v3-style Python API to show:
- how to install and configure Mem0;
- how to add a long-term memory;
- how to retrieve it in a later interaction;
- how
user_id,agent_id, andrun_idscope memories; - how the newer Mem0 memory algorithm differs from older tutorials;
- how Mem0 differs from chat history, RAG, and a plain vector database;
- when Mem0 is useful, and when it is unnecessary.
The hard part of long-term AI memory is not storing facts. It is deciding what deserves to be remembered, what has become stale, and what should be retrieved for the current request.
What Problem Does Mem0 Actually Solve?
There are several straightforward ways to make an AI application “remember.”
Option 1: Send the Full Chat History Every Time
The simplest approach is to keep the entire conversation and send it back with every LLM request.
That works for short conversations, but it becomes inefficient as the history grows:
- token usage increases on every request;
- old messages consume context window space;
- irrelevant details add noise;
- latency and cost increase;
- eventually, old messages need to be truncated or summarized.
A short-lived chatbot can often use this approach. A long-running assistant usually needs something more selective.
Option 2: Build Your Own Memory Pipeline
You can store facts in a database and build the rest yourself:
- decide which conversation details are worth remembering;
- convert memories into embeddings;
- store them in a vector database;
- prevent duplicates;
- retrieve the right memories for each new question;
- decide what to do when old information becomes stale.
This gives you full control, but it is a lot of infrastructure for a feature that many AI applications need.
Option 3: Use a Memory Layer
This is where Mem0 fits.
User │ ▼AI Application │ ├── Recent chat history ├── Mem0 memories └── External knowledge / RAG │ ▼ LLM Context │ ▼ LLMMem0 does not make the LLM itself stateful. It gives your application a persistent memory system that can be queried between sessions.
That distinction is important:
Mem0 retrieves memory; your application still decides how to use that memory in the LLM prompt.
Mem0 in Plain English
The easiest way I think about it is:
- Chat history is a transcript.
- Mem0 is a curated notebook.
Suppose a conversation contains this:
User: I'm rebuilding my backend.Assistant: Which database are you using?User: PostgreSQL. We stopped using MongoDB.A raw chat history keeps all three messages.
A memory layer might extract a compact fact such as:
User's backend uses PostgreSQL.Later, when the user asks:
How should I structure the database layer?the application can retrieve that memory and put it back into the LLM context.
The goal is not to replay every old message. The goal is to retrieve only the information that is useful now.
Install Mem0
Install the Mem0 Python package:
pip install mem0aiFor this self-hosted example, I will use Qdrant as the vector store.
Install the Qdrant Python client:
pip install qdrant-clientThe Python client is not the Qdrant server itself. Start a local Qdrant instance with Docker:
docker run -p 6333:6333 qdrant/qdrantSet your OpenAI API key:
export OPENAI_API_KEY="sk-..."Mem0 supports multiple LLM, embedding, and vector-store providers. I am using OpenAI + Qdrant here because it keeps the example easy to understand.
Your First Mem0 Example
Create a file named mem0_example.py:
from mem0 import Memory
config = { "vector_store": { "provider": "qdrant", "config": { "collection_name": "my_memories", "host": "localhost", "port": 6333, }, }, "llm": { "provider": "openai", "config": { "model": "gpt-5-mini", "temperature": 0.1, }, }, "embedder": { "provider": "openai", "config": { "model": "text-embedding-3-small", }, },}
memory = Memory.from_config(config)
# First interaction: store a durable preference.memory.add( "I prefer Python examples and PostgreSQL.", user_id="alice",)
# Later interaction: retrieve relevant memories.results = memory.search( "How should I implement my backend?", filters={"user_id": "alice"},)
for item in results["results"]: print(item["memory"], item.get("score"))Run it:
python mem0_example.pyA simplified result can look like this:
{ "results": [ { "id": "mem-uuid", "memory": "User prefers Python examples and PostgreSQL.", "score": 0.82, "metadata": {} } ]}The exact wording and score can vary because memory extraction and retrieval involve models and similarity ranking.
The important part is that the second interaction does not need the original conversation transcript. It searches persistent memory instead.
Turning Retrieved Memories into LLM Context
Retrieving memory is only half the job.
Your application still needs to put the useful memories into the final LLM prompt:
user_id = "alice"user_message = "How should I implement my backend?"
results = memory.search( user_message, filters={"user_id": user_id},)
memory_text = "\n".join( f"- {item['memory']}" for item in results["results"])
prompt = f"""Relevant memories about the user:{memory_text}
User:{user_message}"""
print(prompt)The resulting prompt may look like:
Relevant memories about the user:- User prefers Python examples and PostgreSQL.
User:How should I implement my backend?The LLM now has a much better chance of giving a Python + PostgreSQL answer without receiving the user’s entire conversation history.
The Important Concept: user_id
A memory system becomes dangerous if memories from different users can mix.
Mem0 lets you scope memories using entity IDs.
A basic multi-user setup might look like:
alice ├── prefers Python └── uses PostgreSQL
bob ├── prefers TypeScript └── uses MySQLWhen adding Alice’s memory:
memory.add( "I prefer Python.", user_id="alice",)When searching:
memory.search( "What language should I use?", filters={"user_id": "alice"},)The search must be scoped to the correct user.
With the current v3-style API, entity IDs for search() and get_all() belong inside filters.
This is different from older Mem0 examples on the web that may use:
memory.search("query", user_id="alice")That old style can fail on newer versions.
User, Agent, App, and Run Memory
Mem0 supports several IDs for scoping memories:
user_id— memory about an end user;agent_id— memory associated with a specific agent;app_id— memory scoped to an application;run_id— memory associated with a particular run or workflow context.
For example:
results = memory.search( "What do we know about this task?", filters={ "user_id": "alice", "agent_id": "coding-agent", "run_id": "backend-migration-2026", },)A useful rule is to use the narrowest scope that matches your data model.
Do not treat every memory as globally available.
How Mem0 Works Internally
The current Mem0 memory pipeline is more interesting than the older “save a vector and search it” model.

At a high level:
ADD
Conversation │ ▼Retrieve related memories │ ▼Single-pass fact extraction │ ▼Exact deduplication │ ▼Memory + embeddings + entities
SEARCH
Query │ ├── Semantic similarity ├── BM25 keyword signal └── Entity signal │ ▼ Score fusion │ ▼ Relevant memoriesThere are two important ideas here.
1. Memory Extraction Is ADD-Only
Older Mem0 architectures used a second LLM pass to classify memory mutations such as:
ADDUPDATEDELETEThe newer v3 memory algorithm changed this substantially.
The extraction pipeline is designed around a single-pass ADD-only model: it extracts new memories and accumulates them rather than asking a second LLM call to rewrite or delete existing memories during every add operation.
This reduces the complexity of memory mutation and avoids having a model continuously rewrite the memory store.
2. Search Is Hybrid
Search is not only vector similarity.
The current retrieval system can combine:
- semantic similarity;
- BM25 keyword matching;
- entity signals.
This matters because embeddings are good at semantic similarity, but they are not always ideal for exact names, identifiers, or entity-heavy queries.
You can also inspect ranking details in OSS search with explain=True when debugging retrieval behavior.
For example:
results = memory.search( "food preferences", filters={"user_id": "alice"}, explain=True,)
print(results["results"][0]["score_details"])That is useful when you want to understand why a specific memory ranked highly.
What Happens When a Memory Becomes Outdated?
This is one of the hardest problems in long-term memory.
Suppose a user says:
I live in San Francisco.Six months later:
I moved to Seattle.An older memory system might try to immediately replace the first statement with the second.
But that approach has problems.
The old information may still matter historically:
User lived in San Francisco before moving to Seattle.The newer ADD-only approach makes a different trade-off: memories can accumulate, and retrieval must decide which information is most relevant.
That means your application still needs to think about:
- recency;
- metadata;
- explicit deletion;
- source-of-truth data;
- stale memory handling.
This is why long-term memory is not just a storage problem.
A system can remember perfectly and still answer incorrectly if it retrieves stale information.
For authoritative facts such as an account balance, current address for shipping, payment state, or medical record, query the real system of record instead of trusting memory alone.
Why Not Just Use a Vector Database?
A vector database is part of the memory stack, but it is not the entire memory system.
| Concern | Plain vector store | Mem0 |
|---|---|---|
| Store embeddings | Yes | Yes |
| Semantic retrieval | Yes | Yes |
| Extract useful facts from conversations | Manual | Built in |
| Exact deduplication | Manual | Built into memory pipeline |
| User / agent scoping | Design it yourself | Entity IDs |
| Keyword + entity retrieval | Build it yourself | Hybrid retrieval support |
| Stale fact policy | Build it yourself | Still requires application design |
If you already have a mature extraction, deduplication, ranking, and memory lifecycle system, you may not need Mem0.
If you only have a vector database and do not want to build all of that memory logic yourself, Mem0 can save a substantial amount of work.
How Mem0 Uses Entities and Graph Memory
A lot of older Mem0 content describes graph memory using examples such as:
Alice --reports_to--> BobBob --lives_in--> LondonThat can make it sound like Mem0 is simply building a Neo4j-style typed knowledge graph and traversing explicit relationship edges.
The newer architecture is more nuanced.
Think of it as entity-aware memory linking:
Memory A"Alice works at Acme" │ ├── Alice └── Acme
Memory B"Alice prefers Python" │ └── Alice
Memory C"Acme is migrating to PostgreSQL" │ ├── Acme └── PostgreSQLEntities give the retrieval system another signal in addition to semantic and keyword matching.
For example, a query mentioning Acme can benefit from memories linked to the same entity even when the exact wording differs.
This is particularly useful for:
- people;
- organizations;
- products;
- projects;
- named systems;
- recurring entities across conversations.
OSS vs Mem0 Platform
Be careful when reading older Mem0 graph tutorials.
Older versions may mention:
enable_graph;graph_store;- Neo4j-specific setup;
- separate graph visualization.
Those references may describe an earlier architecture.
Current Mem0 v3 documentation emphasizes entity extraction/linking and hybrid retrieval rather than requiring users to build explicit typed relationship graphs for normal memory search.
The managed Mem0 Platform also integrates graph/entity capabilities differently from older OSS graph-store examples.
The practical takeaway is simple:
Do not copy an old
enable_graph=Truetutorial without checking the documentation for your installed Mem0 version.
Mem0 v3 vs Older Mem0 Tutorials
This is worth calling out explicitly because Mem0 has changed quickly.
If you search for examples online, you may find code like:
memory.search("query", user_id="alice")or memory lifecycle explanations based on:
ADDUPDATEDELETENOOPor graph configuration such as:
graph_storeenable_graphNeo4jThose examples may be correct for an older release, but they do not represent the current v3-style architecture.
The important changes are:
Old tutorials Current v3 direction
search(..., user_id=...) → search(..., filters={...})
two-pass memory mutation → single-pass ADD-only extraction
vector-only style explanation → semantic + BM25 + entity signals
separate graph-store emphasis → entity linking integrated into retrievalThis is why I recommend checking the current Mem0 migration docs before copying snippets from older blog posts.
Mem0 vs Chat History vs RAG
These are not competing solutions. They solve different problems.
| Layer | Main question it answers | Example |
|---|---|---|
| Chat history | What did we just talk about? | Last few turns |
| Mem0 | What do I remember about this user or agent? | Prefers Python |
| RAG | What does an external knowledge source say? | Company documentation |

A production agent often combines all three:
System Prompt+Recent Chat History+Relevant Long-Term Memories+RAG Results+Current User MessageChat History
Best for short-term conversational continuity.
Mem0
Best for durable information that should survive across sessions.
RAG
Best for retrieving external knowledge such as:
- product documentation;
- policies;
- PDFs;
- internal knowledge bases;
- code repositories.
A common mistake is to use memory as a replacement for RAG.
It is not.
A user’s preference for Python is a memory.
The current API specification for your payment service belongs in your documentation or source repository.
Mem0 vs Zep vs Letta vs Cognee
Several open-source projects operate in the broader AI memory space.
At a high level:
| Tool | Main emphasis | Good fit |
|---|---|---|
| Mem0 | Drop-in long-term memory layer | Adding persistence to an existing agent |
| Zep | Temporal / agent memory | Time-aware conversational systems |
| Letta | Stateful agent architecture | Building long-lived agents around persistent state |
| Cognee | Graph-oriented knowledge infrastructure | Knowledge-heavy and graph-heavy workflows |
I would not choose based on GitHub stars or benchmark screenshots.
The more useful questions are:
- Do I need a standalone memory layer or a complete agent framework?
- Do I need strong temporal reasoning?
- Do I already have a vector database?
- Do I need graph-heavy knowledge representation?
- Do I need hosted infrastructure or self-hosting?
- Can I evaluate retrieval quality with my own conversations?
For a developer who already has an agent and simply wants to add persistent user memory, Mem0 remains a straightforward option to evaluate.
Benchmarks: Read the Fine Print
Mem0 publishes benchmark results for its newer memory algorithm, including strong scores on long-context memory evaluations.
Those results are useful, but benchmark numbers should not be treated as universal application guarantees.
Always check:
- which Mem0 product or version was tested;
- which model was used;
- which dataset was used;
- which baseline was compared;
- whether the result refers to the managed Platform, OSS SDK, or both.
A memory system that performs well on a benchmark can still fail on your application if:
- extraction misses important facts;
- the wrong memory is retrieved;
- stale information ranks too highly;
- user scoping is incorrect;
- your prompt ignores the retrieved memory.
Use benchmark numbers as a signal, not as a substitute for testing.
The Real Problems with AI Memory
Long-term memory solves one obvious problem:
The agent forgets.
But it introduces several new ones.
1. Forgetting
A useful fact is never extracted.
Example:
User: Always give me PostgreSQL examples.If that preference is not stored, it cannot be retrieved later.
2. False Memory
The system stores an interpretation that is not actually supported by the conversation.
That can happen because extraction itself uses a model.
3. Stale Memory
A fact used to be correct but is no longer current.
User works at Company A.A year later, the user works somewhere else.
4. Wrong Retrieval
A memory is correct, but irrelevant to the current request.
For example:
User prefers Python.That does not mean every future answer should involve Python.
A memory layer needs precision, not just recall.
The difficult part is not simply “remember more.”
It is:
remember the right things, retrieve them at the right time, and know when memory should not be trusted.
Privacy and Security
Persistent memory deserves more security attention than a normal prompt.
Do not casually store:
- passwords;
- API keys;
- authentication tokens;
- payment credentials;
- recovery codes;
- unnecessary personal data.
At minimum, think about:
- per-user isolation;
- deletion requests;
- retention periods;
- encryption at rest;
- access controls;
- audit requirements;
- privacy regulations.
Memory can be more sensitive than raw chat logs because it deliberately converts scattered conversation details into compact, reusable facts.
Treat it as a real user data store.
When Should You Use Mem0?
Mem0 is a good fit when your application becomes noticeably better if it remembers information across sessions.
Examples:
- a personal assistant that remembers preferences;
- a coding agent that remembers project conventions;
- a tutoring agent that remembers what the learner already knows;
- a support assistant that remembers recurring issues;
- a workflow agent that needs previous decisions;
- a SaaS assistant that personalizes answers for each user.
A useful test is:
Do users frequently need to say, “I already told you this”?
If yes, long-term memory may be worth adding.
When You Probably Do Not Need Mem0
Skip it when persistence adds no meaningful value.
Examples:
- one-shot summarization;
- document conversion;
- simple stateless API calls;
- basic calculator-like tools;
- short-lived workflows;
- RAG systems that only need to answer from documents.
Every memory layer adds:
- storage;
- model calls;
- retrieval logic;
- privacy requirements;
- debugging complexity.
Do not add memory just because “AI agents should have memory.”
When Mem0 Is Not Enough
Mem0 is not your source of truth.
Do not use it as the authoritative system for:
- account balances;
- payment status;
- order state;
- permissions;
- medical facts;
- inventory;
- financial records.
For authoritative data:
Agent │ ├── Memory → preferences / context │ └── Database / API → current truthMemory should help the application understand context.
It should not replace the systems that hold facts your business must get exactly right.
Final Architecture
A practical long-running agent often looks like this:
┌─────────────────────┐ │ User │ └──────────┬──────────┘ │ ▼ ┌─────────────────────┐ │ AI Application │ └──────────┬──────────┘ │ ┌────────────────────┼────────────────────┐ │ │ │ ▼ ▼ ▼ Recent messages Mem0 memories RAG / APIs / DB (short-term) (long-term) (source of truth) │ │ │ └────────────────────┼────────────────────┘ ▼ ┌─────────────────────┐ │ LLM Context │ └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ LLM │ └─────────────────────┘Each layer has a different job:
- chat history keeps the conversation coherent;
- Mem0 remembers durable user or agent context;
- RAG retrieves external knowledge;
- databases and APIs provide authoritative current facts.
That separation makes the system easier to reason about and safer to maintain.
Summary
Mem0 is useful because it gives AI applications a reusable memory layer instead of forcing developers to build extraction, storage, scoping, and retrieval from scratch.
The current Mem0 architecture is also different from many older tutorials:
search()scopes entity IDs throughfilters;- the newer extraction flow is single-pass and ADD-only;
- retrieval can combine semantic, BM25, and entity signals;
- older
enable_graphand mutation-oriented examples may no longer match the current API.
The biggest lesson is not that every AI agent needs memory.
It is that long-term memory changes the engineering problem.
Without memory, the challenge is forgetting.
With memory, the challenge becomes deciding what deserves to be remembered, what has become stale, and what should be retrieved for the current request.
That is the part Mem0 helps with — and the part you still need to design carefully.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments