Persistent, structured memory for AI coding agents. pmem stores a project's
conventions, decisions, patterns, and context in a local SQLite database and
serves them to any MCP-compatible agent (Claude Code, Cursor, and others) —
so an agent stops re-deriving the same context every session.
AI coding assistants re-read a codebase from scratch on every conversation.
Decisions explained once get explained again next week. pmem gives a
project a small, queryable memory: short, tagged entries an agent can search,
link, and pull in as context — checked into version control alongside the
code they describe.
- Structured memories — typed entries (convention, pattern, decision, preference, context) with tags and cross-references
- Fuzzy and full-text search — Jaro-Winkler ranking plus SQLite FTS5, with faceted and date-range filters
- MCP integration — stdio transport (Claude Code, Cursor) and an HTTP transport, both speaking the same JSON-RPC protocol
- REST API and web dashboard — browse and query memories outside the CLI
- Snapshots — save, diff, and restore the full memory state
- Duplicate detection — similarity-based merge suggestions
- Encryption at rest — AES-256-GCM with PBKDF2 key derivation for sensitive entries
- Git hooks — auto-export memories on commit
- Code and markdown import — pull in
TODO/FIXMEcomments or existing docs as memories
cargo install --path .Requires a Rust toolchain (2021 edition).
pmem init # create .memory/ in the current project
pmem add --kind convention -k naming -c "Use snake_case" --tags rust
pmem search naming
pmem stdio # serve over MCP stdioPoint an MCP client at it:
{
"mcpServers": {
"project-memory": {
"command": "pmem",
"args": ["stdio"]
}
}
}Every command has its own --help; run pmem help for the full list. The
most commonly used ones:
| Command | Description |
|---|---|
pmem add |
Add a new memory entry |
pmem list / pmem get <id> |
List or show memories |
pmem search <query> |
Fuzzy search |
pmem update / pmem delete |
Modify or remove an entry |
pmem link / pmem related <id> |
Cross-reference entries |
pmem snapshot save|restore|diff |
Save and restore memory state |
pmem dedupe find|merge |
Find and merge duplicate entries |
pmem validate --fix |
Check memory quality and auto-fix issues |
pmem encryption set-key|encrypt|decrypt |
Encrypt sensitive entries |
pmem stdio / pmem serve |
Run the MCP server (stdio or HTTP) |
pmem dashboard / pmem api |
Run the web dashboard or REST API |
The CLI also covers tagging, groups, templates, batch operations, git-hook
integration, backups, and analytics — see pmem help for the complete
reference.
pmem stdio speaks MCP over stdio; pmem serve --port <p> exposes the same
protocol over HTTP at POST /mcp. Both share one request handler, so
behavior is identical across transports. Available tools: memory_add,
memory_search, memory_list, memory_get, memory_delete,
memory_context, memory_link.
pmem api --port 3779Exposes memory CRUD and search over HTTP for integrations that don't speak MCP.
graph LR
CLI["pmem CLI"] --> Store[("MemoryStore\n(SQLite)")]
Stdio["MCP stdio\n(Claude Code, Cursor)"] --> Handler["shared JSON-RPC\nhandler"]
HTTP["MCP HTTP\npmem serve"] --> Handler
Handler --> Store
REST["REST API\npmem api"] --> Store
Dashboard["Web dashboard\npmem dashboard"] --> Store
See docs/ARCHITECTURE.md for how the CLI, storage layer, and server transports fit together.
Full methodology and reproduction steps: docs/BENCHMARK.md.
Everything below was measured against the v0.2.0 release build, not
estimated.
A real, independent MCP client connects to it. OpenCode
— unrelated to this project — was pointed at pmem stdio via a standard
opencode.json config:
$ opencode mcp list
● ✓ project-memory connected
It actually saves tokens, the reason the tool exists. 50 real facts
about this codebase were stored as memories, then queried with 50 separate
memory_search calls (one plausible question per fact) — every one found
its target — and compared against the token cost of reading the 16 source
and doc files those facts live in:
| Tokens | vs. reading the source | |
|---|---|---|
memory_search, mean of 50 queries |
168 (range 149–200) | ~231x cheaper |
memory_context, all 50 facts in one call |
2,022 | ~19x cheaper |
| Reading the 16 files those facts come from | 38,921 | — |
It's fast, and honest about where that stops being true:
| Memories | Insert | Fuzzy search | List all | Stats |
|---|---|---|---|---|
| 100 | 126/sec | 1.1ms | 1.1ms | 0.9ms |
| 1,000 | 128/sec | 3.9ms | 5.4ms | 2.2ms |
| 5,000 | 103/sec | 26.4ms | 29.5ms | 16.7ms |
Growth isn't flat — a known efficiency gap (a couple of store.rs paths
score/aggregate in memory instead of in SQL), not a claim that performance
never degrades. Full table goes to 10,000 in
docs/BENCHMARK.md.
It holds up under concurrent load — and the benchmark caught a real bug
while checking. Building this surfaced that api.rs/dashboard.rs were
running blocking SQLite calls straight on the async executor — the same bug
already fixed once in mcp.rs, just never carried over. Fixed, then
measured: with 100 concurrent searches queued behind MemoryStore's single
SQLite connection (each taking ~125ms), an unrelated /api/health call
stayed at 0.3ms mean — proving one slow store operation can't starve the
rest of the server.
Footprint: 9.9 MB release binary, ~2.1ms cold start (a fresh
pmem stdio process per MCP session), ~450–860 bytes/memory on disk
depending on store size.
cargo build
cargo testFor running the dashboard, REST API, and MCP HTTP server together via Docker, see docs/DEVELOPMENT.md.
MIT
