All apps · 0 apps
mem-zero
Docker app from sworcery's 2nd Repository
Overview
Readme
View on GitHub
Self-hosted memory server for AI coding assistants. Store, search, and manage persistent context across sessions — so your tools remember what happened last week without stuffing everything into the context window.
Each project gets its own isolated vector collection. When you store a memory, an LLM extracts atomic facts, deduplicates against existing memories, and embeds them for semantic search. The result is a clean, searchable memory store per project that any MCP client or HTTP-capable tool can query.
Why?
AI coding assistants forget everything between sessions. Every new conversation starts from scratch — you re-explain decisions, re-discover bugs, repeat yourself. mem-zero fixes that by giving your assistant persistent project memory:
- Session 50 knows what session 1 figured out — bugs fixed, decisions made, preferences learned
- Selective retrieval — semantic search pulls in only what's relevant, not entire conversation logs
- Project-isolated — memories from one project never leak into another
- Self-contained — runs as a single Docker container with an embedded LLM, no external dependencies required
Quick start
docker run -d \
--name mem-zero \
-p 8765:8765 \
-v mem-zero-data:/mem-zero/storage \
ghcr.io/sworcery/mem-zero:latest
That's it. The bundled LLM handles fact extraction and embeddings out of the box. First startup downloads models (~2 GB) and takes a few minutes — subsequent starts are fast.
How it works
- Text comes in via MCP or REST API
- LLM extracts atomic facts (e.g. "User prefers Python over R")
- Each fact is checked against existing memories for duplicates
- Novel facts are embedded and stored; duplicates are merged or skipped
Connecting your tools
Claude Code, the CLI, and the REST API
Claude Code (MCP)
claude mcp add mem-zero --transport http \
"http://your-host:8765/mcp/your-project-slug/http/your-user-id" \
-s local
CLI
pip install mem-zero
mem-zero-cli --url http://your-host:8765 --api-key your-key projects
The CLI provides direct terminal access to all memory operations:
# List projects
mem-zero-cli projects
# Add a memory
mem-zero-cli add my-project "Chose PostgreSQL over Redis for session storage"
# Pipe text from stdin
echo "User prefers dark mode" | mem-zero-cli add my-project -
# Search
mem-zero-cli search my-project "database decision"
# List memories
mem-zero-cli list my-project
# Export/import for backup and migration
mem-zero-cli export my-project -o backup.json
mem-zero-cli import backup.json --project new-project
# Health check
mem-zero-cli health
# Diagnostics
mem-zero-cli stats
mem-zero-cli stats --project my-project
All commands support --json for machine-readable output. Run mem-zero-cli --help for full usage.
REST API (any tool)
Anything that can make HTTP requests can use mem-zero directly:
# Store a memory
curl -X POST http://your-host:8765/api/v1/projects/my-project/memories \
-H "Content-Type: application/json" \
-d '{"text": "Switched from Redis to PostgreSQL for session storage because we need ACID transactions"}'
# Search memories
curl -X POST http://your-host:8765/api/v1/projects/my-project/search \
-H "Content-Type: application/json" \
-d '{"query": "database decision", "top_k": 5}'
The project slug must start with a letter or number, followed by lowercase alphanumeric characters, hyphens, or underscores (1-63 chars). Each unique slug creates an isolated collection.
Other coding agents — Codex, Copilot, Goose, opencode, OpenClaude, Qwen Code, Grok Build
mem-zero exposes a standard streamable-HTTP MCP endpoint with Bearer auth, so any MCP-capable agent connects without server-side changes. Everything below points at the same URL:
http://your-host:8765/mcp/your-project-slug/http/your-user-id
Export your key first so it stays out of the config files:
export MEM_ZERO_API_KEY="your-key"
OpenAI Codex CLI
~/.codex/config.toml (or .codex/config.toml for a single repo). A url key
implies streamable HTTP — there is no transport field to set:
[mcp_servers.mem-zero]
url = "http://your-host:8765/mcp/your-project-slug/http/your-user-id"
bearer_token_env_var = "MEM_ZERO_API_KEY"
GitHub Copilot (VS Code, Visual Studio, JetBrains, Xcode)
.vscode/mcp.json — prompts once for the key, then stores it:
{
"inputs": [
{ "type": "promptString", "id": "mem-zero-key", "description": "mem-zero API key", "password": true }
],
"servers": {
"mem-zero": {
"type": "http",
"url": "http://your-host:8765/mcp/your-project-slug/http/your-user-id",
"headers": { "Authorization": "Bearer ${input:mem-zero-key}" }
}
}
}
opencode
opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"mem-zero": {
"type": "remote",
"url": "http://your-host:8765/mcp/your-project-slug/http/your-user-id",
"enabled": true,
"headers": { "Authorization": "Bearer {env:MEM_ZERO_API_KEY}" }
}
}
}
Goose
~/.config/goose/config.yaml — headers support ${VAR} substitution:
extensions:
mem-zero:
enabled: true
type: streamable_http
name: mem-zero
uri: http://your-host:8765/mcp/your-project-slug/http/your-user-id
headers:
Authorization: "Bearer ${MEM_ZERO_API_KEY}"
timeout: 300
OpenClaude
Not to be confused with opencode above — OpenClaude is a Claude Code-compatible CLI
that runs against any provider, including a local Ollama. Its config is Claude
Code's, so --scope project writes the same .mcp.json:
openclaude mcp add --transport http mem-zero \
"http://your-host:8765/mcp/your-project-slug/http/your-user-id" \
--header "Authorization: Bearer ${MEM_ZERO_API_KEY}" \
--scope project
This pairs well with mem-zero if you want the whole stack local: OpenClaude driving an Ollama model, with mem-zero's Ollama backend doing extraction and embeddings.
Qwen Code
settings.json — note it uses httpUrl (not url) for streamable HTTP:
{
"mcpServers": {
"mem-zero": {
"httpUrl": "http://your-host:8765/mcp/your-project-slug/http/your-user-id",
"headers": { "Authorization": "Bearer ${MEM_ZERO_API_KEY}" },
"timeout": 300000
}
}
}
Grok Build (xAI)
grok mcp add --transport http mem-zero \
"http://your-host:8765/mcp/your-project-slug/http/your-user-id" \
--header "Authorization: Bearer ${MEM_ZERO_API_KEY}"
Or in ~/.grok/config.toml:
[mcp_servers.mem-zero]
transport = "http"
url = "http://your-host:8765/mcp/your-project-slug/http/your-user-id"
headers = { "Authorization" = "Bearer ${MEM_ZERO_API_KEY}" }
Editors with their own MCP support
Cursor, Windsurf, Cline, Zed, and JetBrains Junie each have an MCP settings panel —
add the same URL and Authorization: Bearer header. This is also how you use
mem-zero alongside a non-Anthropic model such as grok-code-fast-1: the editor
handles MCP, the model just sees the tools.
Agents without MCP support (Aider, for example) can still use the REST API or the
mem-zero-cli shown above.
Teaching your agent to use memory
Connecting the server only makes the tools available. Agents won't store or
search memories on their own — they need instructions. Put these in the file your
agent reads at the repo root: AGENTS.md works for Codex, Copilot, Cursor,
Windsurf, Amp, Zed, Junie, Aider, Devin, and Claude Code; Claude Code and Grok
Build also read CLAUDE.md, and Gemini CLI uses GEMINI.md.
Copy-paste block for AGENTS.md
## Project memory (mem-zero)
This project has a persistent memory server available over MCP.
**Search first.** At the start of every session, search memory for prior context
on whatever you're about to work on. Do this before reading code — it will tell
you about decisions and dead ends the code doesn't explain.
**Store at checkpoints**, not continuously: after completing a feature, fixing a
bug, or making a decision worth remembering.
Store:
- Decisions and the reasoning behind them, especially when alternatives were
rejected ("chose X over Y because Z")
- Non-obvious workarounds and gotchas — things that would surprise a future reader
- Dead ends from debugging, so the next session doesn't repeat the investigation
- Conventions and preferences that aren't written down anywhere else
Do NOT store:
- Anything `git log`, `grep`, or the code itself already answers — function
signatures, file structure, what a method does
- Per-file change lists, version bumps, or test results
- Fragments that can't stand alone ("Root cause", "Solution")
**Quality bar:** each memory is one complete, self-contained sentence that a
future session can understand with no other context. Name systems and tools
explicitly rather than saying "this" or "it". One memory per decision, not one
per file touched.
`delete_memories` and `delete_all_memories` are destructive — always ask before
calling them.
LLM backends
Ollama (recommended), bundled (default), and OpenAI-compatible
mem-zero supports three LLM backends for fact extraction and deduplication.
| Backend | LLM | Embeddings | Setup |
|---|---|---|---|
| ollama (recommended) | Any Ollama model | Any Ollama embedding model | Set OLLAMA_BASE_URL |
| bundled (default) | Qwen2.5-3B (built-in, CPU) | nomic-embed-text via fastembed | Zero config — just run the container |
| openai (beta) | Any OpenAI-compatible API | Any OpenAI-compatible embeddings | Set OPENAI_API_KEY |
Auto-detection: If LLM_BACKEND is not set, the backend is chosen automatically:
OPENAI_API_KEYpresent →openaiOLLAMA_BASE_URLpresent →ollama- Neither →
bundled
Using with Ollama (recommended)
Ollama gives the best results. The bundled 3B model works for basic use, but a 7B+ model on GPU produces significantly better fact extraction. If you have a dedicated GPU, qwen2.5:14b is the sweet spot for quality vs. resource usage.
docker run -d \
--name mem-zero \
-p 8765:8765 \
-v mem-zero-data:/mem-zero/storage \
-e OLLAMA_BASE_URL=http://your-ollama-host:11434 \
-e LLM_MODEL=qwen2.5:14b \
ghcr.io/sworcery/mem-zero:latest
Fallback: When using Ollama, if the server is unreachable, requests automatically fall back to the bundled 3B model. The fallback is lazy — it only loads into memory on the first failure. Extraction quality is reduced but the service stays available.
Using the bundled backend
The bundled backend runs a quantized Qwen2.5-3B model on CPU with no external dependencies. It handles embeddings well and provides basic fact extraction. For better extraction quality, use Ollama or an OpenAI-compatible API.
Using with OpenAI-compatible APIs (beta)
docker run -d \
--name mem-zero \
-p 8765:8765 \
-v mem-zero-data:/mem-zero/storage \
-e OPENAI_API_KEY=sk-... \
ghcr.io/sworcery/mem-zero:latest
Works with any OpenAI-compatible API (OpenAI, Groq, Together, etc.) by setting OPENAI_BASE_URL. This backend has not been extensively tested — if you encounter issues, please report them.
Web dashboard
Management UI at the root URL — health, projects, memories, charts
A management UI is served at the root URL (http://your-host:8765/). From the dashboard you can:
- Monitor system health, uptime, and live performance charts
- Browse all projects and their memory counts
- View, search, and delete memories per project
- Consolidate similar memory fragments
- Delete entire projects
- Add new memories manually
Enable DIAGNOSTICS_ENABLED=true to see performance metrics, accuracy stats, score distributions, and error tracking on the home page.
Optionally protect it with basic auth via DASHBOARD_USER and DASHBOARD_PASS.
If API_KEY is set but no dashboard credentials are, the dashboard prompts once for the API key when it first gets a 401 and keeps it in sessionStorage for that browser tab. It is never written to localStorage, the URL, or the page source.
Authentication
API keys for REST/MCP, plus dashboard basic auth
Set API_KEY to protect all API and MCP endpoints. When set, requests must include the key as a Bearer token:
curl -H "Authorization: Bearer your-api-key" \
http://your-host:8765/api/v1/projects
For MCP clients, add the header to your client config. In Claude Code's .mcp.json:
{
"mem-zero": {
"type": "http",
"url": "http://your-host:8765/mcp/my-project/http/my-user",
"headers": {
"Authorization": "Bearer your-api-key"
}
}
}
A query parameter (?api_key=your-key) is also accepted for clients that can't set headers.
If API_KEY is not set, all endpoints are open — suitable for trusted networks.
The dashboard has its own basic auth (DASHBOARD_USER/DASHBOARD_PASS) since browsers need a login prompt rather than Bearer tokens.
If you set dashboard credentials without an API_KEY, those same Basic-auth credentials now also protect /api, /mcp, and /debug. Previously they were left open behind a password-protected dashboard. With both set, the dashboard accepts either: a logged-in browser session or a Bearer token.
MCP tools
The five tools exposed to MCP clients
| Tool | Description |
|---|---|
add_memories(text) |
Extract and store facts from text (up to 50,000 chars) |
search_memory(query, top_k) |
Semantic search within the project; query is capped at 2,000 chars, top_k at 100 |
list_memories(limit) |
Returns {"memories": [...], "total": N, "truncated": bool} (default limit 100, max 1000) |
delete_memories(memory_ids) |
Delete specific memories by ID |
delete_all_memories(confirm) |
Two-step: without confirm=true it returns the count and asks for confirmation; nothing is deleted |
Export and import
Back up or migrate project memories as JSON
Back up project memories to a JSON file, or migrate between servers:
# Export via CLI
mem-zero-cli export my-project -o backup.json
# Import to same or different server
mem-zero-cli import backup.json
mem-zero-cli import backup.json --project different-project
# Export via REST
curl http://your-host:8765/api/v1/projects/my-project/memories?limit=1000 > backup.json
The export format includes project metadata, timestamps, and all memory content. Importing re-processes text through the LLM pipeline (extraction and dedup), so imported memories are properly deduplicated against existing content.
Best practices
How to get good memories out of your assistant
mem-zero supplements conversations — it's not a transcript. Store things a future session would need that aren't obvious from reading the code or git history.
Search first. At the start of every conversation, search mem-zero for prior context. A well-maintained memory store means you never start from scratch.
Store decisions, not play-by-play. "Chose PostgreSQL over Redis because we need ACID transactions" is useful. "Updated line 42 in server.py" is not — that's what git log is for.
Store dead ends. If you spend 30 minutes debugging something that turned out to be a red herring, store that. It prevents future sessions from going down the same path.
Quality over quantity. Each memory should be a complete, self-contained statement. One memory per logical change or decision — not one per file touched. Fragments like "Root cause" or "Solution" without context are useless noise.
Let the code speak. Don't store function signatures, file structure, what a method does, or test results. The codebase is the authoritative source for those. Store the why, not the what.
For Claude Code, add instructions to your CLAUDE.md telling the assistant to use mem-zero proactively. Without explicit instructions, most assistants won't store memories on their own.
REST API
Full endpoint reference
GET /health — health check
GET /api/v1/projects — list all projects
GET /api/v1/projects/{slug}/memories — list memories (?limit=&offset=; X-Next-Offset header carries the cursor for the next page)
POST /api/v1/projects/{slug}/memories — add memory {"text": "..."}
POST /api/v1/projects/{slug}/search — search {"query": "...", "top_k": 10}
DELETE /api/v1/projects/{slug}/memories/{id} — delete one
DELETE /api/v1/projects/{slug}/memories — delete all memories
DELETE /api/v1/projects/{slug} — delete entire project
POST /api/v1/projects/{slug}/reembed — regenerate embeddings for all memories
POST /api/v1/projects/{slug}/cleanup — fix garbled text and split multi-fact entries
POST /api/v1/projects/{slug}/consolidate — merge similar fragments into clean summaries
GET /api/v1/diagnostics — performance and accuracy metrics
GET /api/v1/projects/{slug}/diagnostics — per-project metrics
POST /api/v1/diagnostics/reset — clear collected metrics
POST /api/v1/diagnostics/export — full metrics snapshot as JSON
GET /debug/config — active backend/config summary (never the API key)
The diagnostics endpoints and /debug/config return 404 unless DIAGNOSTICS_ENABLED=true.
Configuration
All environment variables (general, and per-backend)
All settings are via environment variables.
General
| Variable | Default | Purpose |
|---|---|---|
API_KEY |
— | API key for MCP and REST endpoints (disabled if empty) |
LLM_BACKEND |
auto-detect | bundled, ollama, or openai |
EMBEDDER_DIMENSIONS |
per backend | Optional. Unset means the backend default: 768 for Ollama and bundled, the model's native size for OpenAI (1536 for text-embedding-3-small). Set only to override |
EXTRACT_MAX_CHARS |
24000 |
Inputs longer than this are still processed but a warning is logged, since the LLM context will truncate the tail |
COLLECTION_PREFIX |
mem-zero |
Qdrant collection name prefix |
HOST |
0.0.0.0 |
Server bind address |
PORT |
8765 |
Server port |
DASHBOARD_USER |
— | Dashboard login username (auth disabled if empty) |
DASHBOARD_PASS |
— | Dashboard login password |
DIAGNOSTICS_ENABLED |
false |
Enable performance and accuracy metrics on the dashboard |
RERANK_ENABLED |
false |
Rerank search results with a CPU cross-encoder for sharper relevance scores (~0.5s extra per search, ~200 MB RAM) |
RERANK_MODEL |
Xenova/ms-marco-MiniLM-L-6-v2 |
fastembed cross-encoder model used when reranking |
STATS_PATH |
/mem-zero/storage/diagnostics.json |
File where diagnostics counters are persisted across restarts |
Upgrading and want the per-backend default? mem-zero persists your env vars to
.env.savedon the storage volume so Unraid template edits can't silently blank them. That means an old explicitEMBEDDER_DIMENSIONS=768is restored even after you clear the field. To go back to the default, also delete/mem-zero/storage/.env.saved(it is regenerated on next start).
Bundled backend
| Variable | Default | Purpose |
|---|---|---|
BUNDLED_MODEL_PATH |
/mem-zero/storage/models/qwen2.5-3b-instruct-q4_k_m.gguf |
Path to GGUF model |
BUNDLED_EMBED_MODEL |
nomic-ai/nomic-embed-text-v1.5 |
fastembed model name |
BUNDLED_THREADS |
4 |
CPU threads for inference |
Ollama backend
| Variable | Default | Purpose |
|---|---|---|
OLLAMA_BASE_URL |
http://127.0.0.1:11434 |
Ollama API URL |
LLM_MODEL |
qwen2.5:7b |
Model for fact extraction and dedup |
EMBEDDER_MODEL |
nomic-embed-text |
Embedding model |
OLLAMA_MAX_CONCURRENT |
2 |
Max concurrent requests to Ollama |
OpenAI backend
| Variable | Default | Purpose |
|---|---|---|
OPENAI_API_KEY |
— | API key (required) |
OPENAI_BASE_URL |
https://api.openai.com/v1 |
API base URL |
OPENAI_MODEL |
gpt-4o-mini |
Chat model |
OPENAI_EMBED_MODEL |
text-embedding-3-small |
Embedding model |
Qdrant
| Variable | Default | Purpose |
|---|---|---|
QDRANT_HOST |
127.0.0.1 |
Qdrant host (bundled) |
QDRANT_PORT |
6333 |
Qdrant port |
QDRANT_URL |
— | Full Qdrant URL (overrides host/port) |
QDRANT_API_KEY |
— | Qdrant API key (if using external) |
Architecture
What's inside the single container
The container bundles everything into a single image using s6-overlay for process supervision:
- Qdrant — embedded vector database, data persisted to
/mem-zero/storage - FastAPI — HTTP server handling MCP transport, REST API, and static dashboard
- Qwen2.5-3B — bundled LLM for fact extraction and dedup (CPU-only, ~1.8 GB RAM)
- fastembed — bundled embedding model (nomic-embed-text, ~270 MB)
External LLMs (Ollama, OpenAI) are supported as alternatives. When using Ollama, the bundled model serves as an automatic fallback if Ollama is unreachable.
Project isolation is enforced at the Qdrant collection level. Each project slug maps to {prefix}_{slug}, and all queries are scoped to a single collection.
Permissions
The Qdrant and mem-zero processes run as an unprivileged account (uid 99 /
gid 100 by default — the Unraid convention), not root. On the first start
after upgrading, the container chowns /mem-zero/storage to that id once
(a sentinel file skips the walk on later boots; it can take a while on large
volumes). Override with PUID / PGID, or set PUID=0 to run as root like
older releases. Ports below 1024 can no longer be bound.
Unraid
An Unraid Docker template is included at unraid-template.xml. Install it through Community Applications or manually add the template to your Docker configuration.
License
AGPL-3.0 — see LICENSE.
Install mem-zero on Unraid in a few clicks.
Find mem-zero in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/sworcery/mem-zero:latestRuntime arguments
- Web UI
http://[IP]:[PORT:8765]/- Network
bridge- Shell
bash- Privileged
- false
- Extra Params
--add-host=host.docker.internal:host-gateway --stop-timeout=30
Template configuration
Web dashboard and MCP server HTTP port
- Target
- 8765
- Default
- 8765
- Value
- 8765
Persistent storage for Qdrant data
- Target
- /mem-zero/storage
- Default
- /mnt/user/appdata/mem-zero
- Value
- /mnt/user/appdata/mem-zero
Leave blank to auto-detect: openai if OpenAI API Key is set, else ollama if Ollama URL is set, else bundled. Set explicitly only to override. NOTE: an explicit 'bundled' ignores your Ollama/OpenAI settings.
- Target
- LLM_BACKEND
Ollama API base URL (only used when backend=ollama)
- Target
- OLLAMA_BASE_URL
Ollama language model (only used when backend=ollama)
- Target
- LLM_MODEL
- Default
- qwen2.5:7b
- Value
- qwen2.5:7b
Ollama embedding model (only used when backend=ollama)
- Target
- EMBEDDER_MODEL
- Default
- nomic-embed-text
- Value
- nomic-embed-text
Optional. Leave blank for the per-backend default (768 for Ollama/bundled, the model's native size for OpenAI). Set only to override.
- Target
- EMBEDDER_DIMENSIONS
API key for OpenAI-compatible backend (only used when backend=openai)
- Target
- OPENAI_API_KEY
Base URL for any OpenAI-compatible API serving BOTH /chat/completions and /embeddings (OpenAI, Groq, OpenRouter, LM Studio, vLLM, LiteLLM)
- Target
- OPENAI_BASE_URL
- Default
- https://api.openai.com/v1
- Value
- https://api.openai.com/v1
Chat model for OpenAI-compatible backend
- Target
- OPENAI_MODEL
- Default
- gpt-4o-mini
- Value
- gpt-4o-mini
Embedding model for OpenAI-compatible backend
- Target
- OPENAI_EMBED_MODEL
- Default
- text-embedding-3-small
- Value
- text-embedding-3-small
CPU threads for bundled LLM inference
- Target
- BUNDLED_THREADS
- Default
- 4
- Value
- 4
API key for MCP and REST endpoints (leave empty to disable). Generate one with: openssl rand -hex 32
- Target
- API_KEY
Username for web dashboard login (leave empty to disable auth)
- Target
- DASHBOARD_USER
Password for web dashboard login (leave empty to disable auth)
- Target
- DASHBOARD_PASS
Enable performance and accuracy metrics on the dashboard
- Target
- DIAGNOSTICS_ENABLED
- Default
- false
- Value
- false
Re-score search results with a local cross-encoder for sharper relevance separation (CPU; downloads a small model on first use)
- Target
- RERANK_ENABLED
- Default
- false
- Value
- false
User ID the Qdrant and mem-zero processes run as. Files in the Storage path are chowned to this id once on first start (99 = nobody, the Unraid default). Set 0 to run as root like older releases.
- Default
- 99
- Value
- 99
Group ID for the service processes (100 = users, the Unraid default).
- Default
- 100
- Value
- 100
Cross-encoder model used when Rerank Enabled is true
- Target
- RERANK_MODEL
- Default
- Xenova/ms-marco-MiniLM-L-6-v2
- Value
- Xenova/ms-marco-MiniLM-L-6-v2