Comparison

Agent Memory: Comparing Mem0, Letta, Zep, and Ujex Recall

Akshay Sarode 9 min read
Direct answer

Vector-embedding memory tools like Mem0, Letta, and Zep generally store agent memory as embeddings in a vector database: strong for finding conceptually related information even when the wording doesn't match, but the store itself isn't something you can open and read like a document. Ujex Recall stores long-term memory as individual markdown files with YAML frontmatter, searched with BM25 keyword ranking, so every memory is a file a human can open, diff, and grep, at the cost of missing purely conceptual matches phrased in different words. Which one is right depends on whether you need to audit what your agent remembers or need it to find things described differently than you stored them.

Open a vector store and ask it what your agent remembers about a customer, and you get back a list of floating-point numbers and a similarity score. Open a markdown file and ask the same question, and you get a sentence in plain English with a date on it. Both approaches are legitimate ways to give an agent long-term memory, and both show up constantly in this space. They just optimize for different things, and most comparisons of agent memory tools skip past that tradeoff to argue about which one is "better" in the abstract. It isn't abstract. It's a design decision that shows up the first time something in memory looks wrong and someone has to figure out why.

Two philosophies, one underlying question

Every agent memory system answers the same question: how do you let an agent recall something from a past session without replaying the whole transcript back into context every time. The dominant answer in this space, the one used in some form by Mem0, Letta, and Zep, generally involves converting memories into vector embeddings and storing them in a vector database. When the agent needs to recall something, its current context gets embedded too, and the system retrieves whatever is nearest in that embedding space. This is good at semantic recall. It can surface a memory like "the customer was frustrated with billing" when you search for "angry about the invoice," even though neither phrase contains the other's words.

Ujex Recall takes a different position on the same question. Long-term memory is written as one markdown file per memory item, stored per agent, with YAML frontmatter carrying structured fields and the memory content itself underneath in plain text. Retrieval runs on BM25, a well-established lexical ranking algorithm that scores documents by keyword overlap and term frequency rather than by conceptual closeness. There's also an auto-generated index file that summarizes what's currently in memory, so an agent, or a person, can get an overview without opening every single file.

Markdown files (YAML frontmatter) BM25 index Agent query Untrusted-tagged memory excluded by default
Ujex Recall's memory path: markdown files feed a BM25 index that the agent queries at retrieval time. Memory written during a session flagged as receiving untrusted input is tagged and held out of search results unless someone explicitly opts in.

What "you can read the file" actually buys you

The practical benefit of markdown plus BM25 isn't philosophical, it's operational. If an agent does something strange and you suspect it's acting on a bad memory, you can open the actual file and read exactly what it stored, in the same words it stored them in. You can grep across an agent's entire memory directory for a name, an incident, or a decision, using the same tools you'd already use to search any other text on disk. You can put the memory directory under version control and diff two points in time to see precisely what an agent learned, forgot, or overwrote between them. None of that requires a special export tool or a projection of embedding space into two dimensions so a human can squint at it. It's just files, and files are something engineers already know how to work with.

That matters more than it sounds like the first time an agent's behavior changes unexpectedly and someone asks what it thinks it knows. With a vector store, answering that question usually means running a similarity query and reading whatever comes back, and trusting that the nearest neighbors are representative of what's actually in there. With markdown files, the answer is: open the folder and read.

Where semantic search actually wins

None of this makes BM25 strictly better, and it would be dishonest to pretend otherwise. Lexical ranking has a real limitation: it misses things that mean the same thing but use different words. If a memory says "the client churned after a pricing dispute" and a later query asks "why did we lose that account," a lexical search has to work harder to connect the two than a semantic one does, because semantic search is built specifically to bridge that gap by comparing meaning instead of surface text. If your use case genuinely depends on conceptual recall across a large memory store, tens of thousands of items where you can't predict the exact vocabulary a future query will use, a vector-embedding approach is going to serve that need better than lexical search will. That's not a hedge, it's just true.

This is exactly why Ujex still ships a legacy memory path built on Vertex AI embeddings. It predates the markdown-based Recall system and is kept around for backwards compatibility with agents already using it. New work goes to the markdown system, not the legacy one, but the embedding path hasn't been ripped out, because the tradeoff it makes is a real one for some workloads, not a mistake that needed correcting.

The comparison, filtered by what you actually need

Rather than rank these tools against each other on one axis, it's more honest to lay out what each one optimizes for and let you filter by the property that actually matters for your situation. Claims about Mem0, Letta, and Zep below are kept general on purpose: they are established agent-memory tools that generally use embedding-based semantic search, and I'm not going to pretend to know internals I haven't verified.

Filter by what you need
Mem0

General-purpose memory layer for agents. Generally uses embedding-based semantic search over stored memories, with an open-source library and a self-hostable option alongside a hosted service.

Semantic searchOpen-sourceSelf-hostable
Letta

Agent framework, formerly known as MemGPT, with memory management built into the agent runtime. Generally uses embedding-based retrieval for long-term memory. Open-source and self-hostable.

Semantic searchOpen-sourceSelf-hostable
Zep

