{
  "markdown": "# WikiMoth\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/juliangeymonat-jpg/wikimoth/main/docs/img/wikimoth-logo.png\" width=\"88\" alt=\"WikiMoth\">\n</p>\n\n### Connects the dots. The same way, every time.\n\n**[wikimoth.com](https://wikimoth.com)** · `pip install wikimoth`\n\n[![CI](https://github.com/juliangeymonat-jpg/wikimoth/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/juliangeymonat-jpg/wikimoth/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/wikimoth)](https://pypi.org/project/wikimoth/)\n[![npm](https://img.shields.io/npm/v/wikimoth-mcp)](https://www.npmjs.com/package/wikimoth-mcp)\n[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)\n\n**Deterministic, token-minimal, auditable memory for Claude and agents.** Point WikiMoth at a\nfolder of `[[wikilink]]` notes (an Obsidian vault, or Claude's own memory folder) and it\nfollows the authored links to the answer flat search can't reach, shows you the exact note-chain\nbehind it, and feeds the reader ~99% fewer tokens than pasting the whole vault. Pure markdown,\nno GPU, no vector DB, no LLM in the retrieval loop.\n\n<img src=\"https://raw.githubusercontent.com/juliangeymonat-jpg/wikimoth/main/docs/img/connect-the-dots.png\" alt=\"One question, three hops: WikiMoth follows your authored links to the note that holds the answer. Flat search stops at the first keyword match.\">\n\n```bash\npip install wikimoth\nwikimoth demo         # instant multi-hop recall over a bundled demo vault (no setup)\n```\n\n**Already have a `[[wikilink]]` vault** (an Obsidian vault, a notes folder)? Point WikiMoth at it and get the full connect-the-dots view in one command, no capture, no waiting:\n\n```bash\nwikimoth serve --vault /path/to/your/vault      # browse + \"what memory fed this answer\"\nwikimoth recall --vault /path/to/your/vault \"a connect-the-dots question\"\n```\n\n**Want it to build memory from your Claude Code sessions?** Install the capture hooks; each session you run and close is written into a `[[wikilink]]` vault:\n\n```bash\nwikimoth install      # capture: turn your Claude Code sessions into a [[wikilink]] vault\nwikimoth serve        # once you have captured sessions, browse them\n```\n\n---\n\n## Why not just let Claude manage its own context?\n\nWe benchmarked exactly that. An agent that browses the notes folder and prunes its own\ncontext reaches the **same answers**, multi-hop included (12/12 in our run). It just pays for it:\n**4 to 6 model round-trips and roughly 10x the billed tokens per question**, because it re-sends a\ngrowing transcript every step. WikiMoth retrieves the same note-chain in **one deterministic pass,\nno model in the loop**, and shows you the exact notes behind the answer.\n\n<img src=\"https://raw.githubusercontent.com/juliangeymonat-jpg/wikimoth/main/docs/img/agent-vs-wikimoth.png\" alt=\"Same answer, far less work: letting Claude prune its own context takes 4 to 6 model round-trips, about 10x the billed tokens, and roughly 9 seconds per answer; WikiMoth does it in one deterministic retrieval pass with zero model calls in the loop, in milliseconds, with an auditable note-chain. Both reach the right answer 12 out of 12.\">\n\n<sub>Real run, Claude Sonnet 4.6, 12 multi-hop questions on a reproducible vault. The ~10x counts a\nreader on both sides; it is corpus-specific, not a universal law. Reproduce it with\n`python scripts/run_agentic_benchmark.py`. Full breakdown in [Honest limits](#honest-limits).</sub>\n\n---\n\n## Why WikiMoth\n\nMost agent memory is either *paste the whole notes folder into context* (expensive, and the model\ngets lost in the middle) or *LLM-summarised similarity search* (lossy, and non-deterministic:\nthe same question can return different memory next week). WikiMoth takes a different bet: your notes\n**are** the store (plain markdown), the graph is **authored** (your `[[wikilinks]]`, no embeddings\nto train or drift), and retrieval is **code, not a model**, so it's reproducible and you can read\nexactly why each note was chosen.\n\n|  | **WikiMoth** | BM25 | Vector RAG | claude-mem | LLM Wiki (Karpathy) |\n|---|:--:|:--:|:--:|:--:|:--:|\n| Connects the dots (multi-hop over authored `[[links]]`) | ✅ | ❌ | ❌ | ❌ | ✅ *(agentic)* |\n| Deterministic retrieval (same query → same result) | ✅ | ✅ | ✅ | ❌ | ❌ |\n| No LLM call to retrieve | ✅ | ✅ | ✅ | ~ | ❌ |\n| Auditable note-chain (which notes produced the answer) | ✅ | ~ | ❌ | ❌ | ~ |\n| Direct-lookup recall@8 (real vault) | 1.00 | 1.00 | 1.00 | ~ | ~ |\n| No GPU / no vector DB / no index build | ✅ | ✅ | ❌ | ~ | ✅ |\n| Plain-markdown store (open in any editor) | ✅ | ~ | ❌ | ❌ | ✅ |\n| Token-minimal vs dumping the vault | ✅ −99% | ✅ −99% | ✅ −99% | ✅ | ~ |\n| Deterministic, API-free auto-capture | ✅ | ❌ | ❌ | ❌ | ❌ |\n| Hygiene **without an LLM** (conflicts · dupes · stale · supersede) | ✅ | ❌ | ❌ | ~ | ❌ |\n\n<sub>LLM Wiki *follows* links and skips the vector DB like WikiMoth, but an **LLM writes and reads** the wiki, so retrieval is agentic (an LLM call per recall, not reproducible), while its curated pages are richer. `~` = partial / not independently benchmarked.</sub>\n\nThe edge is the **combination**, not higher recall: WikiMoth *matches* flat search on the basics and\nadds connect-the-dots + determinism + an audit trail + a plain-markdown store. See\n[Honest limits](#honest-limits) for exactly where it ties and where it wins.\n\n### Compared to Karpathy's LLM Wiki\n\nWikiMoth shares the substrate Andrej Karpathy's *LLM Wiki* pattern popularised: plain-markdown\n`[[wikilink]]` notes, no vector DB, but flips the **engine**. In the LLM-Wiki pattern an **LLM\nwrites *and* reads** the wiki: rich, source-cited pages, but recall is *agentic* (it costs an LLM\ncall and the path isn't reproducible). WikiMoth computes the edges in **code** and retrieves with a\n**fixed algorithm, no LLM in the loop** → the same note-chain every time, reproducible and\nauditable. They're complementary, not competing: point WikiMoth at a Karpathy-style wiki and you get\ndeterministic multi-hop retrieval over it. (We don't claim to be \"better\" than the LLM Wiki: it\ncurates richer pages; we retrieve deterministically.)\n\n## Quickstart (read)\n\n```python\nfrom wikimoth import MemoryRAG, EchoReader\n\nrag = MemoryRAG(reader=EchoReader())          # API-free default reader\nrag.index(\"/path/to/your/wikilink/vault\")     # notes → ~400-token chunks, graph built\n\nchunks, tokens = rag.retrieve(\"a connect-the-dots question?\", top_k=8)\nprint(f\"{len(chunks)} chunks, {tokens} tokens to feed the reader\")   # the headline win\n\n# EchoReader is a deterministic stub for wiring/tests: it prints a diagnostic\n# `[echo] ...` line, NOT a natural-language answer. Swap in ClaudeReader (below)\n# for real prose. The retrieval + token numbers above are the same either way.\nprint(rag.answer(\"a connect-the-dots question?\"))\n```\n\nSwap in a real Claude answer (only touches the API when constructed):\n\n```python\nfrom wikimoth import MemoryRAG, ClaudeReader\nrag = MemoryRAG(reader=ClaudeReader(model=\"claude-sonnet-4-6\"))   # needs ANTHROPIC_API_KEY\n```\n\n## See what memory fed an answer: `wikimoth serve`\n\n```bash\nwikimoth serve                 # serves http://127.0.0.1:8765 (local-only)\nwikimoth serve --vault PATH --port 8080\n```\n\nA zero-dependency local web viewer (pure stdlib, no Flask, no JS framework, no network):\n\n- **browse + search** your notes,\n- the authored **`[[wikilink]]` graph** (the same edges the retriever walks),\n- and the one that matters, **\"what memory fed this answer\"**: type a question and see the exact\n  note-chain WikiMoth would feed a reader, with per-chunk hop distance, token counts, and the `−N%`\n  vs dumping the whole vault. Retrieval only: no LLM call, no API key, deterministic.\n\nBecause the store is plain markdown, you can equally open the same vault in Obsidian or VS Code;\nthe viewer is a convenience, not a lock-in.\n\n## In the agent loop: `wikimoth mcp`\n\n`wikimoth serve` is for *you*. The MCP server is for the *model*: it exposes the same deterministic\nretrieval over the Model Context Protocol, so Claude calls it itself instead of you fetching context\nby hand.\n\n```bash\n# 1. install into the Python that runs your Claude Code\npython -m pip install wikimoth\n\n# 2. verify the command resolves (prints status, then exits)\npython -m wikimoth status\n\n# 3. register the MCP server with Claude Code\nclaude mcp add wikimoth -- python -m wikimoth mcp\n```\n\nStep 2 is the check that matters: if `python -m wikimoth status` prints a status line, then\n`python -m wikimoth mcp` will run for Claude too. Use the **same `python`** in all three steps (it is\n`python3` on some systems); that is the one thing that has to match.\n\nPrefer the Node world, or no Python set up? One line, no toolchain matching:\n\n```bash\nclaude mcp add wikimoth -- npx -y wikimoth-mcp\n```\n\nThe [`wikimoth-mcp`](https://www.npmjs.com/package/wikimoth-mcp) launcher finds a Python that has\nWikiMoth (or `uvx`-installs one on the fly), injects the vault path so the server never reads an\nempty folder from the client's working directory, and passes the MCP channel through untouched. The\nsame `npx -y wikimoth-mcp` works as the server command in any `mcpServers` config (Claude Desktop,\nCursor, Windsurf); set `WIKIMOTH_VAULT` to your vault.\n\nNow Claude has a `recall(query)` tool. Ask it something that lives in your notes and it calls\n`recall`; WikiMoth walks the `[[links]]` and hands back the exact note-chain (no LLM call to\nretrieve, token-minimal, the same result every time), and Claude answers from it. A `status` tool\nreports the connected vault. For any other MCP client, use `python -m wikimoth mcp` as the server\ncommand (stdio transport); point it at a specific vault with `--vault PATH`.\n\n`python -m wikimoth mcp` is the portable form (it runs wherever the package is installed). The bare\n`wikimoth mcp` works too when the console script is on your PATH. It is pure stdlib: a hand-rolled\nJSON-RPC 2.0 stdio server, no MCP SDK dependency.\n\nmcp-name: io.github.juliangeymonat-jpg/wikimoth\n\n## Capture: sessions → notes (the write half)\n\nRetrieval needs a `[[wikilink]]` vault; hand-authoring one is the friction. `wikimoth.capture` builds\nit automatically by installing Claude Code lifecycle hooks that turn each session into **one**\ndeterministic markdown note.\n\n**The invariant that matters:** a note's `[[wikilinks]]` (the graph edges) are computed by code\n(string/path matching), **never by a model**. An LLM may *optionally* draft the summary prose\n(`WIKIMOTH_LLM_PROSE=1`), but any `[[...]]` it emits is stripped, never parsed as an edge. So the\ngraph is reproducible (same session + vault → same edges) and auditable. Default capture is fully\ndeterministic and makes **zero API calls**.\n\n```bash\nwikimoth install                 # writes 5 hooks into ./.claude/settings.json (absolute interpreter path)\nwikimoth install --user          # ~/.claude/settings.json instead\nwikimoth install --vault PATH    # choose where notes go (sets WIKIMOTH_VAULT)\nwikimoth status                  # vault, note/session/buffer counts, hook state\nwikimoth uninstall               # remove the hooks again\n```\n\nLifecycle: **SessionStart** recalls recent sessions into context · **UserPromptSubmit / PostToolUse**\nbuffer the session · **Stop / SessionEnd** write one note. The captured notes are exactly what the\nread pipeline indexes; capture and retrieval close the loop.\n\n## Keep memory honest: the hygiene suite\n\nA memory that only grows rots. Notes go stale, two notes start disagreeing, the same fact gets saved\ntwice, an old fact is replaced but never retired. Other agent-memory tools resolve this *with an LLM*\nthat silently overwrites the old state. WikiMoth ships six commands that surface it **deterministically**,\nand **never delete anything**: git is your audit trail.\n\n| command | finds | writes? |\n|---|---|:--:|\n| `wikimoth conflicts` | two notes asserting a different value for the same fact (type-aware, valid-time precision) | no |\n| `wikimoth lint` | broken links, orphans, stubs, stale notes, supersession cycles | no |\n| `wikimoth dedup` | exact + near-duplicate notes (MinHash + LSH, confirmed by exact Jaccard) | no |\n| `wikimoth decay` | notes going cold: old, rarely linked, rarely recalled (a review queue, never auto-delete) | no |\n| `wikimoth recall --as-of <date>` | what your memory asserted on a past date (bitemporal time-travel, no DB) | no |\n| `wikimoth supersede OLD NEW` | retire a fact: invalidate, don't delete | **yes** |\n\nThree principles hold across all of them:\n\n- **Invalidate, don't delete.** `supersede` marks a note superseded and links it to its replacement\n  in frontmatter. The old body drops out of retrieval, but its `[[link]]` to the current note stays\n  live, so a query that lands on the stale note **hops free to the new one**. Nothing is ever `rm`'d;\n  the history lives in git.\n- **The tool never guesses.** Every command emits *candidates*. The one judgement that isn't\n  mechanical, deciding whether two notes truly contradict, is left to the calling model, never baked\n  into the tool. So the output is reproducible and you can read exactly why each candidate was flagged.\n- **Bitemporal, no database.** `recall --as-of 2026-01-01` replays what the vault asserted on that\n  date from frontmatter validity windows alone. No event log, no vector store, no migration.\n\nSame invariants as the rest of WikiMoth: pure stdlib, deterministic (byte-identical output),\nread-only except the single `supersede` writer, plain markdown you can diff. The seven commands are\nalso exposed over MCP (`list_conflicts`, `list_lint`, `list_duplicates`, `list_fading`, `supersede`,\nplus `recall` gaining `as_of` / `show_superseded`), so the agent can keep its own memory clean.\n\n## Install\n\n<img src=\"https://raw.githubusercontent.com/juliangeymonat-jpg/wikimoth/main/docs/img/zero-infra.png\" alt=\"In the retrieval loop: 0 GPUs, 0 vector DBs, 0 LLM calls.\">\n\nWikiMoth's core is **pure stdlib** (`dependencies = []`): the retrieval engine, chunker, wikilink\ngraph, pipeline and capture are all vendored under `wikimoth/`: nothing extra to install, no GPU, no\nvector DB.\n\n```bash\npip install wikimoth\n# optional extras:\npip install \"wikimoth[hybrid]\"          # optional BM25-seeded retriever variant\npip install \"wikimoth[claude,tokens]\"   # real Claude reader + exact tiktoken counts\n```\n\nExtras: `hybrid` = BM25-seeded retriever (`rank_bm25`) · `claude` = the `anthropic` reader ·\n`tokens` = exact token counts (`tiktoken`) · `dense` = the dense benchmark baseline · `headroom` =\nreversible CCR compaction.\n\n## How it works\n\n<img src=\"https://raw.githubusercontent.com/juliangeymonat-jpg/wikimoth/main/docs/img/pipeline.png\" alt=\"Pipeline: retrieve by walking the links, compact to the note-chain (about 5k tokens), read plain markdown with an audit trail, then capture new facts back.\">\n\n**retrieve → compact → read.** `index()` splits each note into ~400-token chunks (~50 overlap),\nkeeping per-chunk note identity so the `[[wikilink]]` graph still connects across chunks (multi-hop\nat chunk granularity). `GraphRetriever(source=\"wikilinks\")` seeds lexically, then walks the authored\nlinks, so a passage *not* lexically similar to the question but *reachable by a link* still gets\npulled. An optional compaction stage (reversible CCR via\n[`chopratejas/headroom`](https://github.com/chopratejas/headroom)) shrinks passages further before\nthe (paid) reader; it degrades to a no-op if headroom isn't installed.\n\nA pure-navigation hub (a table-of-contents like `MEMORY.md`) can be indexed as **graph edges only**\n(`exclude_content`, default `(\"MEMORY.md\",)`): its `[[links]]` build edges and it stays a BFS\nwaypoint, but its own chunks never reach the reader.\n\nEvery stage is constructor-injectable via `MemoryRAG(retriever=…, compactor=…, reader=…)`, so you can\nswap the retriever (e.g. the BM25-seeded `HybridRetriever`), the compactor, or the reader.\n\n## Benchmark: tokens fed to the reader\n\n<img src=\"https://raw.githubusercontent.com/juliangeymonat-jpg/wikimoth/main/docs/img/token-cost.png\" alt=\"Tokens to answer one question: about 482,000 to paste the whole vault vs about 5,000 for the WikiMoth note-chain, a 99% cut versus dumping the vault.\">\n\n`wikimoth.benchmark.harness` measures *tokens fed to the reader* (what you actually pay for) across\narms over the **same** vault and questions:\n\n| arm | feeds the reader | status |\n|---|---|---|\n| `dump` | the whole vault | baseline |\n| `deterministic` | wikilink-graph retrieval | implemented |\n| `deterministic_compacted` | retrieval + Headroom | implemented |\n| `agentic` | an LLM browses and prunes its own context | implemented (Claude tool-use) |\n\nNo paid API calls run by default; every arm's reader defaults to the API-free `EchoReader`.\n\n## Honest limits\n\n<img src=\"https://raw.githubusercontent.com/juliangeymonat-jpg/wikimoth/main/docs/img/multi-hop.png\" alt=\"Link-only answers reached: BM25 0%, vector/dense 0%, WikiMoth 100%, on link-only corpora. On direct lookups all three tie at recall@8 = 1.00.\">\n\n<img src=\"https://raw.githubusercontent.com/juliangeymonat-jpg/wikimoth/main/docs/img/determinism.png\" alt=\"Same query run five times: WikiMoth returns one distinct result, LLM-based memory varies run to run.\">\n\nWikiMoth's value is **deterministic, auditable, token-minimal, plain-markdown** memory with a real\nmulti-hop capability, not \"better retrieval than BM25\". Specifically:\n\n- **The −99% is vs *dumping the vault*** (≈5k vs ~482k tokens on a real 356-note vault), not vs\n  BM25: a tuned BM25-RAG also feeds ~5k. The win is against the realistic status quo (paste\n  everything / naive whole-note RAG), and it's deterministic.\n- **On a typical real vault, retrieval ≈ BM25.** Direct-lookup recall@8 ties at 1.00. The\n  multi-hop / connect-the-dots win (0% → up to 100% where flat search scores zero) shows up on\n  **curated, link-heavy** corpora; on an average vault, hybrid is *never worse than BM25*, not\n  strictly better on recall.\n- **Determinism** is inherent to any static retriever (BM25/dense too); WikiMoth's determinism win is\n  specifically **vs LLM-summarised memory** (which varies run to run).\n- **vs letting the model prune its own context** (the `agentic` arm, real run against Claude\n  Sonnet 4.6, 12 multi-hop questions): the agent reaches the same answers, multi-hop included\n  (12/12). The difference is cost. It takes 4 to 6 paid round-trips and about 10x the billed tokens\n  per question, because it re-sends a growing transcript each step, where WikiMoth answers from one\n  deterministic pass with no model call in the retrieval loop and an auditable note-chain. The\n  multiple is corpus-specific, not a law. Reproduce it: `python scripts/run_agentic_benchmark.py`.\n\n## Pluggable + License\n\n`MemoryRAG(retriever=…, compactor=…, reader=…)`; defaults `GraphRetriever(source=\"wikilinks\")` /\n`NoOpCompactor` / `EchoReader`. Anything satisfying the small Protocols drops in.\n\nApache-2.0; see [LICENSE](LICENSE). © 2026 Julian Geymonat.\n",
  "bytes": 18987,
  "sha": "e80421b71d8f87fffabe100e5d1f243d97968213968f4c1e57e12ba31e35f61c",
  "repo_slug": "juliangeymonat-jpg/wikimoth",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_juliangeymonat_jpg_wikimoth_62841a64/readme"
}