A filesystem-native vector database built entirely around Markdown files. Designed for AI agents that need fast semantic search over local knowledge bases.
- Zero infrastructure — no servers, no containers, everything lives on the filesystem
- Markdown-first —
.mdfiles are the source of truth, not rows in a table - Agent-oriented — optimized for speed and programmatic access by AI agents
- Non-destructive — never alters markdown content or frontmatter
- Recursively scan one or more source folders for
.mdfiles - Respect
.gitignorerules automatically; additionally supportMDVDB_IGNORE_PATTERNSin config for extra excludes (glob syntax) - Apply built-in default ignore patterns for directories that commonly contain markdown but should never be indexed:
.claude/— Claude Code agent memory and context.cursor/— Cursor IDE settings.vscode/— VSCode settings.idea/— JetBrains IDE settings.git/— git internalsnode_modules/— npm dependencies.obsidian/— Obsidian app config__pycache__/— Python cache.next/,.nuxt/,.svelte-kit/— framework build outputtarget/— Rust/Maven build outputdist/,build/,out/— general build output
- Built-in ignores are always applied and cannot be overridden
- Parse each file into: frontmatter metadata, heading structure, and body content
- Generate vector embeddings from file content (see §3 Chunking, §4 Embedding Providers)
- Compute a SHA-256 content hash per file and store it in the index — skip re-embedding when content hasn't actually changed
- Store all index data (vectors, metadata snapshots, chunk mappings, file references, content hashes) in a single portable index file on disk
- All file paths stored as relative to the project root (where
.markdownvdblives) - Support re-ingestion of individual files without rebuilding the full index
- Handle file deletions — remove stale entries from the index automatically
- Watch designated folders for filesystem events (create, modify, rename, delete)
- On change, compare content hash against the stored hash — only re-embed if content actually changed
- Debounce rapid successive changes to the same file
- Work across nested subdirectories
Documents are split using a hybrid strategy for optimal search quality:
- Primary split: by headings — each heading (h1–h6) starts a new chunk, preserving the document's natural structure
- Secondary split: size guard — sections exceeding a max token limit are sub-split into overlapping fixed-size chunks
- Each chunk retains a reference to its parent file, heading hierarchy, and line range
- Short files that fall under the token limit remain a single chunk
Embeddings are pluggable — the user configures their provider in the .markdownvdb config file:
- Ship with a sensible default provider (API-based)
- Support local inference (e.g. Ollama) as an alternative provider
- Provider config specifies: endpoint, model name, dimensions, auth (if needed)
- The system defines a standardized embedding interface — any provider that implements it can be swapped in
- Provider choice is transparent to the rest of the system; all internal code works with raw vectors regardless of source
- Accept a natural-language query and return the top-N most relevant results
- Results are section-level: each result returns the matched chunk's heading, content excerpt, line range, parent file path, relevance score, and full file metadata
- Agents get both precision (the specific section) and context (which file it belongs to)
- Must be fast — sub-100ms query response on warm index with 10k+ documents
- Support configurable result count and similarity threshold
- Index is loaded on-demand per invocation — no persistent daemon required
- Use memory-mapped files or a shared cache layer so that the first invocation pays the load cost but subsequent calls within a reasonable window are near-instant
- Target: cold load under 500ms for a 10k-doc index; warm queries under 100ms
- Multiple readers can query the index simultaneously
- Writes (re-indexing from watcher or manual ingest) acquire an exclusive write lock
- Queries arriving during a write either wait briefly or read from the last consistent snapshot
- No data corruption under concurrent access
- Read YAML frontmatter from each markdown file as structured metadata
- Support filtering search results by metadata fields (exact match, range, list membership)
- Combine semantic search with metadata filters in a single query
- Metadata filters must not degrade query performance significantly
The schema uses an infer + overlay approach:
- Auto-infer (baseline): on ingest, scan all frontmatter and automatically discover field names, infer types (string, number, boolean, list, date), and collect observed values
- User overlay (optional): an optional
.markdownvdb.schema.ymlfile lets users annotate, override, or extend the inferred schema — add descriptions, constrain allowed values, mark fields as required, or define fields that don't exist yet - The merged schema (inferred + overlay) is persisted in the index so agents can introspect it
- Agents can query the schema to discover what filterable fields exist before searching
- Schema acts as documentation, not strict enforcement — files missing fields are still indexed
- Automatically group semantically similar documents into clusters
- Assign new documents to the nearest existing cluster on ingest
- Periodically rebalance clusters to prevent drift as the corpus evolves
- Cluster assignments are stored only in the index file — frontmatter is never modified
- Expose cluster summaries (label, document count, representative keywords) so agents can browse topics before searching
Every indexed markdown file should support (but not require) frontmatter like:
---
title: "Document Title"
tags: [tag1, tag2]
# ...additional user-defined fields per schema
---- The system reads all frontmatter fields into metadata
- The system never writes to markdown files — all computed data (clusters, vectors, scores) lives in the index
- Unknown/extra fields are preserved and indexed as-is
- All vector data, metadata snapshots, chunk mappings, cluster assignments, content hashes, and schema live in a single index file
- The file must be portable — moving it with the markdown folder to another machine should just work
- File paths stored as relative to the project root
- Index file is the only artifact the system produces; deleting it triggers a full re-index on next run
- Must support memory-mapping for fast on-demand loading (see §6)
The system exposes two interfaces — a library for programmatic use and a CLI for shell-based agents and scripting:
- Search — semantic query with optional metadata filters; returns section-level results with file context
- Inspect schema — list available metadata fields and types
- List clusters — get all cluster labels with document counts and summaries
- Get document — retrieve full content + metadata for a specific file
- Get status — check index health, document count, last updated timestamp
- Ingest — trigger manual (re-)ingestion of one or more files or full rebuild
- All operations available as subcommands (e.g.
mdvdb search "query",mdvdb status,mdvdb ingest) - Output as JSON for easy parsing by agents
- Exit codes for scripting (0 = success, non-zero = error with message on stderr)
- Importable module with the same operations as typed function calls
- Returns structured objects, not strings
- Async-friendly for integration into agent loops
The .markdownvdb file in the project root serves as both config and environment file (dotenv-style). All settings are defined as environment variables with sane defaults — if the file doesn't exist, the system runs with defaults only.
- Shell environment (highest priority — always wins)
.markdownvdbfile- Built-in defaults (lowest priority)
| Variable | Default | Description |
|---|---|---|
MDVDB_EMBEDDING_PROVIDER |
openai |
Provider type: openai, ollama, custom |
MDVDB_EMBEDDING_MODEL |
text-embedding-3-small |
Model name for embedding generation |
MDVDB_EMBEDDING_DIMENSIONS |
1536 |
Vector dimensions (must match model output) |
MDVDB_EMBEDDING_BATCH_SIZE |
100 |
Number of chunks per embedding request |
OPENAI_API_KEY |
— | API key for OpenAI-compatible providers |
OLLAMA_HOST |
http://localhost:11434 |
Ollama server endpoint |
MDVDB_EMBEDDING_ENDPOINT |
— | Custom endpoint URL (when provider is custom) |
| Variable | Default | Description |
|---|---|---|
MDVDB_SOURCE_DIRS |
. |
Comma-separated list of folders to scan (relative to project root) |
MDVDB_INDEX_FILE |
.markdownvdb.index |
Path to the index file |
MDVDB_IGNORE_PATTERNS |
— | Comma-separated glob patterns to exclude (in addition to .gitignore and built-in ignores) |
MDVDB_WATCH |
true |
Enable/disable file watching |
MDVDB_WATCH_DEBOUNCE_MS |
300 |
Debounce interval for file change events |
| Variable | Default | Description |
|---|---|---|
MDVDB_CHUNK_MAX_TOKENS |
512 |
Max tokens per chunk before sub-splitting |
MDVDB_CHUNK_OVERLAP_TOKENS |
50 |
Token overlap between sub-split chunks |
| Variable | Default | Description |
|---|---|---|
MDVDB_CLUSTERING_ENABLED |
true |
Enable/disable automatic clustering |
MDVDB_CLUSTERING_REBALANCE_THRESHOLD |
50 |
Number of new documents before triggering rebalance |
| Variable | Default | Description |
|---|---|---|
MDVDB_SEARCH_DEFAULT_LIMIT |
10 |
Default number of results returned |
MDVDB_SEARCH_MIN_SCORE |
0.0 |
Minimum similarity score threshold |
# Embedding — use local Ollama
MDVDB_EMBEDDING_PROVIDER=ollama
MDVDB_EMBEDDING_MODEL=nomic-embed-text
MDVDB_EMBEDDING_DIMENSIONS=768
OLLAMA_HOST=http://localhost:11434
# Source
MDVDB_SOURCE_DIRS=docs,notes,wiki
MDVDB_INDEX_FILE=.markdownvdb.index
MDVDB_IGNORE_PATTERNS=drafts/**,archive/**
# Chunking
MDVDB_CHUNK_MAX_TOKENS=1024
# Clustering
MDVDB_CLUSTERING_REBALANCE_THRESHOLD=100- No GUI or web interface
- No multi-user or access control
- No cloud sync or remote storage
- No support for non-markdown file types
- No query language — filters are structured objects, not SQL/DSL
- No frontmatter modification — the system is strictly read-only on source files