Memory and context layer for agents, generally built around embedding-based semantic search. Primarily used as a hosted service in most deployments I've seen referenced.

Semantic search
Ujex Recall

Long-term memory stored as one markdown file per item with YAML frontmatter, searched with BM25 keyword ranking. Every memory is a file you can open, diff, and grep. Apache-2.0, self-hostable.

Human-readableOpen-sourceSelf-hostable
PropertyMem0LettaZepUjex Recall
Storage representationVector embeddingsVector embeddingsVector embeddingsMarkdown files + YAML frontmatter
Primary retrieval methodSemantic similaritySemantic similaritySemantic similarityBM25 lexical ranking
Human-readable storeNot directlyNot directlyNot directlyYes, plain files
grep / diff / version controlNot directlyNot directlyNot directlyYes
Untrusted-memory quarantineNot statedNot statedNot statedYes, excluded by default, opt in to include

The quarantine feature worth knowing about either way

Say an agent reads an email as part of a task, and that email contains hidden instructions trying to manipulate the agent, a classic prompt injection delivered through content rather than through the user. If the session writes something to memory during that interaction, most memory systems don't distinguish where the memory came from. It just gets stored like any other fact, and the next time the agent searches memory, that poisoned claim comes back with the same authority as something the agent learned from a trusted conversation.

Ujex Recall tags memory differently. If a session has been flagged as having received untrusted input, for example content ingested from an external email or a webpage, anything that session writes to memory gets a provenance marker. By default, memory tagged this way is excluded from memory search results. An agent won't surface it unless someone explicitly opts in to include untrusted-tagged memory for a given search. That's a narrow, specific feature, but it closes a real gap: a single manipulated session quietly poisoning what an agent believes long after the session itself ended, then spreading through every future query that happens to match it.

It's worth calling out separately from the BM25-versus-embeddings question because it isn't downstream of that choice. You could build the same kind of provenance tagging on top of a vector store. Nobody asked me to claim any of the other three tools here does or doesn't, so I won't. What I can say is that it's part of how Ujex Recall works today, and it's the kind of thing that only matters until the day it matters a great deal.

How to actually decide

If the honest answer to "what does my agent remember about X" needs to be something you can point to, a file, a line, a timestamp, then markdown-plus-BM25 is doing the job it's designed for. That matters most in regulated or high-trust contexts: support agents, internal tooling, anything where a human might eventually need to explain a decision the agent made based on something it recalled. It also matters for teams that just want to debug their agents the way they debug everything else, with grep and diff and a text editor, instead of learning a new tool to inspect a vector index.

If the honest answer is that your memory store is large, the vocabulary users bring to it is unpredictable, and finding conceptually related memories matters more than being able to read the raw store, an embedding-based tool is going to do that job better. That's not a knock on markdown and BM25, it's just a different problem being solved. Ujex keeps its legacy Vertex AI embedding path alive for exactly this reason. It isn't a recommendation to use it for new work, but it's an acknowledgment that the semantic-recall problem is real and markdown files don't solve it as well as a vector index does.

The two aren't mutually exclusive in principle, but in practice picking both means running and maintaining two memory systems, which is its own cost. Most teams are better off picking the one that matches the failure mode they're actually worried about: opaque memory they can't audit, or lexical search that misses a synonym.

FAQ

Can you use a vector-embedding memory tool and Ujex Recall together?

There's no stated integration between Ujex Recall and tools like Mem0, Letta, or Zep. They're separate systems built on different storage models. You could technically run both alongside the same agent, but that means maintaining two memory stores, keeping them roughly in sync in your head, and deciding which one is authoritative when they disagree. Whether that's worth it depends entirely on your use case. For most teams, picking one is simpler and the sync overhead isn't worth carrying.

How do you migrate memory between a markdown-file system and a vector-embedding system?

Honestly, there's no specific migration tool to point you to here. Conceptually, moving from markdown files to a vector store means embedding the text content of each file and loading the resulting vectors into whatever database the target tool uses. Moving the other direction means extracting the underlying text from wherever the vector store keeps it, if it keeps a readable copy at all, and writing that out as files with whatever metadata you can recover. Either direction is a re-derivation of one representation from the other, not a one-command export. Don't expect a turnkey migration script unless a specific tool documents one.

Does BM25 keyword search miss things that mean the same thing but use different words?

Yes. That's the real limitation of lexical search compared to semantic, embedding-based search, and it's worth being upfront about it rather than talking around it. BM25 ranks documents on keyword overlap and term frequency. If a memory and a query describe the same underlying fact with no shared vocabulary, lexical search is going to struggle to connect them in a way that semantic search generally won't. This is the core tradeoff in this whole comparison, not a minor footnote.

What license is Ujex Recall under, and can I self-host it?

Ujex is Apache-2.0 and self-hostable. Recall runs as part of that same open-source stack, alongside the hosted product at app.ujex.dev and the docs at docs.ujex.dev.

Is Ujex's older embedding-based memory system still around?

Yes. Ujex keeps a legacy memory path built on Vertex AI embeddings for backwards compatibility with agents already using it. New work goes to the markdown-based Recall system, not the legacy embedding path, but the legacy path hasn't been removed.