Updated
Vector database vs markdown files for agent memory
Vector database vs markdown for agent memory, plus SQLite FTS5, MCP memory servers, Mem0, Letta and Zep: what each stores, who can fix it, what it costs.
Keep the memory an AI agent carries between sessions in plain markdown files, such as notes in a vault you already use, and build any search index from those files. A vector database is the right tool for one job: finding text when the question and the note share no words, across more documents than anyone will read. A coding agent’s memory is short records of decisions, facts and follow-ups. While you can still skim the folder, full-text search finds a record by its words and you can see why each result came back.
The other options sit on top of that choice. An MCP memory server is a protocol wrapper, and what matters is the store behind it. Mem0 and Zep earn their price when the memory belongs to thousands of end users nobody will curate by hand. Letta, the third name in most comparisons, moved its agent memory into markdown files in a git repository in February 2026.
Store of record or index
Every storage option for agent memory plays one of two roles. A store of record is the copy you read and correct, and it wins any disagreement. An index is a structure derived from that copy to make retrieval fast or fuzzy, and deleting it loses nothing.
Trouble starts when an index becomes the only copy. A vector database holding the sole record of what your agent believes is a store of record you cannot open in an editor or diff in git. An index over files takes an afternoon to add at any point, and readable memory takes far longer to pull back out of a vector store.
Markdown vs vector database, head to head
| Markdown files | Vector database | |
|---|---|---|
| Best at | Short records you read and correct | Fuzzy recall over large corpora nobody wrote for retrieval |
| Editing a wrong memory | Open the file and change it | Update the row through a client and re-embed it |
| Auditing everything stored | List the folder and read it | Page through the collection with its client |
| Version control | Line-by-line diffs in git | No meaningful diff |
| Finding an exact phrase or error code | grep finds every occurrence |
Needs a keyword leg beside the vectors |
| Recall when the wording differs | Misses unless you guess the words | Strong |
| Changing embedding model | Nothing to do | Every vector must be rebuilt |
| Infrastructure | A folder | An embedded engine or a service, plus an embedding model |
Where markdown wins
Agents bring back reversed decisions with total confidence. With a file you change the sentence or set status: superseded, and git shows the change as a diff. When the agent acts on a belief you never gave it, you open the folder and find the note behind it.
Where a vector database wins
Ask “why is the dashboard slow” and a note about N+1 query latency comes back, which keyword search misses. Over years of support tickets or imported PDFs, nobody can guess the words the writer used.
When scanning every file gets slow, the fix is an inverted index such as SQLite FTS5, Lucene or tantivy, which keeps exact matching on names and error codes. Embeddings fit the growth that comes from many writers over many years, in words you cannot predict.
Every option on the same five points
Prices are as published on each vendor’s site in October 2026.
| Option | What it stores | How retrieval works | Who can read and correct it | How it fails | Cost and lock-in |
|---|---|---|---|---|---|
| Plain markdown files | One memory per .md file with YAML fields |
grep, or an index file loaded at session start |
Anyone with a text editor | Misses unguessed words; contradictions pile up | Free, no lock-in |
| SQLite FTS5 | A word index rebuilt from the files | Keywords ranked by BM25 | Fix the file, reindex | Vocabulary misses; stale until reindexed | Free, disposable |
| Vector database | Text chunks with embeddings | Nearest neighbors by distance | Through a client, then re-embed | Confident near-misses; weak on identifiers | Free self-hosted plus embedding cost |
| MCP memory server | JSONL, markdown or a graph, per server | Tools such as search_nodes |
Depends on the backend | A dead server looks like an empty memory | Lock-in follows the store format |
| Mem0, Zep | Facts an LLM extracted from conversations | Semantic, keyword and graph search via API | Through the API only | Extraction keeps or drops the wrong facts | Free tiers, then $19 to $375 a month |
Plain markdown files
A file-based memory keeps one decision, fact or follow-up per file, with YAML frontmatter for kind, status and date above a few sentences of body. The agent searches the folder or reads an index file with one line per memory.
Claude Code’s auto memory lives in ~/.claude/projects/<project>/memory/, where a MEMORY.md index loads its first 200 lines or 25KB each session and topic files are read on demand (docs). Anthropic’s memory tool for the Claude API asks your code to view, create, str_replace, insert, delete and rename files under /memories. Letta Code’s MemFS keeps markdown files with frontmatter in a git repository and loads the root files into every turn.
A search for “auth” misses a note titled “Rotate session tokens on every refresh”, at fifty notes as readily as at fifty thousand. Two agents writing one file at once can overwrite each other. Claude Code’s auto memory is machine-local, so it stays on the computer that wrote it.
Memory files decay too, collecting contradictions, near-duplicates and an index too long to load; Claude Code warns the agent when MEMORY.md nears its limit. Mark a replaced memory superseded with a link to its successor, close follow-ups when they are done, and sweep for anything unchanged in six months. Instructions you write yourself belong in CLAUDE.md, covered in what to put in CLAUDE.md.
SQLite with full-text search
FTS5 is SQLite’s full-text search extension. It maps each word to the documents containing it and ranks matches with BM25, a formula that weighs rare words above common ones. The porter tokenizer adds stemming, so “tokens” matches “token”, and a trailing * makes a prefix query (FTS5 docs).
This script indexes a folder of memory files and runs a query. It ran unchanged against SQLite 3.50.4 from Python’s standard library:
import pathlib, sqlite3, sys
db = sqlite3.connect("memory-index.db")
db.execute("DROP TABLE IF EXISTS mem")
db.execute("CREATE VIRTUAL TABLE mem USING fts5(path UNINDEXED, body, tokenize='porter unicode61')")
for p in pathlib.Path("memory").rglob("*.md"):
db.execute("INSERT INTO mem(path, body) VALUES (?, ?)", (p.as_posix(), p.read_text(encoding="utf-8")))
db.commit()
for path, snip in db.execute(
"SELECT path, snippet(mem, 1, '[', ']', '...', 8) FROM mem WHERE mem MATCH ? ORDER BY rank LIMIT 5",
(sys.argv[1],),
):
print(path, "|", snip)
Against two memory files, python index.py "token*" returns the session-token decision. python index.py "slow dashboard" returns nothing, though one note says “Dashboard latency came from an N+1 query”. FTS5 joins query words with AND, and that note never uses “slow”.
Each run rebuilds the table from the files, so edits always land and deleting memory-index.db loses nothing.
Vector databases
A vector database stores text chunks with an embedding for each: a list of numbers from a model that places passages of similar meaning close together. Search can be exact, comparing against every vector, or approximate through an index such as HNSW, a layered graph that trades a little recall for speed.
| Store | How it runs | Notes, October 2026 |
|---|---|---|
| Chroma | In process with chromadb.PersistentClient(path=...), a local server with chroma run --path, or Chroma Cloud |
Apache 2.0. Embeds locally with all-MiniLM-L6-v2 by default. Cloud: Starter $0 plus usage ($5 credits), Team $250 a month plus usage ($100 credits); $2.50 per GiB written, $0.33 per GiB stored a month (pricing) |
| pgvector | An extension inside a Postgres you already run | Version 0.8.7. Exact search by default, HNSW or IVFFlat for approximate search. Indexes cover up to 2,000 dimensions for vector and 4,000 for halfvec |
| sqlite-vec | A SQLite extension, in the same file as an FTS5 table | Pre-v1 (0.1.9 on PyPI), with breaking changes expected. Apache 2.0 or MIT. Brute-force nearest-neighbor search through a vec0 virtual table |
A similarity score measures topic, so last spring’s reversed decision ranks as high as the one that replaced it. Exact identifiers such as ERR_TOKEN_REUSED match poorly. A new embedding model leaves every stored vector meaningless until you re-embed, and a corrected memory needs its row updated and re-embedded with no diff to show the change.
MCP memory servers
The Model Context Protocol (MCP) is an open standard for connecting agents to outside tools. A memory server exposes tools the agent calls instead of touching files, and each server picks its own storage.
The reference implementation, @modelcontextprotocol/server-memory, keeps a knowledge graph of entities, directed relations and observations. Its nine tools are create_entities, create_relations, add_observations, delete_entities, delete_observations, delete_relations, read_graph, search_nodes and open_nodes. Everything goes to one JSONL file, memory.jsonl in the server’s own directory by default. Under npx that directory sits inside a package cache, so set MEMORY_FILE_PATH before you store anything you want to keep:
claude mcp add --env MEMORY_FILE_PATH=/home/you/agent-memory/memory.jsonl \
--transport stdio memory -- npx -y @modelcontextprotocol/server-memory
The server is MIT licensed, and the MCP project is moving new contributions to Apache 2.0. A graph earns its place for relationship questions, such as which services depend on the auth module, which a folder search answers poorly. Correcting a memory means editing JSON by hand or calling a delete tool.
Basic Memory writes markdown files to ~/basic-memory with a SQLite index and optional semantic search. It is AGPL-3.0, with a cloud Team plan at $15 per seat a month (pricing). Graphiti ships its own MCP server over a graph database, and Zep’s hosted plans include Memory MCP Server seats, 5 on Flex and 15 on Flex Plus.
The reference server runs over stdio, standard input and output, so the client launches it on demand. A config the client never loaded, or a server that crashes at startup, looks exactly like an empty memory. claude mcp list shows “Failed to connect” for a dead server, /mcp shows status inside a session, and MCP_TIMEOUT=10000 claude gives a slow server ten seconds to start (docs). CLI tools vs MCP servers for agents covers the wider trade.
Mem0, Zep and Letta
Mem0 and Zep have a language model read conversations, decide what to keep, and store it behind an API, which suits an app with thousands of users. Letta now keeps memory as files.
| Mem0 | Zep | Letta | |
|---|---|---|---|
| Model | LLM fact extraction with entity linking | Temporal knowledge graph; each fact carries a validity window | Agent harness whose memory is markdown in git |
| Retrieval | Semantic, BM25 keyword and entity matching, fused | Semantic, BM25 and graph traversal | Root files always in context; the agent opens the rest with file tools |
| Self-host | Yes, Apache 2.0 | Community Edition retired; Graphiti is Apache 2.0 | Yes, Letta Code is Apache 2.0 |
| Correcting a memory | update or delete by memory id |
Through the API | Edit the file |
Mem0. Its April 2026 algorithm extracts facts in one LLM call and only adds, so nothing extracted overwrites an earlier memory (GitHub). You correct one with update or delete and its id, and the hosted Platform’s create_memory_export pulls memories out against a schema you define. The open source build defaults to OpenAI gpt-5-mini, text-embedding-3-small and a local Qdrant at /tmp/qdrant, so self-hosting calls OpenAI until you change the config. Hosted plans are Hobby (free, 10,000 adds and 1,000 retrievals a month), Starter ($19, 50,000 and 5,000) and Pro ($249, 500,000 and 50,000) (pricing).
Zep. Each fact in Zep’s graph records when it became true and when it was superseded, so “what was true in March” has an answer. Zep is cloud only since it dropped support for its Community Edition, and Graphiti, the open core, self-hosts on Neo4j, FalkorDB or Amazon Neptune with an LLM. Zep charges 1 credit per 350 bytes ingested, with retrieval unmetered. Plans are Free (10,000 credits a month), Flex ($125, 50,000) and Flex Plus ($375, 200,000) (pricing).
Letta. Letta, formerly MemGPT, retired its V1 server to an archive branch and moved development to Letta Code (announcement, February 12, 2026). Memory is a git repository of markdown files. On first launch you choose between signing in to Letta Cloud and running locally. Plans run from Free to Pro at $20 a month for up to 20 stateful agents, and the API plan is $20 a month plus $0.10 per active agent (pricing).
The extraction model in Mem0 or Zep can store a passing remark as a fact or drop the decision you cared about, and you see the result only through an API. An add-only store grows contradictions that need consolidation, and leaving needs an export while the service still runs.
What the benchmarks tell you
Mem0’s README reports 92.5 on LoCoMo and 94.4 on LongMemEval, two long-conversation memory benchmarks, measured by Mem0 on its managed platform. In May 2025 Zep published a rebuttal saying Mem0 had misconfigured Zep in an earlier LoCoMo comparison. Run your own questions against your own memory before you rely on any of these numbers.
Which one to choose
| Your situation | Store of record | Add this index |
|---|---|---|
| One developer, a few agents, a folder you can still skim | Markdown notes in a vault, such as Jotura’s memory notes, or a folder in a repo | None yet, or FTS5 once grep gets slow; Jotura keeps a keyword index for you |
| Searches keep missing notes you know exist | Markdown notes | Embeddings over the notes, fused with keyword search, as Jotura’s recall does after you turn on search by meaning in the desktop app |
| Thousands of documents you did not write: tickets, transcripts, PDFs | The source documents | A vector database with a keyword leg |
| Questions about how entities relate | Markdown files, or the MCP reference server’s JSONL graph | A graph built from the store |
| A product with many end users and preferences nobody curates | Mem0 or Zep | Built in |
| Facts that change over time, where history matters | Zep or Graphiti | Built in |
Running a file store with a disposable index
Keep the truth in files and treat every index as a cache, under these rules:
- Agents write files only. The agent writes or edits a file, and the index catches up.
- Every index row records its source. Store the file path and a hash of its content, plus the embedding model name for vectors.
- Retrieval returns paths, and the agent reads the file before acting. A search snippet is a pointer to the file.
- The file wins. When the indexed hash differs from the file on disk, reindex that file and use what the file says.
- Filter on frontmatter status after retrieval. Similarity cannot tell a superseded decision from an active one.
- Rebuilding is one command, and deleting the index is always safe. Keep the index out of git.
With sqlite-vec, the vector leg sits in the same SQLite file as the FTS5 table. This schema and query ran against sqlite-vec 0.1.9:
CREATE TABLE chunks (
id INTEGER PRIMARY KEY,
path TEXT NOT NULL,
content_hash TEXT NOT NULL,
model TEXT NOT NULL,
text TEXT NOT NULL
);
CREATE VIRTUAL TABLE chunk_vectors USING vec0(embedding float[384] distance_metric=cosine);
SELECT c.path, c.content_hash, v.distance
FROM chunk_vectors v JOIN chunks c ON c.id = v.rowid
WHERE v.embedding MATCH :query_vector AND k = 5
ORDER BY v.distance;
Run the keyword and vector queries separately and merge them by rank, since BM25 scores and vector distances sit on unrelated scales. Reciprocal rank fusion scores each document by summing 1 / (60 + its rank) across both lists, which lifts notes that do well in both. Mem0 and Graphiti both run BM25 beside their vectors for the same reason.
Where Jotura fits
Jotura is a markdown notes app whose agent memory follows the layout above, with the notes as the store of record and both indexes disposable. You install the Jotura skills from the jotura-agents marketplace, inside Claude Code with these two lines, and from then on the agent does the work:
/plugin marketplace add adamrichardson14/jotura-agents
/plugin install jotura@jotura-agents
Other agents copy the skill files from the jotura-agents repository. The agent logs decisions, facts and follow-ups as notes under Memory/Decisions/, Memory/Facts/ and Memory/Followups/, and recalls them before proposing an approach. Recall goes through the jotura command line tool, which fuses its own keyword index at .jotura/cli-search/ with the vector index at .jotura/vectors.db. Only the desktop app builds the vector index, and recall falls back to keyword without it.
You read and correct memory in the desktop app’s Agent Memory tab. The one command you run yourself is jotura memory doctor, which lists stale memories and old follow-ups. Jotura has no MCP server, so the agent needs a shell, and the app is closed source.
For the machine-local limit raised earlier, Jotura’s memory notes travel with the vault through end-to-end encrypted sync, at £4 ($5) a month or £40 ($50) a year with no free trial (pricing). The memory guide covers the loop, and the search docs cover the embedding providers.
Frequently asked questions
Should agent memory go in a vector database or markdown files?
Markdown files, because you can read, correct and diff them, and a notes vault such as Jotura’s lets you browse them in an app. A vector database earns its place as an index over those files once searches miss notes you know exist, or over thousands of documents nobody wrote for retrieval.
Is semantic search the same thing as a vector database?
Semantic search is the technique: embed the query and the text, return the nearest matches. A vector database is one place to keep those embeddings. A semantic index built from your markdown files and rebuilt from them leaves the files as the record.
Is an MCP memory server better than plain markdown files?
An MCP server is an interface, so compare the store behind it: the reference server writes a JSONL knowledge graph and Basic Memory writes markdown. Confirm with claude mcp list that the server starts, because a dead stdio server looks like an empty memory.
Mem0 vs Letta vs Zep: which should I use?
Mem0 suits apps that extract preferences from many users’ conversations, with hosted plans from free to $249 a month. Zep suits facts that change over time and runs cloud only, from free to $375 a month. Letta Code is an agent harness that keeps memory as markdown files in git. For your own coding agents, markdown notes in a vault cover the same ground with no account.
Do I need embeddings for a few hundred memories?
Not to start. Full-text search handles that size predictably. Add embeddings over the files when your own searches start missing notes, and keep keyword search beside them for names and error codes.
Can the files and the index disagree?
Yes. After you edit a file, the index can return text the file no longer contains. Store a content hash with every index row, and when it differs from the file, reindex. The file always wins.