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 MarkdownMemoryStore class in chapter3/memobase/markdown_store.py to replace vector-based storage with file-based persistence
  • Store each memory as a separate .md file with YAML front-matter for metadata and Markdown body for content, ensuring human readability
  • Maintain a CSV index file at index.csv to map memory IDs to file paths for O(1) lookups without scanning the directory
  • Use TfidfVectorizer from sklearn to implement text-based similarity search over the Markdown bodies without external vector databases
  • Update chapter3/memobase/agent.py to import the new store class and instantiate it with your desired root_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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →