How to Implement AI Agent Memory Using Markdown Files Instead of Vector Databases
You can implement AI Agent memory using Markdown files by creating a MarkdownMemoryStore class that persists memory entries as .md files with YAML front-matter, replacing the JSON/vector storage in bojieli/ai-agent-book with a lightweight text-based retrieval system using TF-IDF similarity.
The bojieli/ai-agent-book repository demonstrates vector-based memory storage in Chapter 3 using JSON objects and FAISS embeddings. By implementing AI Agent memory using Markdown files instead of vector databases, you create a human-readable persistence layer that requires no compiled dependencies and integrates seamlessly with version control systems.
Why Replace Vector Databases with Markdown?
Human-Readable Persistence
Unlike binary vector indices, Markdown files store content in plain text with YAML front-matter, allowing developers to inspect and edit memory entries directly using any text editor. Each memory entry becomes a self-documenting file where the metadata lives in the front-matter and the content lives in the body.
Version Control Integration
Markdown files generate meaningful diffs in git repositories, enabling code review of memory changes and complete audit trails without specialized database migration tools. This approach treats agent memory as code, making it trivial to track when specific knowledge was added or modified.
Zero External Dependencies
This approach eliminates the need for FAISS, Annoy, or other vector search libraries, reducing installation complexity and deployment overhead. You only require standard Python libraries plus scikit-learn for the optional TF-IDF retrieval component.
Architecture Overview
The Markdown-based implementation replaces the vector storage components while maintaining the same high-level API:
| Component | Vector Database Approach | Markdown File Approach |
|---|---|---|
| Storage Format | JSON objects with embedding vectors | Markdown with YAML front-matter |
| Backend File | chapter3/memobase/agent.py (MemoryStore) |
chapter3/memobase/markdown_store.py (MarkdownMemoryStore) |
| Retrieval Method | Vector similarity search | TF-IDF text similarity |
| Index | Embedding index (FAISS/Annoy) | CSV index mapping IDs to file paths |
| Update Operation | Re-index embeddings | Edit file in place |
Implementing the MarkdownMemoryStore Class
Create chapter3/memobase/markdown_store.py as a drop-in replacement for the existing memory backend. This class handles deterministic ID generation, YAML front-matter serialization, and lightweight text search:
import csv
import hashlib
import os
from datetime import datetime
from pathlib import Path
import yaml
class MarkdownMemoryStore:
"""Persist each memory entry as a markdown file with YAML front‑matter."""
def __init__(self, root_dir: str = "memobase_md"):
self.root = Path(root_dir)
self.root.mkdir(parents=True, exist_ok=True)
self.index_path = self.root / "index.csv"
# Ensure index file exists
if not self.index_path.exists():
self.index_path.write_text("id,filepath\n", encoding="utf-8")
def _id_from_content(self, content: str) -> str:
"""Deterministic short ID based on content hash."""
return hashlib.md5(content.encode()).hexdigest()[:12]
def add(self, content: str, metadata: dict | None = None) -> str:
"""Save a new memory entry and update the index."""
mem_id = self._id_from_content(content + datetime.utcnow().isoformat())
meta = metadata or {}
meta["id"] = mem_id
meta["timestamp"] = datetime.utcnow().isoformat()
md_path = self.root / f"{mem_id}.md"
front = yaml.safe_dump(meta, sort_keys=False)
md_path.write_text(f"---\n{front}---\n{content}\n", encoding="utf-8")
# Append to CSV index
with self.index_path.open("a", newline="", encoding="utf-8") as f:
csv.writer(f).writerow([mem_id, str(md_path)])
return mem_id
def get(self, mem_id: str) -> str | None:
"""Return the raw markdown body for a given ID."""
with self.index_path.open() as f:
for row in csv.reader(f):
if row[0] == mem_id:
return Path(row[1]).read_text(encoding="utf-8").split("---\n", 2)[2]
return None
def search(self, query: str, top_k: int = 5) -> list[tuple[str, str]]:
"""Very simple TF‑IDF search over markdown bodies."""
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel
# Load all documents
ids, texts = [], []
with self.index_path.open() as f:
next(csv.reader(f)) # skip header
for mem_id, fp in csv.reader(f):
txt = Path(fp).read_text(encoding="utf-8").split("---\n", 2)[2]
ids.append(mem_id)
texts.append(txt)
vectorizer = TfidfVectorizer().fit_transform([query] + texts)
cosine_sim = linear_kernel(vectorizer[0:1], vectorizer[1:]).flatten()
top_idxs = cosine_sim.argsort()[::-1][:top_k]
return [(ids[i], texts[i]) for i in top_idxs]
Integrating with the Agent
Modify chapter3/memobase/agent.py to import and instantiate the new store class. Replace the existing MemoryStore initialization with MarkdownMemoryStore:
# Existing imports …
from .markdown_store import MarkdownMemoryStore # ← new import
class Agent:
def __init__(self, …):
# Replace the JSON‑based store with the markdown one
self.memory = MarkdownMemoryStore(root_dir="memobase_md")
# … other initialisation remains unchanged
Alternatively, update chapter3/memobase/config.py to point MEMORY_BACKEND to the new class for a more configuration-driven approach.
Querying and Managing Memories
Use the standard API to interact with the Markdown-based memory system from your application code:
from chapter3.memobase.agent import Agent
agent = Agent()
# Add a memory entry
agent.memory.add(
content="User asked about the capital of France.",
metadata={"source": "conversation", "user_id": "u123"}
)
# Retrieve the most relevant memories for a new query
relevant = agent.memory.search("What is the capital of France?")
for mem_id, body in relevant:
print(f"[{mem_id}] {body.strip()}")
The resulting files follow a predictable structure that you can inspect directly:
$ ls memobase_md
index.csv a1b2c3d4e5f6.md ...
$ cat memobase_md/a1b2c3d4e5f6.md
---
id: a1b2c3d4e5f6
timestamp: 2026-07-30T14:12:33.123456
source: conversation
user_id: u123
---
User asked about the capital of France.
Summary
- Implement a
MarkdownMemoryStoreclass inchapter3/memobase/markdown_store.pyto replace vector-based storage with file-based persistence - Store each memory as a separate
.mdfile with YAML front-matter for metadata and Markdown body for content, ensuring human readability - Maintain a CSV index file at
index.csvto map memory IDs to file paths for O(1) lookups without scanning the directory - Use
TfidfVectorizerfromsklearnto implement text-based similarity search over the Markdown bodies without external vector databases - Update
chapter3/memobase/agent.pyto import the new store class and instantiate it with your desiredroot_dir - Benefit from git-compatible diffs, zero compiled dependencies, and the ability to edit memory entries using standard text editors
Frequently Asked Questions
How does Markdown storage compare to vector databases for large-scale applications?
For production systems with millions of entries, vector databases offer superior performance and approximate nearest neighbor search capabilities. However, for prototypes, educational projects, or agents with modest memory requirements (thousands of entries), the Markdown approach provides sufficient retrieval performance while eliminating infrastructure complexity and deployment overhead.
Can I migrate existing vector-based memories to Markdown format?
Yes. Write a conversion script that loads existing JSON records from the original MemoryStore, extracts the content and metadata fields, and calls MarkdownMemoryStore.add() for each entry. The deterministic ID generation based on content hash ensures no duplicates during migration, and the CSV index will be rebuilt automatically as you add entries.
What happens if the CSV index becomes corrupted or is deleted?
Since each .md file contains its ID in the YAML front-matter, you can rebuild the index by scanning the directory. Implement a rebuild_index() method that iterates through all .md files in the root directory, extracts the ID from the front-matter using yaml.safe_load(), and recreates the CSV mapping without losing any data.
Is TF-IDF search accurate enough for semantic retrieval?
TF-IDF captures keyword overlap but lacks deep semantic understanding compared to embeddings. For better semantic results without heavy vector databases, combine TF-IDF with BM25 ranking or use lightweight sentence transformers to generate embeddings on-the-fly while still storing the raw content in Markdown files for transparency.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →