One memory for Claude Code, Codex and Cursor, kept in plain Markdown files you own, searched and served entirely on your own machine.
pureMind indexes a directory of Markdown notes (an Obsidian vault works as is), builds a local search index, and exposes it to your coding agents over MCP. Every agent reads the same notes, so what you write down once is available to all of them. Once the two models are downloaded, nothing leaves your computer.
Python 3.11 or newer, on macOS (Apple Silicon) or Linux.
pureMind for Mac is not published on PyPI. Install it from this repository:
pipx install git+https://github.com/puretensor/puremind-mac
puremind --helpOn Linux without an NVIDIA GPU, add --pip-args='--extra-index-url https://download.pytorch.org/whl/cpu' to that pipx install to get the CPU build of PyTorch, which is a fraction of the size of the default CUDA build. Intel Macs are not supported: the PyTorch releases pureMind needs have no Intel macOS builds. With Python 3.14 on a Mac, pip builds watchdog from source, which needs the Xcode command line tools.
Two models are downloaded into the Hugging Face cache the first time they are needed, about 3.5 GB in total: Qwen/Qwen3-Embedding-0.6B (1.2 GB, fetched by the first puremind index) and BAAI/bge-reranker-v2-m3 (2.3 GB, fetched by the first search). Both are Apache-2.0. On Apple Silicon they run on the GPU through MPS; on Linux they use CUDA if present, else the CPU.
puremind init ~/Notes # writes ~/.puremind/config.toml
puremind index # chunks, embeds and stores every note (incremental)
puremind search "how did I set up the espresso grinder"
puremind connect claude-code # prints the command that registers pureMind with your agentThe first puremind index embeds every note, which takes from seconds to many minutes depending on the size of the vault and the machine. Run one search from the terminal before you connect an agent, so the reranker download does not happen inside an agent's first tool call.
Re-run puremind index after editing notes, or run puremind watch to re-index automatically once the vault has been quiet for a couple of seconds. Only files whose SHA-256 changed are re-embedded.
puremind connect <agent> prints the exact command or snippet and tells you where it goes. It never edits your agent's config files.
| Agent | Command | What it prints |
|---|---|---|
| Claude Code | puremind connect claude-code |
a claude mcp add --scope user puremind ... command |
| Codex | puremind connect codex |
a codex mcp add puremind ... command, or the entry for ~/.codex/config.toml |
| Cursor | puremind connect cursor |
the entry for ~/.cursor/mcp.json |
The snippet uses the absolute path of the puremind executable and carries any PUREMIND_*, HF_HOME, HF_HUB_CACHE, HF_HUB_OFFLINE, TRANSFORMERS_OFFLINE and HF_ENDPOINT variables set in your shell, because GUI apps such as Cursor do not inherit your shell's environment.
The MCP server is read-only and offers exactly three tools, all marked read-only for the client:
| Tool | Purpose |
|---|---|
puremind_search(query, limit=10) |
Hybrid search. Returns up to 50 matching passages, best first, each with its note path, heading path and score. |
puremind_get(path) |
Reads one note by the path search returned. It serves only the notes the indexer reads (see Vault below) and cuts text beyond 200,000 characters, saying so. |
puremind_recent(days=7, limit=20) |
Lists the notes modified in the last days days, newest first, as of the last index run. |
Each agent session starts its own server process. Tool calls may overlap: every search is answered from one consistent snapshot of the index, even while puremind watch updates it.
Scores. With the reranker, a score is the cross-encoder's probability from 0 to 1. Without it (--no-rerank, or rerank = false), it is a reciprocal rank fusion value, at most about 0.033. puremind search --lexical-only prints BM25 keyword scores. Compare scores only within one result list. There is no relevance cut-off, so a search always returns up to limit passages, and weak ones are still listed.
- Vault. The indexer reads regular
.mdand.markdownfiles of at most 2 MiB. It skips hidden files and folders (such as.obsidianand.git),node_modulesand__pycache__(in any letter case), symlinks that resolve outside the vault or to a skipped file, pipes, names that are not valid UTF-8, and paths inside the vault longer than 1,024 characters. A note that is replaced by such a symlink during a run is not read through it. A note it cannot read keeps its previous index entry. Notes below a folder that cannot be listed are kept in the index, nothing is removed when the vault looks empty while the index is not, and the run says why. - Chunker. Heading-aware: splits on
#to####and keeps a breadcrumb such asProject > Setup. Headings inside fenced code blocks and YAML frontmatter do not count. A heading with no text of its own joins the subsection under it, and most sections under 100 characters merge with the one before. A section longer than 2,048 characters is cut into near-even windows with about 20% overlap, preferably at blank lines. A code block or frontmatter that fits in a window is usually kept whole, but one that is longer, or that starts late in a long section, can be cut; every part of a cut block keeps its own opening and closing fence lines. Frontmatter of 100 characters or more is a chunk of its own. - Store. One SQLite file,
~/.puremind/index.sqlite, readable only by you: files and hashes, chunks, an FTS5 keyword index (used bypuremind search --lexical-only), and float16 vectors. Vectors are loaded into a numpy matrix for dense search. - Retrieval. BM25 over English-stemmed words (top 50) and Qwen3-Embedding-0.6B cosine search (top 50, 1024 dimensions, with the query instruction prefix) are fused with reciprocal rank fusion (k=60). The bge-reranker-v2-m3 cross-encoder then re-scores the 50 fused candidates, reading the first 800 characters of each.
--no-rerankskips that last step. - Languages. The embedder is multilingual, so search by meaning works in most languages. Keyword matching uses an English stemmer. In hybrid search, Chinese, Japanese and Korean text is matched by single characters and overlapping pairs of characters;
search --lexical-onlydoes not do this, and treats an unspaced run of those characters as one word. Thai, Lao, Khmer and Burmese are not split into words for keyword matching. In scripts that need more than one token per character, such as Telugu, Burmese, Khmer and Ethiopic, a full-length chunk is longer than the 2,048 tokens the embedder reads, so search by meaning misses its end (keyword search does not); raiseembed_max_seqto about 3,200 if most of your notes are in such a script. - Watcher.
puremind watchindexes once, then again each time the vault has been quiet for--debounceseconds (default 2) after a change to a visible note or folder. Reads, hidden folders and other file types never trigger a run, and runs never overlap. It follows a vault directory that is replaced or made again (for example by a remount or a sync tool) or that cannot be read for a while, and if nothing has happened for 10 minutes it scans anyway, in case a change was missed. A run that fails is reported on one line and watching goes on, except when no later run could succeed (a vault symlink that now names another directory than the index's, or a system limit such as the inotify watch limit): thenpuremind watchstops with an error.
Each puremind serve process loads the models on its first search, which takes several seconds. Measured on Linux on 8 CPU cores, with the CPU build of PyTorch and current releases of the libraries:
| With the reranker | With serve --no-rerank |
|
|---|---|---|
| Resident memory per server | about 3 to 3.5 GB, up to about 3.8 GB while reranking | about 1.5 GB |
| A search after the first | 10 to 20 seconds | about 0.3 seconds |
The reranker costs about 0.2 seconds per candidate on a CPU, so with the default 50 candidates it takes most of the search time. On a machine without a GPU, use --no-rerank, or lower candidates, if that is too slow. On Apple Silicon both models run on the GPU, which we have not measured yet. An idle server that has not searched yet uses under 100 MB. Older transformers 4.x releases load the models as 32-bit floats and need about twice the memory.
~/.puremind/config.toml is written by puremind init (and rewritten, keeping your settings, by puremind init --force). Environment variables override it.
| Setting | In config.toml | Environment variable | Default |
|---|---|---|---|
| Vault directory | vault |
PUREMIND_VAULT |
set by init |
| Index file | db |
PUREMIND_DB |
~/.puremind/index.sqlite |
| Embedding model | [models] embedder |
PUREMIND_EMBED_MODEL |
Qwen/Qwen3-Embedding-0.6B |
| Embedding model revision | [models] embedder_revision |
PUREMIND_EMBED_REVISION |
a pinned commit, see below |
| Reranker model | [models] reranker |
PUREMIND_RERANK_MODEL |
BAAI/bge-reranker-v2-m3 |
| Reranker revision | [models] reranker_revision |
PUREMIND_RERANK_REVISION |
a pinned commit, see below |
| Device | [models] device |
PUREMIND_DEVICE |
auto (mps, then cuda, then cpu) |
| Tokens of each chunk the embedder reads | [models] embed_max_seq |
2048 |
|
| Candidates from each retriever | [retrieval] candidates |
50 |
|
| Reciprocal rank fusion constant | [retrieval] rrf_k |
60 |
|
| Rerank the candidates | [retrieval] rerank |
true |
|
| Characters of each passage the reranker reads | [retrieval] rerank_chars |
800 |
|
| Config directory | PUREMIND_HOME |
~/.puremind |
- Model revisions. The default models are pinned to
Qwen/Qwen3-Embedding-0.6Bat97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3andBAAI/bge-reranker-v2-m3at953dc6f6f85a1b2dbfca4c34a2796e7dde08d41e. Another model uses its repository's default branch as it was at the first download, and is not refreshed afterwards unless you set a revision or clear the cache. - Model names. A name is a Hugging Face model id unless it starts with
/,./,../or~, which mark a local directory. A directory in your working directory is never picked up by accident. Models load withtrust_remote_codeoff. - Changing the embedding model, or
embedder_revision, takespuremind index --rebuild, since vectors from different models are not comparable. Until thenpuremind indexandpuremind watchstop with an error and change nothing, and searches are refused. Apuremind watchkeeps the model it started with, so restart it after the change; one left running stops instead of wiping the rebuilt index. With--rebuild, the new model must first load and embed a test passage, so a mistyped name leaves the existing index untouched. The index is then emptied, and searches find nothing until the run finishes.puremind index --rebuildwith the same model, by contrast, replaces one note at a time, so searches keep working. - The reranker window. 800 characters is the setting the benchmark below was measured with. Raising
rerank_charslets the reranker see more of each passage, at the cost of slower searches. - Indexes from 0.1.1 or earlier are brought up to date by the next
puremind indexorpuremind watchrun, which cuts and embeds every note again. It takes about as long as the first index did, and searches keep working meanwhile.
Once both models are in the Hugging Face cache, pureMind makes no network requests: models load from the cache at their pinned revisions, and Hugging Face telemetry is turned off unless you have set HF_HUB_DISABLE_TELEMETRY yourself. The only network use is the one-time download, which sends your Hugging Face token if you have one configured. Set HF_HUB_OFFLINE=1 to forbid downloads altogether; a model that is not downloaded then fails at once with a clear message. A cached copy that lacks a file the models need (after an interrupted download, or one made by another tool) is completed on the next run with network access, and reported by name when offline.
The index is a second copy of your notes' text. It is created readable only by you (mode 0600 in a 0700 directory), and an index, ~/.puremind directory or config.toml that an earlier version left more open is tightened when pureMind next uses it. When a note is deleted or edited, the index run that notices it removes the old text from the index file and its -wal file; if another process is reading the index at that moment, the -wal file is emptied by a later run. The keyword index keeps the old words a little longer under puremind watch: it is compacted once the vault has been quiet for a minute, and at the latest five minutes after the change, rather than after every save. A watch that stops first compacts on its way out, and one that crashes leaves a note in the index so the next puremind index or puremind watch does it. puremind index compacts at the end of every run. Text deleted while you used 0.1.1 or earlier is removed when 0.1.2 first opens the index. Copies of the index in backups or snapshots still hold it; to remove everything, delete ~/.puremind/index.sqlite and the -wal and -shm files beside it, then run puremind index again.
| Command | What it does |
|---|---|
puremind init <vault_dir> [--force] |
Writes config.toml. |
puremind index [vault_dir] [--rebuild] |
Indexes the vault incrementally; --rebuild embeds every note again, and is needed after changing the embedding model. |
puremind watch [vault_dir] [--debounce SECONDS] |
Indexes, then re-indexes when notes change. |
puremind search <query> [--limit N] [--no-rerank] [--lexical-only] |
Searches from the terminal; --limit is 1 to 50. |
puremind serve [--no-rerank] |
Runs the MCP server on stdio. Your agent starts this for you. |
puremind connect <claude-code|codex|cursor> |
Prints the setup snippet for an agent. |
Exit status: 0 on success, 1 when a search finds nothing, 2 for an error such as a missing vault, a broken config file, a model that cannot load or an index file that cannot be written (reported on an error: line, sometimes followed by a hint), and 130 after Ctrl-C.
- Not a cloud service. There is no account, no sync and no telemetry.
- No write path yet. The MCP server cannot create or edit notes. You write the notes; agents read them.
- No app yet. v0.1 is a command line tool and a stdio server. There is no menu-bar app, no installer and no auto-start.
- Not a capture tool. It does not record agent sessions. It searches the notes you already have.
We measured the retrieval design in this repository on LongMemEval_s (500 questions, each with about 50 sessions of chat history) against claude-mem v13.39.0. pureMind is not affiliated with claude-mem. The numbers come from our own harness; read them with the limits listed under the table.
| System | Session Recall@5 | Judged QA accuracy |
|---|---|---|
| This design: BM25, Qwen3-Embedding-0.6B, bge-reranker-v2-m3 | 0.971 | 90.4% |
| claude-mem v13.39.0, storage and search engine only (see below) | 0.928 | 78.2% |
- Session Recall@5 is the share of a question's evidence sessions that appear in the top 5 results, averaged over the 470 questions that have evidence. The other 30 are abstention questions with nothing to find.
- Judged QA accuracy covers all 500 questions. A reader model (
claude-sonnet-5-5) answered from the top 10 retrieved exchanges, and a judge model (claude-opus-5-5) graded each answer with the official LongMemEval per-type prompts. A second judge agreed on 99 of 100 sampled verdicts. The QA difference is +12.2 percentage points (95% confidence interval +9.0 to +15.6).
Limits:
- Not this package on a Mac. The design ran in our benchmark harness, with the models served from an NVIDIA GPU (the embedder in bfloat16, reading whole exchanges). No Mac hardware was used.
bench/longmemeval.pyreproduces the retrieval half with this package's code; it does not run the reader or the judge. - claude-mem was not used the way its users use it. Both systems received the same chat exchanges, one chunk per user and assistant exchange, cut by the benchmark harness (not by this package's Markdown chunker). claude-mem got them through its import API, one observation per exchange, so its LLM observer, which normally writes short summaries and facts, was bypassed. Searches passed an explicit full date range, because the default 90-day window drops older memories to keyword search only (Recall@5 0.896 and QA 79.0% in that configuration). This compares retrieval engines fed identical text, not the two memory products.
- No reranker in claude-mem. Part of the gap is the cross-encoder. pureMind's server stack scored Recall@5 0.942 without its reranker, against claude-mem's 0.928; the reranker added 0.035.
- Embedding window. claude-mem's default embedder reads only the first 256 tokens of a document. Re-running it with every exchange split into facts of at most 256 tokens left its Recall@5 at 0.921 and its QA at 76.0%.
- Home field. The harness's exchange chunking and answer prompt were developed in part against LongMemEval_s in August 2026, before this comparison.
- Patched dependencies. To let ingestion finish in time, two performance patches were applied to claude-mem's Chroma dependencies during the run (a cached ONNX session and a narrower duplicate-id check). They left its vectors bit-identical and were reverted afterwards.
- Exploratory arm. The study protocol was written before any scored run but is not published. Its primary endpoints were set on pureMind's larger server stack (Qwen3-Embedding-4B), which scored Recall@5 0.977 and QA 90.4% against claude-mem's 0.928 and 78.2% (Holm-corrected p = 0.0002 and p < 0.0001). The design in this repository was an exploratory arm of the same study, and its QA run was added after the primary QA result was known. It was statistically indistinguishable from the server stack.
- Scope. The benchmark covers retrieval and question answering over chat history. It says nothing about claude-mem's other features, or about how either system does on your own notes.
To run the retrieval benchmark yourself, see bench/README.md.
- v0.2: a write path through a human-approved inbox. Agents propose memories, you approve them, and approved memories become Markdown files in your vault.
- v0.3: a menu-bar app that runs the watcher and server, with first-run setup for the supported agents.
CI runs the test suite on macOS (Apple Silicon) and Linux with Python 3.11, 3.12 and 3.13, and on Linux with 3.14. The tests use stand-in models, and a weekly job runs a smoke test with the real models on Linux. The full models have not yet been run on a Mac. See CONTRIBUTING.md to run the tests, the linter and the other CI checks yourself.
Apache-2.0. Copyright PureTensor, Inc. See LICENSE and NOTICE.
Claude Code, Codex, Cursor and claude-mem are trademarks of their owners. pureMind is not affiliated with or endorsed by them.