markdown-memory
AGENTS.md1 starsChanged 38 days agoMITView on GitHub
Local MCP server for AST-aware Markdown indexing: SQLite with FTS5 (BM25), sqlite-vec section and passage vectors, EmbeddingGemma-300m (default) or bge-small embeddings, fused with Reciprocal Rank Fusion. CLAUDE.md is the full developer guide (layout, architecture rules, regression policy); this file is the short form for agents that do not read it. - Python 3.11+, mypy --strict clean, no untyped defs, no unconstrained Any. - Domain models are @dataclass(slots=True, frozen=True). - SQLite: WAL mode, writes only inside with db.transaction():. - Never write…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository4 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- reindex-docsSkill
- run-evalSkill
- test-regressionSkill
The file
We split this AGENTS.md into the rules it gives an agent
6 rules. About 0 are common, 0 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 27
Never write to stdout in src/ (it is the JSON-RPC channel).
- Very specific: line 29
Never tune on the held_out queries.
- Very specific: line 34
Never dump whole files: Do not Read/read_file/cat a Markdown document longer than ~150 lines into context unless the user explicitly asks …
- Very specific: line 48
search_docs(query, limit=5): - start here. Returns {"results": [...], "keyword_match": ..., "index_status": {...}}, plus a … - Very specific: line 74
When index_status.coverage is "unknown", the documentation you just searched is missing files or was never indexed end to end - say so …
- Very specific: line 82
get_document_outline(file_path): - only when you need the structure of a document: the heading tree with line spans and per-section token …
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
markdown-memory - agent instructions
Local MCP server for AST-aware Markdown indexing: SQLite with FTS5 (BM25), sqlite-vec
section and passage vectors, EmbeddingGemma-300m (default) or bge-small embeddings, fused
with Reciprocal Rank Fusion. CLAUDE.md is the full developer guide (layout,
architecture rules, regression policy); this file is the short form for agents that do not
read it.
Commands
uv sync # set up
uv run src/markdown_memory/server.py # run the MCP server (stdio)
uv run ruff check . && uv run ruff format . # lint, format
uv run mypy --strict src/ # type check (zero errors required)
uv run pytest # tests
uv run python scripts/eval_retrieval.py # retrieval gate: Top-1 >= 80%, Top-5 >= 90%, identifiers 100%
uv run python scripts/live_test.py # end-to-end over stdio
scripts/check.sh # all of the above except the eval, fail-fastNon-negotiable rules
- Python 3.11+,
mypy --strictclean, no untyped defs, no unconstrainedAny. - Domain models are
@dataclass(slots=True, frozen=True). - SQLite: WAL mode, writes only inside
with db.transaction():. - Never write to stdout in
src/(it is the JSON-RPC channel); log tosys.stderr. - Any change to ranking, chunking or embedding must pass
scripts/eval_retrieval.py. Never tune on theheld_outqueries.
<!-- markdown-memory:navigation-rules:start -->Reading Markdown documentation (when the markdown-memory MCP server is mounted)
Never dump whole files. Do not Read/read_file/cat a Markdown document longer than
~150 lines into context unless the user explicitly asks for the whole file. Fetch the one
section you need instead; it is typically 10-50x cheaper.
Pass cwd - your working directory, as an absolute path - on every call. In another git
worktree of the repository the server was started for, answers then come from that worktree's
own copy of the docs, indexed into its own database the first time it is named; anywhere else
it changes nothing. index_status.root names the documentation root that answered: when it is
not the tree you are working in, you are reading another checkout's docs. Every file_path in
an answer is relative to that root (absolute when another worktree answered): pass it back
verbatim, with the same cwd.
Work in this order:
search_docs(query, limit=5)- start here. Returns{"results": [...], "keyword_match": ..., "index_status": {...}}, plus akeyword_messagewheneverkeyword_matchis not"matched". The first hit carries the full sectioncontentwith its breadcrumb (heading_path), line range and token estimate - or, markedexcerpt: true, only the passage that matched and its neighbours, verbatim, withlinesnaming them andtokensstill what the whole section costs: when the excerpt is not enough,read_section(file_path, heading_path)returns the full section. The rest are pointers -file_path,heading_path,lines,tokensand, when a passage won the vector ranking,matched_passage: how that passage begins. Use exact identifiers verbatim (--dry-run,HELIOS_BATCH,ENOSPC): they are matched by keyword at 100% Top-1. Plain-language questions work too, but the first hit is not always the answer (Top-1 ~88%; Top-5 is ~97% reliable): when it is not, read the pointers'matched_passageand fetch the one that fits withread_section, passing itsheading_pathverbatim - for a(Part n)the base path returns every part. A(Part n)pointer also carriespart_preview, how that part begins, so parts of one split section can be told apart before reading any of them.keyword_matchsays whether keyword search found the query's terms; when it is"no_match", no indexed section contains them: a query of identifiers alone then returns no results, and any other query's hits are only semantic neighbours. Report such an identifier as not in the indexed documentation rather than answering from neighbours - unlessindex_status.indexingis true and coverage"unknown": then it may only not be indexed yet, so search again once indexing is false.index_status.gitignoreis what git said when a run of the root last finished:applied,off,no_repository,unavailable(git could not be asked, so ignored files may be in the index; the message says how to see why) orunknown- no run has recorded it yet, which is not a failure and needs nothing from you. Whenindex_status.coverageis"unknown", the documentation you just searched is missing files or was never indexed end to end - say so rather than concluding the docs do not cover it. Whenindex_status.changed_filesis non-zero, that many indexed documents could not be confirmed to be what was indexed - edited, unreadable or deleted - so a hit may quote text that is no longer there; re-runindex_directorybefore relying on it. The count is best-effort: files created since the last run are not in it, and an edit that puts a file's modification time back is not seen, so a zero is "nothing detected", not "everything verified".get_document_outline(file_path)- only when you need the structure of a document: the heading tree with line spans and per-section token estimates, at a few hundred tokens. Use it to choose a section, or to find sibling sections of a search hit.read_section(file_path, heading_path)- fetch exactly one section, verbatim.heading_pathis the breadcrumbRoot > Child > Subchildas shown by the outline or bysearch_docs; a bare heading title works when it is unambiguous. It returns the section's own text only - passinclude_subsections=truefor a parent heading whose content lives in its children. Oversized sections appear asPath (Part n); reading the base path returns all parts reassembled.
Supporting tools: list_documents(directory="") returns
{"documents": [...], "index_status": {...}} - what is indexed, and whether anything
vouches for it; index_directory(directory=None) (re)indexes. The stdio server keeps its
own docs root indexed in the background (from the first search, and whenever a search sees
change), so call it only when index_status.message asks for it - never while
index_status.indexing is true. It is incremental (SHA-256 per file), so re-running is cheap.
file_path may be absolute, relative to the docs root, or any unique path suffix. An
error from read_section lists the valid heading paths - retry with one of them rather
than falling back to reading the file.
<!-- markdown-memory:navigation-rules:end -->Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

