Persistent memory for Claude Code, retrieved on demand.
Project description
bettermemory
Persistent memory for Claude Code, retrieved on demand — not pre-loaded into every prompt.
bettermemory stores memory as plain markdown on disk and exposes it through MCP tools the model calls when context is needed. The default is no retrieval per turn; when the model does pull a memory in, the contract is to say so in the reply. Files are grep-able, git-versionable, and hand-editable.
Install
For Claude Code:
/plugin marketplace add 0Mattias/bettermemory
/plugin install bettermemory@bettermemory
For any other MCP client (Claude Desktop, Cursor, Continue, Cline), see docs/clients.md. The short form:
uv tool install bettermemory # or: pipx install / pip install
bettermemory init --client claude-desktop
What it looks like
Day one. You say: "When I ask for a tutorial, I want runnable code, not screenshots of an IDE." Claude calls memory_write(category="user-inference"). Because it's a claim about you, the write goes pending. Claude asks: "Want me to remember that?" You confirm. A markdown file lands at ~/.claude-memory/.
Week two, fresh session: "Walk me through pandas from zero to hero." The phrase is ambiguous in a way stored preferences could resolve, so Claude calls memory_search, surfaces the preference, and tells you up front: "Using your stored preference for code-driven tutorials…" before answering.
Month three: "What's the difference between find and fd?" Generic question. Claude doesn't search. The reply isn't tinted by months of accumulated personal context. That's the design point.
Features
- Opt-in retrieval.
memory_searchis a tool the model calls when context is needed. The default per turn is not to call it. - Proactive writing with structural gates. Aggressive writing is safe because a durability check, content/tombstone dedup, scope-mismatch check, and a
user-inferencepending tier guard the writes. - Hybrid retrieval. Four selectable rankers:
keyword(default),bm25,semantic(sentence-transformers), orhybrid(Reciprocal Rank Fusion). Per-call or via config. - Three staleness signals on every hit, folded into a
staleness_verdict∈ {fresh,spot_check_recommended,spot_check_required}: calendar verification age, filesystem path drift, and commit drift against the memory's origin repo. - Claim-level provenance.
memory_record_use(claim_excerpts=[…])logs the load-bearing claim each memory contributed. Audits trace a response back to a specific sentence. - Write-time groundedness gate. Opt-in
memory_write(groundedness_check=True, source_transcript=…)flags sentences that don't anchor to the conversation that produced them. - Negative-results suppression. When a hit was
ignoredorcontradictedrecently and not sinceapplied, it carriesrecent_negative_outcomesso the model doesn't keep re-suggesting the same junk. - Typed inter-memory links.
supersedes/contradicts/extends/depends_on. Surfaced bidirectionally onmemory_show. - Tombstones, not deletes. Removed memories keep their
removed_reason. Tombstone-aware dedup catches paraphrases six months later. Reversible viamemory_restore. - Confirmation tier for claims about you.
category="user-inference"always goes pending regardless of config — misattribution sticks, so the user always gets the veto. - Auto-scoped by repo and worktree. Memories written from a git checkout carry the repo URL and worktree root.
memory_searchfilters by both. Sibling worktrees of the same repo are isolated. - Plain-text storage. No database, no opaque blob.
Where it fits
bettermemory occupies the file-backed, retrieval-on-demand corner of the memory-system design space. Other open-source projects make different choices — graph or vector databases for richer joins, agent-routed tiered memory for context-window management, managed cloud for ops simplicity. The table below sketches those design choices in each system's own terms; it isn't a scorecard.
| bettermemory | mem0 | Letta (MemGPT) | Zep / Graphiti | Cognee | Anthropic Memory Tool | |
|---|---|---|---|---|---|---|
| Retrieval | Tool-call, off by default per turn | Explicit search() API |
Tool-routed across tiered memory | Explicit search() over temporal graph |
Explicit search() (multiple modes) |
List + read, no search |
| Storage | Markdown + YAML on disk | Vector DB (optional graph backend) | Core / recall / archival tiers | Temporal knowledge graph | Graph + vector | Plain-text on disk |
| Verification signals | Calendar + path + commit drift, per-claim attestation | Temporal reasoning | — | Bi-temporal (t_valid + t_created) |
— | — |
| Inter-memory links | Typed (supersedes / contradicts / extends / depends_on) | Graph edges (optional Neo4j) | — | Graph edges (Graphiti episodic) | Graph edges | — |
| Cross-host sync | Built-in git wrapper | Self-host (Docker) or managed cloud | Self-host or managed cloud | Self-host (Graphiti OSS) or managed | Self-host or managed | Provider-managed |
| License | MIT | Apache-2.0 | Apache-2.0 | Apache-2.0 (Graphiti) | Apache-2.0 | Closed |
The differentiators bettermemory leans on — opt-in retrieval as a per-turn default, claim-level provenance on memory_record_use, and the path/commit drift signals folded into staleness_verdict — are spelled out in the Features list above. Other systems target different problems; pick the system whose default behaviour matches what you want.
Coexistence with Claude Code's built-in memory
Claude Code 2.x ships its own filesystem-backed memory that auto-injects into the system prompt. Installing the plugin lands the "persistent memory between sessions lives in this server's MCP tools, do not fragment it across ad-hoc files alongside" anchor in the system prompt, which keeps the model from drifting back to the built-in directory mid-conversation. Manual installs can paste docs/system_prompt.md into CLAUDE.md for the same effect.
On-disk format
One file per memory:
~/.claude-memory/2025-03-14-jupyter-tutorial-style.md
---
schema_version: 1
id: 01HXYZ123ABC
created: 2025-03-14T10:23:00+00:00
updated: 2025-03-14T10:23:00+00:00
scopes: [tools, learning-style]
confidence: high
source: explicit-statement
---
When I ask for a "zero to hero" tutorial, I want a hands-on
walkthrough with code I can run, not a tour of the IDE.
Tombstones move to .tombstones/. Optional fields are written only when populated: origin (cwd + repo + branch + worktree captured at write time), last_verified_at, category, verified_paths / verified_commits / verified_versions, and links.
Storage resolution: $BETTERMEMORY_DIR if set, else ./.claude-memory/ if it exists, else ~/.claude-memory/. Project-scoped overrides global; cross-project queries are explicit (auto_scope=false).
Tools
18 MCP tools, grouped:
- Retrieval —
memory_search,memory_show,memory_list,memory_scope_overview - Writing —
memory_write(plusmemory_write_confirm/memory_write_cancelfor the staged-write flow),memory_update - Lifecycle —
memory_remove,memory_restore,memory_list_tombstones - Verification —
memory_verify - Curation —
memory_record_use,memory_health,memory_audit_turn,memory_rename_scope - Session-local —
memory_scope_disable,memory_scope_enable
Full signatures, defaults, and return shapes in docs/api.md.
CLI
The bettermemory script is the MCP server entry point by default — no args, runs over stdio. It also exposes offline tooling:
bettermemory init --client claude-code # register with a client (idempotent)
bettermemory doctor # diagnose install state
bettermemory health # curation rollup (text or --json)
bettermemory consolidate # dedup + demote + cold-scope + typo passes
bettermemory consolidate --apply # commit dedup + demotions
bettermemory reindex # rebuild FTS5 index from on-disk files
bettermemory sync init --remote URL # git-based cross-host sync
bettermemory sync push | pull | auto | status
bettermemory ui # local FastAPI curation UI (needs [ui] extra)
bettermemory tombstones list | prune
bettermemory export # backup
Performance
Below ~500 memories, search uses load_all (byte-stable to 1.x). Above the threshold (BETTERMEMORY_INDEX_THRESHOLD), an SQLite FTS5 inverted index pre-filters candidates, capping per-search work regardless of corpus size. Files stay canonical; the index is a derived cache at <store>/.index.sqlite, kept live by Store hooks. Recovery from hand-edits: bettermemory reindex.
Config
config.toml is created on first run under platformdirs:
- macOS:
~/Library/Application Support/bettermemory/config.toml - Linux:
~/.config/bettermemory/config.toml - Windows:
%LOCALAPPDATA%\bettermemory\config.toml
Defaults are sensible — most users never edit it. Knobs that matter: behavior.search_mode (keyword / bm25 / semantic / hybrid), behavior.require_write_confirmation (per-write veto; off by default for solo setups, but category="user-inference" always goes pending regardless), behavior.verification_stale_days (default 30), telemetry.enabled (flip to false to disable the event log).
Limitations
- No encryption. Memories are plaintext on disk. Don't store secrets; use OS-level disk encryption if you need it.
- No automatic conflict resolution for sync.
bettermemory syncdelegates to git. True content conflicts surface as normal merge conflicts. - Web UI is read-mostly. Curation and one-click
memory_verifyonly. Writes happen in-conversation. - Disabled scopes don't survive restart. Intentional; each session starts fresh.
- Multi-process locking falls back to no-op on Windows. Single-process recommended there.
Out of scope
- Cloud sync as a service. Sync is git-based; bring your own remote (GitHub, Forgejo, bare repo over SSH).
- Cross-user sharing. Single-user tool. Team scopes are deferred.
- Automatic memory extraction from transcripts. The opt-in retrieval contract loses its meaning if writes happen behind the user's back; bettermemory's writes are always model-initiated and visible in the conversation.
Design notes
The motivating problem is auto-injection: when stored facts get pre-loaded into every conversation, generic questions inherit context they shouldn't. bettermemory's response is to make retrieval a tool call the model makes deliberately, and to make every retrieval visible in the reply. Everything else — the staleness verdict, the user-inference pending tier, the typed links, the groundedness gate — exists to make that deliberate retrieval trustworthy enough to rely on.
Built by Mattias Rask. MIT licensed — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bettermemory-2.3.1.tar.gz.
File metadata
- Download URL: bettermemory-2.3.1.tar.gz
- Upload date:
- Size: 600.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9fbe2b47fa3ac8e76f31027f5aa22a3488a907a1f6a9e5eab2d5dea412e02a6f
|
|
| MD5 |
ab0861249a81774a57ff964f1e8b4cf0
|
|
| BLAKE2b-256 |
821980539c43541f179673fca95a843697b36ebef1501dccf76c3baf81c8d1bf
|
Provenance
The following attestation bundles were made for bettermemory-2.3.1.tar.gz:
Publisher:
release.yml on 0Mattias/bettermemory
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bettermemory-2.3.1.tar.gz -
Subject digest:
9fbe2b47fa3ac8e76f31027f5aa22a3488a907a1f6a9e5eab2d5dea412e02a6f - Sigstore transparency entry: 1586142596
- Sigstore integration time:
-
Permalink:
0Mattias/bettermemory@95099a0d2b90e6f26f07930eba5ec061a80e0b16 -
Branch / Tag:
refs/tags/v2.3.1 - Owner: https://github.com/0Mattias
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@95099a0d2b90e6f26f07930eba5ec061a80e0b16 -
Trigger Event:
push
-
Statement type:
File details
Details for the file bettermemory-2.3.1-py3-none-any.whl.
File metadata
- Download URL: bettermemory-2.3.1-py3-none-any.whl
- Upload date:
- Size: 229.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1e01e5cc0db9095e5eb987fbb8cebe34a975e5080d4ad2a03aea4dc7a407dd5
|
|
| MD5 |
dea74e547f1a93a0b88ba973be67f818
|
|
| BLAKE2b-256 |
faa72c2769f82e401d444e3f58a0b6c782e13597762cfb9aa318cddbd32540a5
|
Provenance
The following attestation bundles were made for bettermemory-2.3.1-py3-none-any.whl:
Publisher:
release.yml on 0Mattias/bettermemory
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bettermemory-2.3.1-py3-none-any.whl -
Subject digest:
f1e01e5cc0db9095e5eb987fbb8cebe34a975e5080d4ad2a03aea4dc7a407dd5 - Sigstore transparency entry: 1586142661
- Sigstore integration time:
-
Permalink:
0Mattias/bettermemory@95099a0d2b90e6f26f07930eba5ec061a80e0b16 -
Branch / Tag:
refs/tags/v2.3.1 - Owner: https://github.com/0Mattias
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@95099a0d2b90e6f26f07930eba5ec061a80e0b16 -
Trigger Event:
push
-
Statement type: