Agent memory that's just Markdown and one SQLite file. No server, no vector database, no infrastructure.
Give any AI agent long-term memory in about 30 seconds. Your memories are plain
Markdown files you own and can git — minimem indexes them into a single local
SQLite file and serves hybrid semantic search to Claude Code, Codex, Cursor, or
your own code over MCP.
npm install -g minimem && minimem init ~/memoriesThat's the whole setup. No Docker, no Postgres, no Pinecone, no daemon to babysit.
- Zero infrastructure - The entire index is one SQLite file (
sqlite-vecfor vectors, FTS5 for keywords). Nothing to host, nothing to run. - Your memory, in plain files - Memories are Markdown you can read, edit, diff, and version with git. No proprietary store, no lock-in.
- Local-first & portable - Commit the Markdown,
.gitignorethe index, and rebuild it on any machine withminimem sync. - Strong hybrid retrieval - Vector similarity fused with BM25 via reciprocal rank fusion (RRF) — the best-scoring config across the BEIR datasets we tested (results).
- Bring your own embeddings - OpenAI, Gemini, or a fully local model via
llama.cpp(no API key required). Guided setup picks one for you oninit. - Drops into your agent - First-class MCP server for Claude Code, Codex, Cursor, and Claude Desktop, plus a Claude Code plugin — see integrations.
Long-term-memory QA benchmark results for an agent memory pipeline (extract → retrieve → answer), answer model gpt-5.5:
| benchmark | score | retrieval | notes |
|---|---|---|---|
| LongMemEval_S | 93.0% | full pipeline | full 500-question run; ~1.9pp behind Mastra (94.87%) |
| LOCOMO | 79.3% | minimem | 10 multi-session conversations, mem0 judge |
| BEAM | 72.7% | minimem | ICLR 2026 long-horizon memory, 500K-token conversations |
The retrieval finding that earns the LOCOMO/BEAM numbers: minimem's focused hybrid retrieval beats a dump-everything KB baseline by +13pp at 500K tokens and +43pp on LOCOMO, and the gap widens with scale (the KB's context dump truncates; minimem retrieves the right notes regardless of store size). Scores are same-family-judge, not leaderboard-exact. Full methodology, the clean substrate-vs-graph decomposition, and honest per-benchmark caveats: evals/beam/RESULTS.md.
Install globally for the CLI + MCP server (recommended for most users):
npm install -g minimemOr add it as a library dependency:
npm install minimemRequires Node.js 22+ (uses the built-in node:sqlite).
# Initialize a memory directory. init detects an embedding API key in your
# environment; if it doesn't find one, it asks how you'd like to search:
# • a hosted provider (OpenAI/Gemini),
# • a local model (~320 MB, runs offline, no key), or
# • keyword-only for now (upgrade anytime).
minimem init ~/memories
# Add some memories
minimem append "Decided to use PostgreSQL for the main database" --dir ~/memories
# Search your memories
minimem search "database decisions" --dir ~/memories
# Create or update a memory file
minimem upsert "memory/architecture.md" "# Architecture Notes..." --dir ~/memoriesPrefer non-interactive setup? Pass --provider (e.g. minimem init ~/memories --provider local) or --yes to accept the keyword-only default in scripts/CI.
import { Minimem } from 'minimem';
// Create a Minimem instance
const mem = await Minimem.create({
memoryDir: './memories',
embedding: {
provider: 'openai',
openai: { apiKey: process.env.OPENAI_API_KEY }
}
});
// Search memories
const results = await mem.search('database architecture');
for (const result of results) {
console.log(`[${result.score}] ${result.path}:${result.startLine}`);
console.log(result.snippet);
}
// Append to today's log
await mem.appendToday('Reviewed the API design document');
// Clean up
mem.close();| Command | Description |
|---|---|
minimem init [dir] |
Initialize a memory directory (creates files, DB, and indexes) |
minimem search <query> |
Semantic search through memories |
minimem sync |
Force re-index memory files |
minimem status |
Show index stats and provider info |
minimem append <text> |
Append to today's daily log |
minimem upsert <file> [content] |
Create or update a memory file |
minimem mcp |
Run as MCP server (stdio) |
minimem config |
View or modify configuration (--set key=value, --unset key) |
| Command | Description |
|---|---|
minimem store:add <name> <path> |
Register a store in the global manifest (--remote <url> for git-backed stores) |
minimem store:remove <name> |
Remove a store from the global manifest |
minimem store:list |
List all registered stores and their links |
minimem store:link <store> <target> |
Link a store to another for cross-store search |
minimem store:unlink <store> <target> |
Remove a link between stores |
Sync memory directories through a central git repository — write on one machine, recall on another.
| Command | Description |
|---|---|
minimem sync:init-central <path> |
Initialize a central repository for syncing |
minimem sync:init --path <name>/ |
Enable sync for a memory directory |
minimem push / minimem pull |
Push/pull changes to/from the central repository |
minimem sync:status |
Show sync status for a directory |
minimem sync:list |
List all sync mappings |
minimem sync:remove |
Remove sync mapping for a directory |
minimem sync:conflicts |
List quarantined conflicts |
minimem sync:resolve <timestamp> |
Resolve a quarantined conflict with a merge tool |
minimem sync:cleanup |
Clean up old quarantined conflicts |
minimem sync:log |
Show sync history |
minimem sync:validate |
Validate the registry for collisions and stale mappings |
| Command | Description |
|---|---|
minimem daemon |
Start the sync daemon (--background to detach) |
minimem daemon:stop |
Stop the sync daemon |
minimem daemon:status |
Show daemon status |
minimem daemon:logs |
Show daemon logs (--follow to tail) |
-d, --dir <path>- Memory directory (default: current directory)-g, --global- Use~/.minimemas the memory directory-p, --provider <name>- Embedding provider:openai,gemini,local, orauto
# Search with options
minimem search "project decisions" \
--dir ~/memories \
--max 5 \
--min-score 0.5 \
--json
# Search multiple directories
minimem search "api design" \
--dir ~/work-memories \
--dir ~/personal-notes \
--global# Create/update with inline content
minimem upsert "notes.md" "# My Notes" --dir ~/memories
# Pipe content from stdin
cat document.md | minimem upsert "imported.md" --stdin --dir ~/memories
# Use heredoc for multi-line content
minimem upsert "memory/decisions.md" --stdin --dir ~/memories << 'EOF'
# Architecture Decisions
## Database
We chose PostgreSQL for its reliability and JSON support.
## API
REST with OpenAPI documentation.
EOFmy-memories/
├── MEMORY.md # Main memory file (indexed)
├── memory/ # Additional memory files
│ ├── 2024-01-15.md # Daily logs
│ ├── 2024-01-16.md
│ └── projects.md # Topic-specific notes
├── config.json # Configuration
├── index.db # SQLite database with vectors (gitignored)
└── .gitignore # Ignores index.db
Everything lives in the memory directory itself, so it's trivially portable: the Markdown
files and config.json can be committed to git, while the derived index.db is ignored
and rebuilt on any machine with minimem sync.
(Legacy layouts with a .minimem/ subdirectory are still read transparently.)
Configuration is stored in config.json at the memory directory root:
{
"embedding": {
"provider": "auto",
"model": "text-embedding-3-small"
},
"hybrid": {
"enabled": true,
"fusion": "rrf",
"ftsQueryMode": "or"
},
"query": {
"maxResults": 10,
"minScore": 0.3
}
}The values above are the defaults. Set "fusion": "weighted" to use a score-weighted
sum instead of RRF (then vectorWeight/textWeight, default 0.7/0.3, apply). A global
config at ~/.minimem/config.json is layered under each directory's local config.
| Variable | Description |
|---|---|
OPENAI_API_KEY |
OpenAI API key for embeddings |
GOOGLE_API_KEY |
Google/Gemini API key for embeddings |
GEMINI_API_KEY |
Alternative Gemini API key |
MEMORY_DIR |
Default memory directory |
OpenAI (recommended for quality):
export OPENAI_API_KEY=sk-...
minimem search "query" --provider openaiGemini (good free tier):
export GOOGLE_API_KEY=...
minimem search "query" --provider geminiLocal (no API needed, requires setup):
minimem search "query" --provider localAuto (default): Tries OpenAI → Gemini → Local based on available API keys.
minimem can run as an MCP server for integration with Claude Desktop, Cursor, and other tools.
Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"minimem": {
"command": "minimem",
"args": ["mcp", "--global"],
"env": {
"OPENAI_API_KEY": "your-key"
}
}
}
}The MCP server supports searching across multiple memory directories:
{
"mcpServers": {
"minimem": {
"command": "minimem",
"args": ["mcp", "--dir", "/path/to/work", "--dir", "/path/to/personal", "--global"],
"env": {
"OPENAI_API_KEY": "your-key"
}
}
}
}When multiple directories are configured:
- The
memory_searchtool searches all directories by default - Results are merged and ranked by score
- Each result shows which directory it came from
- Use the optional
directoriesparameter to filter to specific directories
Add to Cursor's MCP settings:
{
"mcpServers": {
"minimem": {
"command": "minimem",
"args": ["mcp", "--dir", "/path/to/memories"],
"env": {
"OPENAI_API_KEY": "your-key"
}
}
}
}The fastest way is the Codex CLI, which writes the config for you:
codex mcp add minimem --env OPENAI_API_KEY=your-key -- minimem mcp --globalOr add the table to ~/.codex/config.toml directly (note the snake_case
mcp_servers — a typo here is silently ignored):
[mcp_servers.minimem]
command = "minimem"
args = ["mcp", "--global"]
[mcp_servers.minimem.env]
OPENAI_API_KEY = "your-key"Verify it's live with codex mcp list. The CLI and IDE extension share this
config, so you only set it up once. To scope memory to a single project, use a
trusted-project .codex/config.toml and swap --global for
--dir /path/to/memories.
To nudge Codex (or any agent) to actually use the memory tools, add a short
block to your project's AGENTS.md:
## Memory (minimem)
Before starting a task, call `memory_search` to recall relevant prior context.
When you make a decision or learn something durable, write it to `MEMORY.md`
(or `memory/YYYY-MM-DD.md`) so it's indexed for next time.The MCP server exposes five tools:
| Tool | Purpose |
|---|---|
memory_search |
Semantic search across memory files. Supports maxResults, minScore, directories, detail ("compact" index or "full" snippets), and type (filter by observation type: decision, bugfix, feature, discovery, context, note) |
memory_get_details |
Fetch full text for chunks returned by a compact memory_search — a two-phase workflow that saves tokens |
knowledge_search |
Search knowledge notes with domain/entity/confidence/type filters (see Knowledge Frontmatter) |
knowledge_graph |
Traverse knowledge-graph relationships outward from a note |
knowledge_path |
Find the shortest path between two knowledge notes via graph links |
The three knowledge_* tools are additive: they only return results when
knowledge-formatted notes (YAML frontmatter with type, domain, links, etc.)
are present. Regular memory files work fine without them.
Typical search flow:
// 1. Compact search — lightweight index of matches
memory_search({ query: "api design decisions", maxResults: 10 })
// 2. Fetch full text only for the results that matter
memory_get_details({ results: [{ path, startLine, endLine }] })import { Minimem, type MinimemConfig } from 'minimem';
const config: MinimemConfig = {
memoryDir: './memories',
embedding: {
provider: 'openai',
openai: {
apiKey: process.env.OPENAI_API_KEY,
model: 'text-embedding-3-small' // or text-embedding-3-large
}
},
hybrid: {
enabled: true,
fusion: 'rrf', // default; or 'weighted' (uses vectorWeight/textWeight)
ftsQueryMode: 'or' // default; 'and' requires all query terms to match
},
watch: true, // Auto-sync on file changes
debug: console.log // Optional debug logging
};
const mem = await Minimem.create(config);const results = await mem.search('database architecture', {
maxResults: 10,
minScore: 0.3
});
// Results include:
// - path: relative file path
// - snippet: matching text chunk
// - score: relevance score (0-1)
// - startLine, endLine: line numbers
// - heading: section heading if available// Append to today's daily log (memory/YYYY-MM-DD.md)
await mem.appendToday('Meeting notes: discussed API design');
// Append to specific file
await mem.appendFile('memory/project.md', 'New decision made');
// List all memory files
const files = await mem.listFiles();
// Get status
const status = await mem.status();
console.log(`Files: ${status.fileCount}, Chunks: ${status.chunkCount}`);// Manual sync (usually automatic)
await mem.sync();
// Force full re-index
await mem.sync({ force: true });import { createEmbeddingProvider } from 'minimem';
// Create provider with custom settings
const provider = createEmbeddingProvider({
provider: 'openai',
openai: {
apiKey: process.env.OPENAI_API_KEY,
model: 'text-embedding-3-large',
dimensions: 1536
}
});
// Or use Gemini
const geminiProvider = createEmbeddingProvider({
provider: 'gemini',
gemini: {
apiKey: process.env.GOOGLE_API_KEY,
model: 'text-embedding-004'
}
});┌─────────────────────────────────────────────────────────────┐
│ minimem │
├─────────────────────────────────────────────────────────────┤
│ Memory Files (.md) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │MEMORY.md│ │daily.md │ │notes.md │ ← Plain Markdown │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └───────────┴───────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Chunking │ │
│ │ Split by headings/paragraphs │ │
│ │ ~256 tokens per chunk │ │
│ └──────────────────┬──────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Embedding Provider │ │
│ │ OpenAI / Gemini / Local │ │
│ │ text → [0.1, -0.3, 0.8, ...] │ │
│ └──────────────────┬──────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ SQLite Database │ │
│ │ ┌─────────┐ ┌─────────────────┐ │ │
│ │ │ FTS5 │ │ sqlite-vec │ │ │
│ │ │ (text) │ │ (vectors) │ │ │
│ │ └─────────┘ └─────────────────┘ │ │
│ └──────────────────┬──────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Hybrid Search │ │
│ │ Vector similarity + BM25 ranking │ │
│ │ Reciprocal rank fusion (RRF) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
- File Discovery: Scans for
MEMORY.mdandmemory/*.mdfiles - Chunking: Splits content by Markdown headings and paragraphs (~256 tokens each)
- Hashing: Each chunk gets a content hash to detect changes
- Embedding: New/changed chunks are sent to the embedding provider
- Storage: Chunks and vectors stored in SQLite with sqlite-vec extension
- Caching: Embeddings cached by content hash to avoid re-computation
- Query Embedding: Convert search query to vector using same embedding model
- Vector Search: KNN search using the sqlite-vec index (cosine distance)
- Text Search: Find matching chunks using FTS5 full-text search (BM25, OR-mode)
- Hybrid Fusion: Combine both ranked lists with reciprocal rank fusion (RRF)
- Ranking: Sort by fused score (max-normalized), apply min-score filter
Pure vector search excels at semantic similarity but can miss exact matches. Pure text search finds exact terms but misses synonyms. Hybrid search combines both:
- Vector: Finds conceptually related content ("database" matches "PostgreSQL")
- Text (BM25): Boosts exact keyword matches ("PostgreSQL" query ranks PostgreSQL mentions higher)
The default fusion is RRF (reciprocal rank fusion): it merges the two result
lists by rank position instead of mixing raw scores, so it needs no score
normalization and is robust across embedding models. On BEIR retrieval
benchmarks, RRF was the best (or tied-best) configuration on every dataset and
embedding model tested, while score-weighted fusion collapsed when switching
embedding models — see docs/RETRIEVAL-EVAL-RESULTS.md
for the full numbers. A score-weighted sum is available via
hybrid.fusion: "weighted".
SQLite database (index.db in the memory directory) with these main tables:
-- File metadata and modification tracking
CREATE TABLE files (path, source, hash, mtime, size);
-- Content chunks with embeddings and optional knowledge metadata
CREATE TABLE chunks (
id, path, source, start_line, end_line, hash, model, text, embedding,
type, -- observation type (decision, bugfix, ...)
knowledge_type, knowledge_id, -- knowledge frontmatter metadata
domains, entities, confidence,
updated_at
);
-- Full-text search index (BM25)
CREATE VIRTUAL TABLE chunks_fts USING fts5(text, id, path, ...);
-- Vector KNN index (when sqlite-vec is available; dims are model-dependent)
CREATE VIRTUAL TABLE chunks_vec USING vec0(embedding FLOAT[<dims>]);
-- Knowledge graph edges (from knowledge frontmatter links)
CREATE TABLE knowledge_links (from_id, to_id, relation, layer, weight, ...);Embeddings are cached by provider, model, and content hash:
CREATE TABLE embedding_cache (
provider, model, provider_key, hash, embedding, dims, updated_at,
PRIMARY KEY (provider, model, provider_key, hash)
);This means:
- Identical text always produces the same embedding (deterministic)
- Moving/copying chunks doesn't require re-embedding
- Switching files with same content is instant
- The cache survives schema migrations, so re-indexing is cheap
A ready-to-use Claude Code plugin is included in the claude-plugin/ directory.
# Install minimem globally
npm install -g minimem
# Initialize global memory
minimem init --global
# Test the plugin
claude --plugin-dir /path/to/minimem/claude-plugin- MCP Server: all five memory/knowledge tools (see Available MCP Tools)
- Memory Skill: Auto-invoked for storing/recalling context
- Commands:
/minimem:remember <text>- Store information/minimem:recall <query>- Search memories
- Session hooks (opt-in): inject recent memories at session start, log a
marker at session end. Enable with
minimem config --set hooks.sessionStart=trueandhooks.sessionEnd=true.
See claude-plugin/README.md for detailed documentation.
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm run test:all # All tests
npm run test # Unit tests (vitest)
npm run test:cli # CLI command tests
npm run test:integration # E2E integration tests
# Development mode
npm run devMIT