{"id":"dd883e56-5bd7-497c-b881-33dcc7d2243a","slug":"mem-zero","kind":"mcp","name":"mem-zero","summary":"Self-hosted MCP memory server. Each project gets isolated memory with LLM fact extraction, deduplication and semantic search. Works with Claude Code, Codex, Copilot, Cursor and any MCP client.","owner":"sworcery","repoUrl":"https://github.com/sworcery/mem-zero","license":"AGPL-3.0","licenseTier":"strong-copyleft","trust":"community","stars":7,"match":null,"fewStars":false,"format":"mcp","createdAt":"2026-04-28T08:42:05.000Z","addedAt":"2026-10-28T06:10:03.412Z","logoUrl":"https://avatars.githubusercontent.com/u/3312211?u=b202579802f3712db9970df1fdf7c578cae95257&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["memory-knowledge","developer-tools","cloud-infra"],"tags":["claude-code","long-term-memory","memory","ollama","qdrant","self-hosted","unraid"],"runtime":"python","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-28T06:10:16.675Z","sample":false,"url":"https://conduits.dev/tools/mem-zero","language":"Python","transports":["stdio"],"install":{"pypi":"mem-zero"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"<p align=\"center\">\n  <img src=\"banner.png\" alt=\"mem-zero\" />\n</p>\n\nSelf-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.\n\nEach 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.\n\n## Why?\n\nAI 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:\n\n- **Session 50 knows what session 1 figured out**: bugs fixed, decisions made, preferences learned\n- **Selective retrieval**: semantic search pulls in only what's relevant, not entire conversation logs\n- **Project-isolated**: memories from one project never leak into another\n- **Self-contained**: runs as a single Docker container with an embedded LLM, no external dependencies required\n\n## Quick start\n\n```bash\ndocker run -d \\\n  --name mem-zero \\\n  -p 8765:8765 \\\n  -v mem-zero-data:/mem-zero/storage \\\n  ghcr.io/sworcery/mem-zero:latest\n```\n\nThat'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.\n\n## How it works\n\n1. Text comes in via MCP or REST API\n2. LLM extracts atomic facts (e.g. \"User prefers Python over R\")\n3. Each fact is checked against existing memories for duplicates\n4. Novel facts are embedded and stored; duplicates are merged or skipped\n\n## Connecting your tools\n\n<details>\n<summary><b>Claude Code, the CLI, and the REST API</b></summary>\n\n### Claude Code (MCP)\n\n```bash\nclaude mcp add mem-zero --transport http \\\n  \"http://your-host:8765/mcp/your-project-slug/http/your-user-id\" \\\n  -s local\n```\n\n### CLI\n\n```bash\npip install mem-zero\nmem-zero-cli --url http://your-host:8765 --api-key your-key projects\n```\n\nThe CLI provides direct terminal access to all memory operations:\n\n```bash\n# List projects\nmem-zero-cli projects\n\n# Add a memory\nmem-zero-cli add my-project \"Chose PostgreSQL over Redis for session storage\"\n\n# Pipe text from stdin\necho \"User prefers dark mode\" | mem-zero-cli add my-project -\n\n# Search\nmem-zero-cli search my-project \"database decision\"\n\n# List memories\nmem-zero-cli list my-project\n\n# Export/import for backup and migration\nmem-zero-cli export my-project -o backup.json\nmem-zero-cli import backup.json --project new-project\n\n# Health check\nmem-zero-cli health\n\n# Diagnostics\nmem-zero-cli stats\nmem-zero-cli stats --project my-project\n```\n\nAll commands support `--json` for machine-readable output. Run `mem-zero-cli --help` for full usage.\n\n### REST API (any tool)\n\nAnything that can make HTTP requests can use mem-zero directly:\n\n```bash\n# Store a memory\ncurl -X POST http://your-host:8765/api/v1/projects/my-project/memories \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\": \"Switched from Redis to PostgreSQL for session storage because we need ACID transactions\"}'\n\n# Search memories\ncurl -X POST http://your-host:8765/api/v1/projects/my-project/search \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\": \"database decision\", \"top_k\": 5}'\n```\n\nThe 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.\n\n</details>\n\n<details>\n<summary><b>Other coding agents: Codex, Copilot, Goose, opencode, OpenClaude, Qwen Code, Grok Build</b></summary>\n\nmem-zero exposes a standard streamable-HTTP MCP endpoint with Bearer auth, so any\nMCP-capable agent connects without server-side changes. Everything below points at\nthe same URL:\n\n```\nhttp://your-host:8765/mcp/your-project-slug/http/your-user-id\n```\n\nExport your key first so it stays out of the config files:\n\n```bash\nexport MEM_ZERO_API_KEY=\"your-key\"\n```\n\n### OpenAI Codex CLI\n\n`~/.codex/config.toml` (or `.codex/config.toml` for a single repo). A `url` key\nimplies streamable HTTP. There is no transport field to set:\n\n```toml\n[mcp_servers.mem-zero]\nurl = \"http://your-host:8765/mcp/your-project-slug/http/your-user-id\"\nbearer_token_env_var = \"MEM_ZERO_API_KEY\"\n```\n\n### GitHub Copilot (VS Code, Visual Studio, JetBrains, Xcode)\n\n`.vscode/mcp.json`, which prompts once for the key and then stores it:\n\n```json\n{\n  \"inputs\": [\n    { \"type\": \"promptString\", \"id\": \"mem-zero-key\", \"description\": \"mem-zero API key\", \"password\": true }\n  ],\n  \"servers\": {\n    \"mem-zero\": {\n      \"type\": \"http\",\n      \"url\": \"http://your-host:8765/mcp/your-project-slug/http/your-user-id\",\n      \"headers\": { \"Authorization\": \"Bearer ${input:mem-zero-key}\" }\n    }\n  }\n}\n```\n\n### opencode\n\n`opencode.json`:\n\n```json\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"mcp\": {\n    \"mem-zero\": {\n      \"type\": \"remote\",\n      \"url\": \"http://your-host:8765/mcp/your-project-slug/http/your-user-id\",\n      \"enabled\": true,\n      \"headers\": { \"Authorization\": \"Bearer {env:MEM_ZERO_API_KEY}\" }\n    }\n  }\n}\n```\n\n### Goose\n\n`~/.config/goose/config.yaml`, where headers support `${VAR}` substitution:\n\n```yaml\nextensions:\n  mem-zero:\n    enabled: true\n    type: streamable_http\n    name: mem-zero\n    uri: http://your-host:8765/mcp/your-project-slug/http/your-user-id\n    headers:\n      Authorization: \"Bearer ${MEM_ZERO_API_KEY}\"\n    timeout: 300\n```\n\n### OpenClaude\n\nNot to be confused with opencode above. OpenClaude is a Claude Code-compatible CLI\nthat runs against any provider, including a local Ollama. Its config is Claude\nCode's, so `--scope project` writes the same `.mcp.json`:\n\n```bash\nopenclaude mcp add --transport http mem-zero \\\n  \"http://your-host:8765/mcp/your-project-slug/http/your-user-id\" \\\n  --header \"Authorization: Bearer ${MEM_ZERO_API_KEY}\" \\\n  --scope project\n```\n\nThis pairs well with mem-zero if you want the whole stack local: OpenClaude driving\nan Ollama model, with mem-zero's Ollama backend doing extraction and embeddings.\n\n### Qwen Code\n\n`settings.json`; note it uses `httpUrl` (not `url`) for streamable HTTP:\n\n```json\n{\n  \"mcpServers\": {\n    \"mem-zero\": {\n      \"httpUrl\": \"http://your-host:8765/mcp/your-project-slug/http/your-user-id\",\n      \"headers\": { \"Authorization\": \"Bearer ${MEM_ZERO_API_KEY}\" },\n      \"timeout\": 300000\n    }\n  }\n}\n```\n\n### Grok Build (xAI)\n\n```bash\ngrok mcp add --transport http mem-zero \\\n  \"http://your-host:8765/mcp/your-project-slug/http/your-user-id\" \\\n  --header \"Authorization: Bearer ${MEM_ZERO_API_KEY}\"\n```\n\nOr in `~/.grok/config.toml`:\n\n```toml\n[mcp_servers.mem-zero]\ntransport = \"http\"\nurl = \"http://your-host:8765/mcp/your-project-slug/http/your-user-id\"\nheaders = { \"Authorization\" = \"Bearer ${MEM_ZERO_API_KEY}\" }\n```\n\n### Editors with their own MCP support\n\nCursor, Windsurf, Cline, Zed, and JetBrains Junie each have an MCP settings panel:\nadd the same URL and `Authorization: Bearer` header. This is also how you use\nmem-zero alongside a non-Anthropic model such as `grok-code-fast-1`: the editor\nhandles MCP, the model just sees the tools.\n\nAgents without MCP support (Aider, for example) can still use the REST API or the\n`mem-zero-cli` shown above.\n\n</details>\n\n## Teaching your agent to use memory\n\nConnecting the server only makes the tools *available*. Agents won't store or\nsearch memories on their own. They need instructions. Put these in the file your\nagent reads at the repo root: **`AGENTS.md`** works for Codex, Copilot, Cursor,\nWindsurf, Amp, Zed, Junie, Aider, Devin, and Claude Code; Claude Code and Grok\nBuild also read `CLAUDE.md`, and Gemini CLI uses `GEMINI.md`.\n\n<details>\n<summary><b>Copy-paste block for AGENTS.md</b></summary>\n\n```markdown\n## Project memory (mem-zero)\n\nThis project has a persistent memory server available over MCP.\n\n**Search first.** At the start of every session, search memory for prior context\non whatever you're about to work on. Do this before reading code — it will tell\nyou about decisions and dead ends the code doesn't explain.\n\n**Store at checkpoints**, not continuously: after completing a feature, fixing a\nbug, or making a decision worth remembering.\n\nStore:\n- Decisions and the reasoning behind them, especially when alternatives were\n  rejected (\"chose X over Y because Z\")\n- Non-obvious workarounds and gotchas — things that would surprise a future reader\n- Dead ends from debugging, so the next session doesn't repeat the investigation\n- Conventions and preferences that aren't written down anywhere else\n\nDo NOT store:\n- Anything `git log`, `grep`, or the code itself already answers — function\n  signatures, file structure, what a method does\n- Per-file change lists, version bumps, or test results\n- Fragments that can't stand alone (\"Root cause\", \"Solution\")\n\n**Quality bar:** each memory is one complete, self-contained sentence that a\nfuture session can understand with no other context. Name systems and tools\nexplicitly rather than saying \"this\" or \"it\". One memory per decision, not one\nper file touched.\n\n`delete_memories` and `delete_all_memories` are destructive — always ask before\ncalling them.\n```\n\n</details>\n\n## LLM backends\n\n<details>\n<summary><b>Ollama (recommended), bundled (default), and OpenAI-compatible</b></summary>\n\nmem-zero supports three LLM backends for fact extraction and deduplication.\n\n| Backend | LLM | Embeddings | Setup |\n|---------|-----|------------|-------|\n| **ollama** (recommended) | Any Ollama model | Any Ollama embedding model | Set `OLLAMA_BASE_URL` |\n| **bundled** (default) | Qwen2.5-3B (built-in, CPU) | nomic-embed-text via fastembed | Zero config: just run the container |\n| **openai** (beta) | Any OpenAI-compatible API | Any OpenAI-compatible embeddings | Set `OPENAI_API_KEY` |\n\n**Auto-detection:** If `LLM_BACKEND` is not set, the backend is chosen automatically:\n- `OPENAI_API_KEY` present → `openai`\n- `OLLAMA_BASE_URL` present → `ollama`\n- Neither → `bundled`\n\n### Using with Ollama (recommended)\n\nOllama 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, `gemma4:12b` is the sweet spot for quality vs. resource usage (about 9.5 GB of VRAM); it deduplicates noticeably more accurately than `qwen2.5:14b`. mem-zero sends `think: false` on every call, so models with optional thinking (gemma4, qwen3.5, granite4.2) answer directly. gpt-oss always reasons and is not recommended.\n\n```bash\ndocker run -d \\\n  --name mem-zero \\\n  -p 8765:8765 \\\n  -v mem-zero-data:/mem-zero/storage \\\n  -e OLLAMA_BASE_URL=http://your-ollama-host:11434 \\\n  -e LLM_MODEL=gemma4:12b \\\n  ghcr.io/sworcery/mem-zero:latest\n```\n\n**Fallback:** When using Ollama, if the server is unreachable, search and cleanup fall back to the bundled embedder. That only works when your collections use its 768 dimensions; with a different embedder they fail until Ollama is back. The fallback is lazy: it only loads into memory on the first failure. Consolidate waits for Ollama, and adds are queued until Ollama is back (see Outages and the retry queue).\n\n### Using the bundled backend\n\nThe 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.\n\n### Using with OpenAI-compatible APIs (beta)\n\n```bash\ndocker run -d \\\n  --name mem-zero \\\n  -p 8765:8765 \\\n  -v mem-zero-data:/mem-zero/storage \\\n  -e OPENAI_API_KEY=sk-... \\\n  ghcr.io/sworcery/mem-zero:latest\n```\n\nWorks 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.\n\n</details>\n\n## Outages and the retry queue\n\nIf the LLM backend (Ollama or an OpenAI-compatible API) is unreachable when you add a memory, the note is saved raw to `PENDING_DIR` instead of being lost or stored without deduplication. The add returns `{\"stored\": 0, \"ids\": [], \"queued\": true}` (the MCP tool returns the same with a short note). Queued adds never use the bundled fallback model.\n\nEvery 30 seconds, if the queue is not empty and the backend is healthy again, queued adds are replayed oldest first through the normal extract, dedup and store path. A replayed add that fails for a reason other than an outage is retried, and moved to `PENDING_DIR/failed/` after 10 attempts. `/health` reports the queue size as `pending`, and the diagnostics counters `queue.enqueued`, `queue.replayed` and `queue.dead_lettered` track activity. Deployments that use only the bundled backend never queue. To retry dead letters, move the files from `PENDING_DIR/failed/` back into `PENDING_DIR`. Keep `PENDING_DIR` on persistent storage so the queue survives restarts.\n\n## Web dashboard\n\n<details>\n<summary><b>Management UI at the root URL: health, projects, memories, charts</b></summary>\n\nA management UI is served at the root URL (`http://your-host:8765/`). From the dashboard you can:\n\n- Monitor system health, uptime, and live performance charts\n- Browse all projects and their memory counts\n- View, search, and delete memories per project\n- Consolidate similar memory fragments\n- Delete entire projects\n- Add new memories manually\n\nEnable `DIAGNOSTICS_ENABLED=true` to see performance metrics, accuracy stats, score distributions, and error tracking on the home page.\n\nOptionally protect it with basic auth via `DASHBOARD_USER` and `DASHBOARD_PASS`.\n\nIf `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.\n\n</details>\n\n## Authentication\n\n<details>\n<summary><b>API keys for REST/MCP, plus dashboard basic auth</b></summary>\n\nSet `API_KEY` to protect all API and MCP endpoints. When set, requests must include the key as a Bearer token:\n\n```bash\ncurl -H \"Authorization: Bearer your-api-key\" \\\n  http://your-host:8765/api/v1/projects\n```\n\nFor MCP clients, add the header to your client config. In Claude Code's `.mcp.json`:\n\n```json\n{\n  \"mem-zero\": {\n    \"type\": \"http\",\n    \"url\": \"http://your-host:8765/mcp/my-project/http/my-user\",\n    \"headers\": {\n      \"Authorization\": \"Bearer your-api-key\"\n    }\n  }\n}\n```\n\nA query parameter (`?api_key=your-key`) is also accepted for clients that can't set headers.\n\nIf `API_KEY` is not set, all endpoints are open, suitable for trusted networks.\n\nThe dashboard has its own basic auth (`DASHBOARD_USER`/`DASHBOARD_PASS`) since browsers need a login prompt rather than Bearer tokens.\n\nIf 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.\n\n</details>\n\n## MCP tools\n\n<details>\n<summary><b>The five tools exposed to MCP clients</b></summary>\n\n| Tool | Description |\n|------|-------------|\n| `add_memories(text)` | Extract and store facts from text (up to 50,000 chars) |\n| `search_memory(query, top_k)` | Semantic search within the project; `query` is capped at 2,000 chars, `top_k` at 100 |\n| `list_memories(limit)` | Returns `{\"memories\": [...], \"total\": N, \"truncated\": bool}` (default limit 100, max 1000) |\n| `delete_memories(memory_ids)` | Delete specific memories by ID |\n| `delete_all_memories(confirm)` | Two-step: without `confirm=true` it returns the count and asks for confirmation; nothing is deleted |\n\n</details>\n\n## Export and import\n\n<details>\n<summary><b>Back up or migrate project memories as JSON</b></summary>\n\nBack up project memories to a JSON file, or migrate between servers:\n\n```bash\n# Export via CLI\nmem-zero-cli export my-project -o backup.json\n\n# Import to same or different server\nmem-zero-cli import backup.json\nmem-zero-cli import backup.json --project different-project\n\n# Export via REST\ncurl http://your-host:8765/api/v1/projects/my-project/memories?limit=1000 > backup.json\n```\n\nThe 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.\n\n</details>\n\n## Best practices\n\n<details>\n<summary><b>How to get good memories out of your assistant</b></summary>\n\nmem-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.\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n\n**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*.\n\nFor 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.\n\n</details>\n\n## REST API\n\n<details>\n<summary><b>Full endpoint reference</b></summary>\n\n```\nGET    /health                                  — health check\nGET    /api/v1/projects                         — list all projects\nGET    /api/v1/projects/{slug}/memories          — list memories (?limit=&offset=; X-Next-Offset header carries the cursor for the next page)\nPOST   /api/v1/projects/{slug}/memories          — add memory {\"text\": \"...\"}\nPOST   /api/v1/projects/{slug}/search            — search {\"query\": \"...\", \"top_k\": 10}\nDELETE /api/v1/projects/{slug}/memories/{id}      — delete one\nDELETE /api/v1/projects/{slug}/memories           — delete all memories\nDELETE /api/v1/projects/{slug}                    — delete entire project\nPOST   /api/v1/projects/{slug}/reembed            — regenerate embeddings for all memories\nPOST   /api/v1/projects/{slug}/cleanup            — fix garbled text and split multi-fact entries\nPOST   /api/v1/projects/{slug}/consolidate        — merge similar fragments into clean summaries\nGET    /api/v1/diagnostics                        — performance and accuracy metrics\nGET    /api/v1/projects/{slug}/diagnostics        — per-project metrics\nPOST   /api/v1/diagnostics/reset                  — clear collected metrics\nPOST   /api/v1/diagnostics/export                 — full metrics snapshot as JSON\nGET    /debug/config                              — active backend/config summary (never the API key)\n```\n\nThe diagnostics endpoints and `/debug/config` return 404 unless `DIAGNOSTICS_ENABLED=true`.\n\n</details>\n\n## Configuration\n\n<details>\n<summary><b>All environment variables (general, and per-backend)</b></summary>\n\nAll settings are via environment variables.\n\n### General\n\n| Variable | Default | Purpose |\n|----------|---------|---------|\n| `API_KEY` | none | API key for MCP and REST endpoints (disabled if empty) |\n| `LLM_BACKEND` | auto-detect | `bundled`, `ollama`, or `openai` |\n| `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 |\n| `EXTRACT_MAX_CHARS` | `24000` | Inputs longer than this are still processed but a warning is logged, since the LLM context will truncate the tail |\n| `COLLECTION_PREFIX` | `mem-zero` | Qdrant collection name prefix |\n| `HOST` | `0.0.0.0` | Server bind address |\n| `PORT` | `8765` | Server port |\n| `DASHBOARD_USER` | none | Dashboard login username (auth disabled if empty) |\n| `DASHBOARD_PASS` | none | Dashboard login password |\n| `DIAGNOSTICS_ENABLED` | `false` | Enable performance and accuracy metrics on the dashboard |\n| `RERANK_ENABLED` | `false` | Rerank search results with a CPU cross-encoder for sharper relevance scores (~0.5s extra per search, ~200 MB RAM) |\n| `RERANK_MODEL` | `Xenova/ms-marco-MiniLM-L-6-v2` | fastembed cross-encoder model used when reranking |\n| `STATS_PATH` | `/mem-zero/storage/diagnostics.json` | File where diagnostics counters are persisted across restarts |\n| `PENDING_DIR` | `/mem-zero/storage/pending` | Directory where adds are held while the LLM backend is unreachable (see Outages and the retry queue) |\n\n> **Upgrading and want the per-backend default?** mem-zero persists your env\n> vars to `.env.saved` on the storage volume so Unraid template edits can't\n> silently blank them. That means an old explicit `EMBEDDER_DIMENSIONS=768` is\n> restored even after you clear the field. To go back to the default, also\n> delete `/mem-zero/storage/.env.saved` (it is regenerated on next start).\n\n### Bundled backend\n\n| Variable | Default | Purpose |\n|----------|---------|---------|\n| `BUNDLED_MODEL_PATH` | `/mem-zero/storage/models/qwen2.5-3b-instruct-q4_k_m.gguf` | Path to GGUF model |\n| `BUNDLED_EMBED_MODEL` | `nomic-ai/nomic-embed-text-v1.5` | fastembed model name |\n| `BUNDLED_THREADS` | `4` | CPU threads for inference |\n\n### Ollama backend\n\n| Variable | Default | Purpose |\n|----------|---------|---------|\n| `OLLAMA_BASE_URL` | `http://127.0.0.1:11434` | Ollama API URL |\n| `OLLAMA_KEEP_ALIVE` | `30m` | How long Ollama keeps the models loaded after each mem-zero request: a duration (`30m`, `1h`), a number of seconds, or `-1` to keep them loaded. It is sent per request, so it overrides the Ollama server's own `OLLAMA_KEEP_ALIVE`; set it empty to use the server's value |\n| `LLM_MODEL` | `qwen2.5:7b` | Model for fact extraction and dedup |\n| `EMBEDDER_MODEL` | `nomic-embed-text` | Embedding model |\n| `OLLAMA_MAX_CONCURRENT` | `2` | Max concurrent requests to Ollama |\n\n### OpenAI backend\n\n| Variable | Default | Purpose |\n|----------|---------|---------|\n| `OPENAI_API_KEY` | none | API key (required) |\n| `OPENAI_BASE_URL` | `https://api.openai.com/v1` | API base URL |\n| `OPENAI_MODEL` | `gpt-4o-mini` | Chat model |\n| `OPENAI_EMBED_MODEL` | `text-embedding-3-small` | Embedding model |\n\n### Qdrant\n\n| Variable | Default | Purpose |\n|----------|---------|---------|\n| `QDRANT_HOST` | `127.0.0.1` | Qdrant host (bundled) |\n| `QDRANT_PORT` | `6333` | Qdrant port |\n| `QDRANT_URL` | none | Full Qdrant URL (overrides host/port) |\n| `QDRANT_API_KEY` | none | Qdrant API key (if using external) |\n\n</details>\n\n## Architecture\n\n<details>\n<summary><b>What's inside the single container</b></summary>\n\nThe container bundles everything into a single image using s6-overlay for process supervision:\n\n- **Qdrant**: embedded vector database, data persisted to `/mem-zero/storage`\n- **FastAPI**: HTTP server handling MCP transport, REST API, and static dashboard\n- **Qwen2.5-3B**: bundled LLM for fact extraction and dedup (CPU-only, ~1.8 GB RAM)\n- **fastembed**: bundled embedding model (nomic-embed-text, ~270 MB)\n\nExternal LLMs (Ollama, OpenAI) are supported as alternatives. When using Ollama, the bundled embedder serves as an automatic fallback for search and cleanup if Ollama is unreachable (768-dimension collections only). Adds are queued instead.\n\nProject isolation is enforced at the Qdrant collection level. Each project slug maps to `{prefix}_{slug}`, and all queries are scoped to a single collection.\n\n### Permissions\n\nThe Qdrant and mem-zero processes run as an unprivileged account (uid 99 /\ngid 100 by default, the Unraid convention), not root. On the first start\nafter upgrading, the container chowns `/mem-zero/storage` to that id once\n(a sentinel file skips the walk on later boots; it can take a while on large\nvolumes). Override with `PUID` / `PGID`, or set `PUID=0` to run as root like\nolder releases. Ports below 1024 can no longer be bound.\n\n</details>\n\n## Unraid\n\nAn Unraid Docker template is included at `unraid-template.xml`. Install it through Community Applications or manually add the template to your Docker configuration.\n\n## License\n\nCopyright (C) 2026 Gadsden LLC. Licensed under the GNU Affero General Public License v3.0 only (AGPL-3.0-only); see [LICENSE](LICENSE).\n"}