Persistent, portable, cross-AI memory over a plain markdown vault — with push-based auto-recall so the model remembers your past work without being asked.
Note
P. Sherman, 42 Wallaby Way, Sydney. Dory could say the address. She just couldn't recall it when it mattered. That's your AI. That's every memory tool that only does storage. mnemo is the one that remembers at the right moment.
You: "remember we decided X yesterday"
AI: "i have no memory of that, what is X"
You: *re-explains the entire project for the 9th time*
New chat → instant amnesia. The decision from yesterday? Gone. The bug you already killed? It's back, baby. The contract two repos share? Never heard of her.
Most "memory" tools only solve storage — they save notes the model never reads back. Cool, you wrote down Nemo's address on a fridge he can't reach. 🧲 The actual hard part is retrieval: getting the right memory into context at the right moment.
Storage was never the hard part. Retrieval is. 🎯
Two currents. Same ocean.
| 🌊 PUSH — the rescue | a SessionStart hook injects the relevant notes into context before you type a word |
the AI starts every session already knowing your history |
| 🪝 PULL — the net | an MCP server lets any MCP-capable AI search the same vault mid-chat | Claude Code, Cursor, whatever — same brain |
Nemo never stays lost. He just keeps swimming back.
- 🗂️ Vault = source of truth. Plain markdown + YAML frontmatter. Edit it in Obsidian, your editor, or a cave with a stick. The index (SQLite FTS5 + vectors) is derived and rebuildable — never committed.
- 🧮 Map, then expand. Search returns summaries + paths, not full bodies. Token cost stays flat whether your vault has 10 notes or 10,000. Your context window stays unbothered.
- 🧠 Store only what can't be re-derived. File trees and function signatures? The AI can find those itself. Decisions, gotchas, contracts, why-the-hell-did-we-do-it-this-way? Saved.
- 🔍 Hybrid search. Keyword (FTS5) + semantic (local embeddings, RRF-fused) — catches the paraphrase keyword search fumbles. Runs 100% local: offline, private, zero API spend.
- 🚚 Portable AF. Your vault is a private git repo. New machine, new AI?
mnemo clone <url>→ reindex → it knows everything. Memory that survives a laptop death. - 🔗 Shared across repos. Drop a
.mnemo-projectmarker and many repos feed one project brain.
# zero-install run (recommended)
uvx mnemofish --vault ./my-vault init
# or install it (ships the `mnemo` command)
pip install "mnemofish[embed,mcp]" # everything: semantic search + MCP server
pip install mnemofish # core only (FTS5 keyword search)📦 PyPI package is
mnemofish; the CLI command ismnemo(the aliasmnemofishalso works).
From source (dev)
uv venv
uv pip install -e ".[dev]" # core (FTS5 keyword search)
uv pip install -e ".[dev,embed,mcp]" # + semantic search + MCP serveruv run mnemo --vault ./my-vault init
uv run mnemo --vault ./my-vault write \
--type decision --title "RF update is sequential" \
--summary "Devices update one at a time, not concurrently — prevents system lockup."
uv run mnemo --vault ./my-vault search "rf update order"
uv run mnemo --vault ./my-vault context "continue RF update work" --project stm32-rf-ota
uv run mnemo --vault ./my-vault daily "what I shipped today"Auto-recall isn't magic; it's a 4-step setup. Do it once, then forget about it.
pip install "mnemofish[embed,mcp]" # ships the `mnemo` command
mnemo --vault "~/my-memory" init # scaffolds the vault as a git repoPick one vault path and use it everywhere (e.g. ~/my-memory). This is your private
brain — keep it separate from any code repo.
Add this to your Claude Code settings (~/.claude/settings.json for all projects, or a
project's .claude/settings.json). Full example: hooks/settings.example.json.
- Use the absolute vault path, and make sure
mnemois on yourPATH(pip install/uv tool install mnemofishboth do this). - Keep
--project-dir "$CLAUDE_PROJECT_DIR". This is how the hook knows which project you opened. Without it, recall guesses from the hook's working directory and often comes back empty.
Recall has nothing to inject until you save something. Record decisions, lessons, and
gotchas as you work (or let the AI do it via the MCP memory_write tool):
mnemo --vault "~/my-memory" write \
--type decision --project my-app \
--title "Auth tokens are short-lived" \
--summary "15-min access tokens + refresh; long-lived tokens were a security risk."
⚠️ The #1 reason recall comes back empty: it's project-scoped. The hook only injects notes whoseprojectmatches the repo you opened. mnemo derives the project from the git remote slug → else the folder name. So a note saved with--project my-apponly surfaces when you open themy-apprepo. Notes for other projects stay out of your context (that's the point — no noise).Working across several repos that share one brain? Drop a
.mnemo-projectfile in each, containing the shared project name — they'll all recall the same notes.
The hook fires at session start, so close and reopen Claude Code in a project that
has notes. You'll get a ## 🧠 mnemo recall block in context before you type a word.
Before running a daemon, test whether automatic retrieval is useful with context:
mnemo --vault "~/my-memory" context "continue RF update work" \
--project-dir "C:/path/to/repo" \
--budget 2400 \
-k 5context returns one compact markdown block: project MOC first, then relevant
decision/lesson/reference/note summaries. It never returns full note bodies; expand
one item only when needed:
mnemo --vault "~/my-memory" get 20260623-guncelleme-sirali-yapilirUse --json for benchmarks or hook experiments.
For MCP clients (Claude Code, Cursor, …), run the server and point your client at it. Any connected AI can then search the same vault mid-conversation:
mnemo --vault "~/my-memory" serve # exposes memory_search / get / moc / write| Symptom | Cause / fix |
|---|---|
| Recall block is empty | The current repo's project has no notes yet — write some with --project <name>, or the detected project name doesn't match. Check with mnemo --vault <v> recall --project <name>. |
| Nothing happens at session start | mnemo not on PATH, wrong/relative vault path, or the hook lacks --project-dir "$CLAUDE_PROJECT_DIR". Test the exact command in a terminal first. |
write / reindex hangs |
A stuck mnemo process is holding the SQLite index lock. Kill stray mnemo/python processes and retry. Don't run two indexing commands at once. |
First write is slow |
One-time: semantic embeddings download the local model (~80 MB). Subsequent runs are fast; recall never loads the model. |
┌──────────────────────────────────────────┐
│ Vault (markdown + frontmatter) │ ← single source of truth (Obsidian-friendly)
└───────────────────┬──────────────────────┘
│ parse (incremental: mtime/hash)
┌──────────▼───────────┐
│ CORE LIBRARY │ index.sqlite (FTS5 + vectors)
│ parse / index / │ ← derived, .gitignored, rebuildable
│ search / write │
└─────┬───────────┬────┘
┌────────────▼──┐ ┌───▼─────────────────┐
│ CLI + hook │ │ MCP server │
│ (PUSH) │ │ (PULL) │
│ session-start │ │ any MCP-capable AI │
│ auto-recall │ │ in-chat tool calls │
└────────────────┘ └─────────────────────┘
A task = 1 MOC + a few atomic notes, no matter how big the vault gets. Flat tokens. 📉
---
id: 20260623-rf-uid-sequential
type: decision # decision | lesson | daily | project | reference | note | profile
title: RF update is sequential
project: stm32-rf-ota
tags: [rf, protocol, stm32]
summary: Devices update one at a time, not concurrently — prevents system lockup.
links: [20260623-rf-uid-identity]
---
Sequential update: id1 finishes, id2 begins. All devices don't drop into the
bootloader at once, so the system stays alive...summary is required and short — the index shows that, not the body. That's where the
token discipline comes from. (No summary = bad note. The fish judges you. 🐠)
| Command | What it does |
|---|---|
init / sync / clone |
manage the vault as a private git repo |
write / daily |
add notes (deduped; --supersedes <id> retires older ones) / append journal entries |
search / get |
hybrid search (summaries) / fetch one full note |
context |
compact query-time context pack (recency-weighted, profile-pinned) |
bench |
score retrieval quality (hit-rate / MRR / recall) against a cases file |
reindex |
rebuild the derived index from scratch |
recall --hook |
emit the SessionStart recall block (push) |
serve |
run the MCP server (pull, cross-AI) |
export / import |
zip the vault for one-shot transfer (Drive, etc.) |
Working. F1–F6 shipped, 28 tests green. ✅ See DESIGN.md for
architecture and roadmap.
- F1 — core: markdown/frontmatter parse, FTS5, incremental index
- F2 —
recall+write+ SessionStart hook (push auto-recall) —hooks/ - F3 — MCP server (
memory_search/get/moc/write) —docs/mcp.md - F4 — portability:
init/sync/clone/export/import - F5 — semantic hybrid search (fastembed + sqlite-vec, RRF), content dedup,
dailyjournaling - F6 —
.mnemo-projectmarker for shared cross-repo project memory
Two repos, never mixed: the public one is this software (generic, zero personal data); your private one is your vault. Embeddings run locally, so your notes never leave your machine. 🏠 (API embeddings are opt-in only.)
PRs and issues welcome. Small Python codebase — core library + two frontends.
Run the suite with pytest, keep notes atomic, and just keep swimming. 🐠
MIT © Emir Furkan Sarı
{ "hooks": { "SessionStart": [ { "matcher": "startup", "hooks": [{ "type": "command", "command": "mnemo --vault \"/ABSOLUTE/path/to/my-memory\" recall --hook --reindex --project-dir \"$CLAUDE_PROJECT_DIR\"", "timeout": 30 }]} ] } }