The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Marm MCP Server listing page.
Contributions welcome! Browse open issues to contribute, or join the MARM Discord to share workflows, get setup help, and connect with other builders.
Also available: --g-qwen and --g-kiro. Run without flags to install into your current project folder instead of home
"Use the marm-init skill to set up MARM."
Manual setup
Prefer to wire it up yourself:
Replace "agent" with your client’s CLI command (for example, claude, gemini, or qwen). For Codex, use codex mcp add marm-memory --url http://localhost:8001/mcp instead.
| If you are... | Start the server | Connect your MCP client |
|---|---|---|
| Solo developer / researcher | marm-memory start | "agent" mcp add --transport http marm-memory http://localhost:8001/mcp |
| Private local STDIO user | marm-mcp-stdio | "agent" mcp add --transport stdio marm-memory-stdio marm-mcp-stdio |
| Multiple agents sharing memory | marm-memory start --profile swarm | "agent" mcp add --transport http marm-memory http://localhost:8001/mcp |
| Private high-throughput swarm | marm-memory start --profile swarm-max | "agent" mcp add --transport http marm-memory http://localhost:8001/mcp |
| Trusted private lab/server | marm-memory start --profile trusted | "agent" mcp add --transport http marm-memory http://localhost:8001/mcp |
Your AI forgets everything. MARM Memory doesn't.
marm-memory gives your agents a private, shared memory for the context that normally gets lost between chats: decisions, research, fixes, notes, and project history. Switch from Claude Code to Codex or Gemini without losing the context already gathered.
It brings three things together:
All 16 tools work over HTTP and STDIO. Your agents share the same local memory across sessions instead of starting from scratch each time. The bundled Console App provides a browsable view of Memories, the Knowledge Graph, and Indexed Projects, including progress for graph builds and repository indexing. Indexing a repository creates its independent Code Graph, which you can explore from Knowledge Graph → Code Explorer even before storing any memories.
| Layer | What it does | Why it matters |
|---|---|---|
| Memory model | Sessions, structured logs, notebooks, summaries, and semantic memories | Keeps project history searchable instead of trapped in one chat |
| Scale layer | SQLite WAL mode, connection pooling, serialized write queue, and HTTP rate-limit presets | Lets one server support solo use, multi-agent work, and swarm-style bursts |
| Intelligence layer | FTS filter, semantic re-rank, bounded semantic fallback, auto-classification, write-time consolidation, and compaction candidates | Keeps recall useful as memory grows instead of letting duplicates pile up |
| Code graph layer | Repo indexing, symbol lookup, call tracing, architecture overview, and change-impact analysis | Gives agents project structure without rereading the whole codebase |
| Concept graph layer | Entity and relationship extraction from stored memories, with links back into the code graph | Connects decisions, errors, tools, and people across sessions instead of leaving them as flat text |
| Token layer | Lightweight 8-tool core surface (16 total with bundled graph tools), semantic re-rank before retrieval, and write-time deduplication | Reduces tokens sent to the model on every recall and cost stays predictable as memory scales |
| Deployment layer | Pip, Docker, STDIO, HTTP, and managed swarm, swarm-max, and trusted profiles | Lets you run private local memory or shared multi-agent memory with the same MCP surface |
See Performance & Scaling Benchmarks for retrieval latency, concurrency, and write-cost numbers, and Architecture & Internals for the mechanisms behind each layer.
marm-memory is the local runtime manager installed with the Python package. These are the normal operational commands; use marm-memory <command> --help for flags and command-specific examples.
Daily runtime work
Transports and setup
Knowledge, projects, and maintenance
Docker commands are documented separately below because they require explicit data mounts, network exposure, and key-handling choices.
MARM is tuned for fast recall first, even as memory grows and long memories are chunked behind the scenes.
These measurements use the fastembed-backed jinaai/jina-embeddings-v2-small-en encoder and a throwaway local SQLite database. Every timed path calls the shipped MARMMemory code, not a benchmark-local reimplementation. Sections 1-4 are timings from a single run of scripts/benchmarking/performance/bench_hotpath.py on local hardware; absolute milliseconds vary by machine, so treat the scaling shape as the signal. Section 5 is a separate accuracy benchmark (run_eval.py) measuring retrieval rather than speed, and its latest row is a controlled before and after, explained there.
End-to-end recall_similar latency (includes query encoding).
| Session Size ($N$) | Min Latency | Median Latency | p95 Latency |
|---|---|---|---|
| N = 100 | 7.4 ms | 7.9 ms | 9.4 ms |
| N = 250 | 11.9 ms | 13.5 ms | 15.4 ms |
| N = 500 | 10.9 ms | 11.8 ms | 13.4 ms |
| N = 1,000 | 13.3 ms | 13.5 ms | 15.6 ms |
| N = 2,000 | 17.5 ms | 18.2 ms | 19.6 ms |
| N = 4,000 | 23.8 ms | 25.9 ms | 30.9 ms |
Run-to-run variance at small $N$ is larger than the gap between adjacent sizes, which is why N = 250 reads slower than N = 500 here. Treat the trend from N = 1,000 upward as the real signal.
893ms3.8ms, p95 4.3ms151.5ms vs 176.0ms serial (gather/serial = 0.86). Do not read that as parallelism: repeated runs of this same benchmark land anywhere from 0.63 to 0.86, so the ratio is not stable enough to claim a speedup. The path is serialized around shared encoder and SQLite work by design, and any apparent gain is measurement noise.6.5ms, p95 7.6ms58.1ms, p95 106.5ms9.0x median cost so recall stays fast and the store stays cleaner over time. Consolidation is off by default.Why recall stays flat as memory grows: Instead of scanning every vector, production recall uses an FTS keyword pre-filter to narrow the candidate pool, then re-ranks using a blended semantic + BM25 + temporal score. Both benchmark columns represent authentic asynchronous code paths timed with precomputed vectors to isolate retrieval speed from raw encoding overhead. Tests alternate execution to ensure completely unbiased cache conditions.
| Session Size ($N$) | Full Semantic Scan | Production Hybrid | Speedup | FTS candidates |
|---|---|---|---|---|
| N = 100 | 3.3 ms | 6.6 ms | 0.5x | 85 / 200 |
| N = 500 | 16.3 ms | 11.6 ms | 1.4x | 200 / 200 |
| N = 1,000 | 31.1 ms | 14.7 ms | 2.1x | 200 / 200 |
| N = 2,000 | 63.5 ms | 19.0 ms | 3.3x | 200 / 200 |
| N = 4,000 | 127.2 ms | 29.1 ms | 4.4x | 200 / 200 |
| N = 10,000 | 316.7 ms | 53.8 ms | 5.9x | 200 / 200 |
The full scan grows roughly linearly with $N$ while hybrid recall grows far more slowly, so the advantage still widens with session size. At very small $N$ the pre-filter is not worth its overhead and hybrid is slower.
All 10 LoCoMo conversations are ingested through marm_log_entry (5,882 memories), then top-5 marm_smart_recall results are scored against 1,977 evidence-annotated questions. No answer-generation model or LLM judge is involved, so this measures whether the right memory is retrieved, not whether an agent answers correctly with it.
| Configuration | Any evidence hit | All evidence hit | Mean evidence recall |
|---|---|---|---|
| MiniLM baseline | 37.5% | 29.5% | not published |
| Jina v2 Small (v2.29.0) | 53.0% | 43.4% | 47.6% |
| v2.33.1 through v2.44.3 | 62.9 - 63.5% | 53.1 - 53.5% | 57.4 - 57.9% |
| v2.44.4 (log lane fix) | 69.1 - 69.6% | 58.2 - 58.6% | 63.0 - 63.5% |
The last row is a controlled comparison, same build and data with the log lane as the only variable. That lane previously substring-matched the whole query against log topics and summaries, so a natural-language question never matched and it scored 0.0% on all 1,977 questions. It now tokenizes the query and reaches 53.3% on its own. Ranges rather than single figures because the semantic lane varies about half a point between runs, so a sub-point difference is not a result.
Multi-hop remains the weakest category at 44.9%, and single-hop evidence recall is 36.6% against a 66.2% any-hit rate, so the lane often surfaces some of a question's evidence rather than all of it. Reproduce with run_eval.py.
MARM targets a specific niche: local-first memory for MCP-connected coding agents, not general personalization memory or a full agent runtime. Here's how it differs architecturally from established names in AI agent memory:
| MARM | Mem0 | Letta (MemGPT) | Zep / Graphiti | agentmemory | |
|---|---|---|---|---|---|
| Type | Memory engine, MCP-native | Memory layer API | Full agent runtime | Temporal knowledge graph | Memory engine, MCP-native |
| Required infrastructure | No separate data service (embedded SQLite) | Vector DB (Qdrant/pgvector) | Postgres + vector DB | Neo4j | Separate iii-engine runtime |
| Deployment | Local-first by default; Docker for shared/remote | Cloud API or self-hosted | Self-hosted or cloud | Cloud or self-hosted | Local-first |
| Retrieval model | Hybrid: FTS5 BM25 exact lane + semantic rerank | Vector + graph + key-value | Vector archival store + agent-managed core memory | Temporal knowledge graph (fact validity windows) | BM25 + vector + graph (RRF fusion) |
| Write capture | Explicit tool calls from the connected agent | Explicit add() calls (some integrations auto-extract) | Agent self-edits its own memory | Explicit API calls | Hook-based, automatic (no explicit calls needed) |
| Code structure awareness | Bundled code graph + concept graph, fused with memory | Not built in | Not built in | Not built in | Not built in (pairs with a separate project) |
| Framework lock-in | None (any MCP client) | None | High (must run within Letta) | None | None (any MCP client) |
Disclaimers & Accuracy: Competitor landscapes evolve rapidly. The matrix above reflects core architectural traits as of Q3 2026, based on public documentation and READMEs, not internal testing of each system. If any data point regarding an alternative framework has changed or is misrepresented, please open an issue or submit a Pull Request to update the table. We actively welcome corrections from peer maintainers.
Manual pip install
Swarm / multi-agent note: The write queue is enabled by default to serialize memory writes through one worker. For shared HTTP deployments, use marm-memory start --profile swarm (200 RPM) or --profile swarm-max (600 RPM). --profile trusted disables rate limiting entirely for private deployments. STDIO is still best for private single-agent/local use. See Swarm & multi-agent presets for the full table.
"agent" refers to claude, gemini, grok, qwen, or any MCP client. Codex uses --url instead of --transport to add MCP tools.
Default pip/local startup is zero-config: MARM binds to localhost and does not require a key unless you expose it with SERVER_HOST=0.0.0.0.
Replace marm-mcp-stdio with python -m marm_mcp_server.server_stdio if using a virtualenv or a path-based setup. Works with Claude Code, Cursor, VS Code, Qwen, and Gemini CLI. STDIO stays a single local process with no port and no API key, and exposes the same 16 tools as HTTP.
Use HTTP when multiple agents need to share one live MARM server. STDIO is still best for private single-agent use because each client owns its own local process.
Docker HTTP requires an API key because it exposes MARM as a network server; STDIO stays local to the client process and does not need one.
If you installed MARM through pip, the product CLI can safely preview or run the same setup. It uses a loopback port by default, preserves ~/.marm, stores the generated key in ~/.marm/.env rather than shell history, and refuses to replace an existing container.
The HTTP run, command, and compose commands accept the same operational flags:
| Flag | Purpose |
|---|---|
--data-dir <absolute path> | Persistent host directory mounted at /home/marm/.marm. Defaults to ~/.marm; this holds memory, indexes, logs, and the managed key file. |
--env-file <path> | Explicit Docker env file. It must already contain MARM_API_KEY; without this flag, MARM uses ~/.marm/.env and creates a key there only when docker run or docker compose --yes needs one. |
--port <number> | Host HTTP port. Default: 8001. |
--expose-network | Bind the host port to 0.0.0.0 instead of loopback. This is deliberate network exposure; configure a firewall and TLS proxy. |
--profile standard|swarm|swarm-max|trusted | Select the same write-queue and rate-limit preset as native HTTP startup. |
--rate-limit-rpm <number> | Override the selected profile's HTTP rate limit. 0 disables rate limiting. |
--repo <absolute path> | Repeatable read-only repository mount for code indexing. MARM reports each corresponding /workspace/repo-N path to index inside the container. |
--tag <tag> | Official image tag. Default: latest. |
--pull | Pull the selected image before creating a new HTTP container. |
--name <name> | Managed container name. MARM refuses to replace an existing container with that name. |
--memory <limit> / --cpus <limit> | Optional Docker resource limits. |
--dry-run | docker run only: print the planned command without creating a container or key file. docker command is always a preview. |
For example:
Docker STDIO is separate from Docker HTTP: marm-memory docker stdio-command uses docker run -i --rm, has no port and no bearer key, but still mounts the data directory so SQLite memory persists after the short-lived container exits. Use --data-dir and --tag with that command when needed. There are no separate docker key or docker mount commands; --env-file and --data-dir make those choices explicit in the generated HTTP command.
marm-memory docker pull only downloads an image. marm-memory docker maintenance embeddings migrate runs against the same data mount and refuses while the managed HTTP container is running. The helper is available only with the pip-installed marm-memory command; Docker-only users can use the raw commands below.
--bearer-token-env-var takes the environment variable name, not the raw key. Start or restart Codex from the same shell after setting $env:MARM_API_KEY. For local Docker smoke tests, MARM_API_KEY=test is fine and avoids shell escaping problems; use a generated key for real deployments. A 406 Not Acceptable from the smoke-test GET /mcp means auth reached the MCP endpoint; 401 Unauthorized means the key is missing or mismatched.
Docker graph tools run inside the container, so they cannot see host paths unless you mount them at docker run.
Then index the container path, not the Windows host path:
Graph tools must use the container path. Mounts cannot be added to an already-running container; stop and restart the container with the repo mount when you want Docker graph indexing.
Docker STDIO includes the same built-in marm-graph tools; no extra image or install step is required.
401, verify key match and client restart after env var changes.Start the server (python -m marm_mcp_server), then wire up your client below. Every block assumes the default local install (no key). For Docker or exposed servers, add the Authorization: Bearer header shown in each client's collapsible.
Claude Code supports HTTP, SSE, and STDIO through claude mcp add; use HTTP for MARM. For STDIO: claude mcp add --transport stdio marm-memory-stdio marm-mcp-stdio.
Add to .vscode/mcp.json in your workspace. Use marm-memory-local for direct Python installs; marm-memory-docker for Docker or exposed/key mode.
Open .vscode/mcp.json, click Start above the server you want, then use Copilot Agent or any extension that consumes VS Code's native MCP registry.
Add to .cursor/mcp.json in your workspace. Cursor uses mcpServers, not VS Code's servers root.
For Docker/key mode, launch Cursor with MARM_API_KEY set in the environment.
Codex uses codex mcp add or TOML config at ~/.codex/config.toml (%USERPROFILE%\.codex\config.toml on Windows).
Equivalent ~/.gemini/settings.json (user scope) or project .gemini/settings.json:
Equivalent .qwen/settings.json (project) or ~/.qwen/settings.json (user):
xAI connects from its own infrastructure, so localhost will not work. Expose MARM behind HTTPS and set MARM_API_KEY.
Full platform walkthroughs, key setup, and OS-specific notes: Windows · macOS · Linux · Docker/key mode · Other platforms
Using a client that isn't listed? Open an issue and let us know; client adapters are a first-class feature request.
Requirements
Data location
~/.marm/ (Linux/macOS) or %USERPROFILE%\.marm\ (Windows)~/.marm/index/ database~/.marm/ directory to preserve all dataVerify installation
Use the MCP server health endpoint for the fastest live check:
Expected output includes server version, feature availability (semantic search status), database connection status, and service health status.
💡 Pro Tip: You don't need to manually call these tools! Just tell your AI agent what you want in natural language:
The AI agent will automatically use the appropriate tools. Manual tool access is available for power users who want direct control.
| Tool | What it does | Key parameters |
|---|---|---|
marm_smart_recall | Hybrid memory recall with an additive, bounded concept/code graph sidecar when a compatible graph exists | query, limit, session_name, search_all, detail=1/2/3, project, platform, exact_mode |
marm_log_entry | Add structured session log entries; each entry is also embedded into semantic memory so marm_smart_recall can find it | entry, session_name, project (optional; defaults to the server's detected project) |
marm_log_show | Display all entries and sessions, with filtering | session_name |
marm_delete | Delete a log session, log entry, or notebook entry | type, target, session_name, project, platform |
marm_summary | Cached, paste-ready session summaries with intelligent truncation | session_name |
marm_notebook | Session-scoped scratch pad plus promotion to a permanent, graph-linked doc | action="add"|"use"|"show"|"status"|"clear"|"save", name, data, session_name, project, platform |
marm_compaction | Agent-assisted memory cleanup with a reviewable audit trail | action="status"|"candidates"|"review"|"stage"|"apply"|"discard" |
marm_distill | Turn raw conversation into memory proposals, each resolved against the store as new, duplicate, or near. Selects sentences verbatim by default; with use_llm=true and local generation enabled, writes self-contained facts with a local model instead, keeping the verbatim span each came from. Staged for review, never written unattended | action="propose"|"review"|"apply"|"discard", text, session_name, proposal_id, use_llm |
| Tool | What it does | Key parameters |
|---|---|---|
marm_graph_index | Index a repo into the code-structure graph, check status, list projects, or turn automatic re-indexing on and off | repo_path, project, action |
marm_code_lookup | Find symbols, text patterns, or a symbol's source; use instead of grep/glob | kind="auto"|"symbol"|"text"|"snippet" |
marm_code_context | Composed context for a task in one call: symbols ranked by personalised PageRank, their source read from disk, and what memory records about them | task, project, cwd, budget, detail (0-3; 0 uses MARM_CODE_CONTEXT_DETAIL, the server default), include_graph (default false) |
marm_graph_trace | Trace call paths and data flow from a function | direction, mode |
marm_graph_architecture | Architecture overview: modules, node/edge breakdown, schema | project |
marm_graph_impact | Blast radius of code changes: git diff → affected symbols + risk | since, base_branch, depth |
| Tool | What it does | Key parameters |
|---|---|---|
marm_concept_build | Rebuild the graph, or index memories stored before automatic indexing. New memories are indexed on their own | session_name, project, or search_all=True (one required) |
marm_concept_recall | Explicitly query entities, relationships, and linked code symbols | query, depth (1-5), direction, project, platform |
All 16 tools are available on both HTTP and STDIO. Behind the tool surface, the server handles lifecycle setup, protocol refresh, docs indexing, date context, summary-cache maintenance, write queue handling, concept indexing, code re-indexing as repos change, project/platform attribution, and health checks automatically; none of those consume the agent's attention or tokens. The two graph engines start lazily on first use and never block the 8 core memory tools if they fail to start. See Architecture & Internals for the mechanisms.
MARM handles lifecycle work internally. Docs and session state initialize on the first real tool call, and packaged docs are indexed into the marm_system memory namespace with source-file hash tracking, so your agent can answer MARM usage questions from memory itself.
A realistic workflow showing MARM in action. Scenario: you're researching authentication patterns for a new project using multiple AI clients.
Result: Three different AI clients collaboratively researched a topic, shared insights, and documented decisions. All without re-explaining the project to each new AI.
Knowledge base loop:
marm_log_entry for structured session learningsmarm_summary for knowledge consolidationmarm_notebook(action="add", ...) entriesMulti-AI collaboration: each AI works in dedicated sessions on its strengths, uses marm_smart_recall to build on the others' work, then a collaborative session combines the insights.
search_all=True to search across all sessionsdetail=1 returns a short summary view (~200 chars), detail=2 a larger context view (~500 chars), detail=3 full memory contentmarm_compaction to stage, review, apply, or discard summariesTwo searches, two very different problems, one tool:
The first query is about meaning, so MARM reranks candidates with local vector embeddings — RAG-style semantic search without a hosted vector database. The second is syntax-shaped (a config key), so MARM detects that automatically and routes it through deterministic exact matching instead. This exact-retrieval lane is the difference between a memory system that works in demos and one that answers the questions developers actually ask: config keys, CLI flags, file paths, API names, error strings. Pure-semantic memory systems fail at exactly those queries.
MARM uses filter→rerank hybrid recall plus an exact retrieval lane:
exact_mode="auto", the default): config keys, CLI flags, file paths, API/tool names, dotted namespaces, HTTP routes, URLs, and quoted command strings are detected and routed through deterministic FTS5 BM25 with a LIKE fallback. No embeddings involved, so results are stable and literal.FTS_CANDIDATE_LIMIT, default 200), then semantic embeddings rerank those candidates by meaning. Conservative temporal weighting gives fresher memories a modest boost when matches are otherwise close.RECALL_SCAN_LIMIT). If the response includes recall_scan_truncated=true, the fallback hit its cap; narrow the session/query or raise the env var for larger stores.This is why recall latency stays nearly flat as the store grows (see benchmarks): the semantic rerank always scores a bounded set instead of scanning every embedding.
Exact recall control: exact_mode="auto" is usually right. Use exact_mode="exact" when a query must match literal text such as RECALL_SCAN_LIMIT, --generate-key, or settings.py. Use exact_mode="semantic" when a syntax-looking query should still be treated as meaning-based recall.
MARM automatically categorizes content on write: Code (programming snippets and technical discussions), Project (work conversations and planning), Book (literature, learning materials, research), and General (everything else).
MARM stores nullable project and platform columns on memories, log entries, and notebook entries. The project is detected from the working directory and the platform from the connecting client (Claude Code, VS Code, Cursor, ...); MARM_PROJECT and MARM_PLATFORM override detection. marm_smart_recall(project=..., platform=...) scopes recall without changing the default unfiltered behavior, so one shared server can hold several projects without cross-contamination.
Storing a memory is an explicit act, so the things worth keeping are the ones somebody remembered to keep. marm_distill works the other way round: hand it a stretch of raw conversation and it proposes the durable facts in it.
Every proposal is resolved against what is already stored and carries a verdict: new, duplicate, or near. near is the one that needs a person: it is close enough to a stored memory to be related, and a similarity score cannot say whether it refines that memory, contradicts it, or is simply adjacent. The proposal is shown beside the memory it resembles so a reviewer can decide which.
It proposes; it never writes. propose stages into a review queue and only apply creates a memory. That is deliberately the same shape as marm_compaction, and for the same reason: a similarity score is not evidence enough to write memory unattended, and anything that does so on such a score fills a store with near-misses faster than it fills it with facts. A discarded proposal is never proposed again, enforced by a unique constraint rather than by convention — re-offering something a reviewer already rejected is how a review queue stops being read. It also makes re-running propose over the same text a no-op, which is what makes it safe to call at the end of every session.
How the text is written. With no local model reachable, this selects sentences that already read like durable facts and normalises them, rather than composing new ones — so a fact spread across three turns, or implied but never stated, will not be proposed. It finds what was said plainly, not what was meant. That is a real limitation, and also a reasonable fit: a MARM memory is a headline, and a headline is usually a sentence someone already typed. Asked to (use_llm=true) with local generation enabled and a model reachable, it writes the fact instead and keeps the verbatim span it came from; a proposal's mode reports which of the two happened. See Optional local generation.
While proposals sit unreviewed, MARM can attach a review request to a tool response rather than waiting to be asked. The request names one proposal and asks for a decision on it — apply or discard — and only one is attached per cooldown window, server-wide, so a batch of proposals cannot put a request on every response. MARM_DISTILL_NUDGE=0 turns that off; the cooldown and budget are tunable in the configuration reference.
MARM ships two graph systems that complement the memory store: a code graph that understands your repository's structure, and a concept graph that understands what your stored memories are about. When both are indexed for the same project, concept entities cross-link to code symbols.
marm-graph is bundled into both transports. It indexes a repository once, then lets agents ask code-structure questions without repeatedly scanning files:
The recommended agent workflow: index once, then marm_code_context for a task-shaped question, marm_code_lookup before broad file reads, marm_graph_trace when callers/callees or data-flow context matters, marm_graph_architecture for orientation, and marm_graph_impact before risky refactors. One graph query replaces dozens of grep/read cycles, which is where the token savings come from.
marm_code_contextThe other five tools answer one question each, and an agent typically chains them: search for a name, trace its callers, read each file, then look for anything memory recorded about them. marm_code_context runs that chain server-side and returns the result as one payload:
Six steps: seed with lexical and semantic search, expand along callers and callees, rank that subgraph with personalised PageRank, read the winners' source off disk, recall the memories and memory→symbol links attached to them, and budget the result down to a character limit with the decisive material first.
Step three is the one plain search cannot do. Lexical search answers "which symbols mention these words", which is not the question an agent is asking; a symbol nothing calls and nothing references ranks below one sitting at the centre of the relevant neighbourhood, even when both mention the terms equally. Step five is the one a pure code index structurally cannot do — it carries why the code is the way it is, not only what it says.
detail trades size for structure. 1 (the default) returns the markdown and notes: what an agent needs, and nothing twice. 2 adds symbol and memory metadata — names, files, lines, scores, and the provenance that records whether a symbol was seeded from the task or pulled in along a call edge — without repeating the source already in the markdown. 3 adds source and memory text as structured fields too, which is what a renderer wants and what the Console asks for. MARM_CODE_CONTEXT_DETAIL moves that default for every agent at once. include_graph is a separate switch on a different axis, off by default: it returns the ranked edge list for visualisation, which nothing else reads.
One constraint worth knowing: traces resolve by qualified name. A bare name matching two symbols comes back as status: "ambiguous" with no edges rather than a guess, which leaves ranking with nothing to work on and collapses the result to plain search order.
answer=true goes one step further and answers the task in prose from the composed context alone — see Optional local generation.
Once a repository is indexed, MARM keeps it current on its own. A filesystem watcher notices a save, a commit, a branch switch, or a merge and re-indexes shortly after, debounced so a burst of changes becomes one pass rather than one per file. A periodic reconciliation pass catches anything a watcher event missed and is the only trigger for a directory that is not a git repo. To index only on request instead:
An agent can do the same with marm_graph_index(action="auto_off"), and action="auto_status" reports what is being watched and when each project was last indexed. The switch persists across restarts and beats the GRAPH_AUTO_INDEX environment variable.
Under the hood, the engine is codebase-memory-mcp (MIT), a zero-dependency static binary that parses 158 languages through tree-sitter with Hybrid LSP type resolution for the major ones, indexes an average repository in seconds, and answers symbol search and call tracing in well under a second. Measured on a 149,107-node graph over the persistent connection MARM holds: symbol search 146ms, call tracing 67ms, and the full architecture overview 1.23s, which is the one query that is not sub-second. MARM pins a specific release, verifies its tool schema on startup, and routes the upstream tool set through 6 focused MCP tools so the model surface stays small. The graph backend starts lazily on first graph-tool use, so memory, logging, notebook, and summary tools still start fast. In Docker, the engine binary is baked into the image; local pip installs fetch it on first graph use (~269MB, one time).
Degraded mode: if the graph engine fails to start (no network for the first-run download, disk full, schema drift) or GRAPH_ENABLED=false is set, graph tools return {"status": "error", "message": "graph backend unavailable"} while the other 10 tools keep working normally. Graph failures can never take down memory.
MARM extracts a knowledge graph from the memories you store, producing typed entities (concepts, decisions, patterns, errors, tools, people, organizations) connected by typed relationships (fixes, implements, depends_on, uses, causes, replaces, extends). This happens on its own: storing a memory queues it, and a background worker adds it to the graph roughly 30 seconds later. marm_concept_build is still there for a full or scoped rebuild. Once there is a graph, marm_smart_recall adds bounded related entities, relationships, and linked code as a graph_context sidecar without changing primary memory ranking. marm_concept_recall remains available for explicit graph exploration:
How to use it:
CONCEPT_AUTO_INDEX=false to go back to manual builds only, which stops the worker but keeps recording queue rows, so turning it back on picks up everything written while it was off; CONCEPT_INDEX_DEBOUNCE_SECONDS (30) and CONCEPT_INDEX_BATCH_SIZE (20) control the pace.scripts/benchmarking/performance/bench_concept_worker.py --from-live.marm_concept_build scoped to a session_name, project, or search_all=True indexes memories stored before automatic indexing existed, and rebuilds after an upgrade that requires one.marm_concept_build(search_all=True). A full build backs up and resets only the derived concept database; targeted builds refuse to guess platform ownership.CONCEPT_BUILD_ROW_CAP (default 500) is the page size, so lowering it makes a build read more, smaller pages rather than skipping the rest.marm-memory knowledge status, then reinstall MARM if needed.~/.marm/index/marm_index.db) with its own connection pool, so concept-graph writes can never block or corrupt the production memory database.This fills the cross-session structure gap that flat memory search leaves open: sessions organize memories, but the concept graph connects them, so "what depends on the write queue?" is answerable even when the answer spans five sessions from three different agents.
Everything above runs on a small number of deliberate mechanisms. This section is the full map, so you (or your agent) never have to guess what the server is doing.
~/.marm/marm_memory.db with a connection pool (5 connections). WAL keeps readers unblocked during writes, which matters when several agents recall while one writes.memories_fts) is maintained as an external-content table over the memories table and powers both the exact lane (BM25) and the filter stage of hybrid recall.memory_chunks table, each with its own embedding. Recall scores chunks and collapses to the parent memory.jinaai/jina-embeddings-v2-small-en encoder: 33M parameters, 512 dimensions, an 8,192-token context window, and an Apache-2.0 license. It does not require separate query/document text prefixes. The encoder is lazily loaded on first semantic use and serialized behind a lock so concurrent encodes can't corrupt each other. If it is unavailable, writes still succeed; memories are stored without embeddings until it loads. Semantic scoring runs as a single NumPy batch (matrix cosine) rather than a Python loop.~/.marm/index/marm_index.db) and its own pool, reusing the same pool implementation but never sharing connections with the memory store. Deliberate isolation: an experimental graph build must not be able to stall the production WAL. The one exception is the indexing queue, which lives in the memory database on purpose so a memory and its indexing task commit together; the graph itself stays derived and disposable.MAX_QUEUE_SIZE bounds it.CONSOLIDATION_ENABLED=1) runs two layers before a memory lands:
CONSOLIDATION_THRESHOLD cosine similarity are merged rather than accumulated. This never blocks a write; if the encoder isn't available, the write proceeds unconsolidated.COMPACTION_ENABLED=1) is Layer 3: after enough writes in a session, a background pass detects clusters of related memories using cosine similarity plus union-find connected components, gated by minimum cluster size, minimum age, and an active-session grace period so it never compacts work in flight. MARM then injects a bounded request asking the connected agent to summarize each cluster: candidates → stage → review → apply or discard. Source memory IDs are preserved on apply, so compacted summaries stay traceable to their originals. Staged summaries expire (COMPACTION_STAGING_TTL_HOURS), nudges are capped and cooldown-limited, and the injection has a byte budget. The design is honest about what LLMs are for: MARM detects, the agent summarizes, and a human-reviewable stage/apply/discard loop gates the destructive step.Covered in Understanding MARM Memory: exact lane (FTS5 BM25 + LIKE fallback), filter→rerank (bounded FTS candidates → batch semantic rerank → temporal blend), bounded semantic fallback with an explicit truncation flag, and chunk-collapse scoring. Recall depth (detail=1/2/3) controls how much of each memory is returned, and every MCP response passes through a 1MB response limiter that truncates content intelligently instead of breaking the protocol.
The bundled graph engine runs as a supervised child process, not an import:
result.isError, not JSON-RPC errors, and are converted to clean {"status": "error"} dicts with the upstream's own remediation hint attached.asyncio.to_thread so the event loop never blocks on subprocess IO.HEAD plus a fingerprint of non-ignored untracked files, computed outside the engine so an idle check costs no engine lock and two different edits to the same already-modified file are told apart instead of read as identical. A periodic reconciliation pass catches a missed watcher event, covers a filesystem that cannot be watched, and is the only trigger for a directory that is not a git repo. Git runs with core.fsmonitor disabled and a scrubbed environment, since that setting names a program git would otherwise execute from a watched repository.MARM has never shipped a generative model. Concept extraction is spaCy and search is a sentence encoder, both local, which is why marm_distill selects sentences rather than writing them. This does not change that default — generation is used only once the operator switches it on (the Console's System → Controls toggle, or MARM_LLM_ENABLED=1) and a local server is reachable. Discovering a running model is never enough on its own, and everything keeps working without one.
Two features use it, both only when asked per call, and both degrade rather than fail:
marm_distill(use_llm=true) writes self-contained facts instead of lifting sentences, and keeps the verbatim span each one came from. The proposal's mode says which happened: generated means a model answered, selected means it did not.marm_code_context(answer=true) closes the loop and answers the task in prose, grounded only in the context it just composed — so the ranking decides what the answer is allowed to be about. The Console streams that answer over an internal route, so text appears while the rest is still being written. That stream is the only request: its first event, context, is the composition itself (the payload marm_code_context returns, or its no_project/unavailable status), and the answer is written from exactly that composition, so what the Console displays and what the answer is grounded in are one retrieval. If the model spends its token budget before it finishes writing, the stream sends restart, withdrawing the text it sent, and retries once at the wider budget the non-streaming path uses; done carries truncated when even that was not enough.Loopback is enforced, not documented. A non-loopback host is refused outright rather than warned about, because a configuration mistake pointing this at a hosted endpoint would ship transcripts and source off the machine quietly, with no other symptom. The override exists, requires stating the intent in full (MARM_LLM_ALLOW_REMOTE=i-understand-this-leaves-my-machine), and is named in the refusal.
Every failure is a None, never an exception. A cold model, a busy GPU, a stopped container and a malformed reply all degrade to "no model answer this time". Callers branch on the None; they do not catch. A memory tool must not stop working because an unrelated container was restarted.
An answer is labelled grounded only when its citations check out. answer_status is ok when the answer cites symbols from the context it was written from and names nothing outside it. Otherwise it is unverified: the text is still returned, answer_unresolved lists any cited identifier the context does not contain, and answer_hint says why. The Console's stream carries the same verdict in its final done event, and calls an answer grounded only once that verdict is ok.
That is the contract for marm_code_context(answer=true), which returns "answer": null when a model is unreachable or answers with nothing. The Console's streaming route cannot use it, because a stream has already started by the time generation fails: it emits an SSE error event whose JSON data carries a message, plus a hint when no model is reachable at all. Same outcome either way -- the ranked context stands and only the answer is missing -- but a client reading the stream branches on the event, not on a null.
The endpoint is resolved in order: a runtime choice saved from the Console, then MARM_LLM_URL, then discovery, then the built-in default. Discovery scans loopback for the ports the common local runtimes use and reports what answered rather than what a port usually belongs to, so MARM follows whichever server is actually serving without a restart or a config change. A stated endpoint always wins, so an address you set and that is dead surfaces as dead instead of being silently replaced.
The Console's System → Controls tab surfaces all of it: which server answered and how it was chosen, the models it is serving, the model files found in the usual local roots, and a picker that pins a choice as a durable runtime flag.
127.0.0.1), MARM_API_KEY (Bearer) mandatory the moment the server is network-exposed (SERVER_HOST=0.0.0.0, Docker). --generate-key produces one. Safe by default, zero setup friction locally.~/.marm/; no cloud sync, no telemetry, no external storage.| Flag | Rate Limit | Write Queue | Use When |
|---|---|---|---|
| (none) | 80 RPM | enabled | Normal local use and small 3-5 agent setups |
--swarm | 200 RPM | enabled | Shared HTTP server, roughly 15-30 agents depending on write style |
--swarm-max | 600 RPM | enabled | Heavier local/private swarm, roughly 50-100 agents depending on write style |
--trusted | disabled | enabled | Private/trusted deployments only |
--rate-limit-rpm N | N RPM | unchanged | Custom override; 0 disables limiting |
The write queue serializes memory writes regardless of preset; swarm flags tune the HTTP rate limit on top of that. The queue controls write ordering; consolidation and compaction are separate memory-maintenance layers. This stack (WAL + pooling + one serialized writer + RPM presets) is intentionally scoped to "SQLite, many agents, one machine"; distributed multi-node memory is out of scope for the current design.
Packaged docs are indexed into the marm_system memory namespace on startup and refreshed every 50 tool calls, with source-file hash tracking so unchanged docs are skipped and changed or deleted rows are re-indexed. Connected agents can answer MARM usage questions with marm_smart_recall instead of you pasting docs at them.
| Variable | Default | What it controls |
|---|---|---|
SERVER_HOST | 127.0.0.1 | Bind address; 0.0.0.0 exposes the server and makes MARM_API_KEY mandatory |
SERVER_PORT | 8001 | HTTP port |
MARM_API_KEY | (empty) | Bearer key for network-exposed deployments |
MARM_DB_PATH | ~/.marm/marm_memory.db | Memory database location |
MARM_CONCEPT_DB_PATH | ~/.marm/index/marm_index.db | Concept graph database location |
MARM_PROJECT / MARM_PLATFORM | (auto-detected) | Override project/platform attribution |
MARM_RATE_LIMIT_RPM | 80 | Requests per minute per IP (presets override) |
WRITE_QUEUE_ENABLED | 1 | Serialize writes through one worker |
FTS_CANDIDATE_LIMIT | 200 | BM25 candidates fetched before semantic reranking; raise for stores with weak keyword overlap, lower to tighten results to the closest keyword matches |
RECALL_SCAN_LIMIT | 10000 | Cap on the semantic fallback scan; recall_scan_truncated=true in responses means it was hit |
FTS_QUERY_MODE | or_nostop | How semantic recall builds its keyword query: or_nostop ignores filler words then matches any remaining term, or matches any term, and requires every term (the pre-2.31.0 behavior). The exact/lexical lane always requires every term. |
FTS_EXTRA_STOPWORDS | (empty) | Comma-separated extra words to ignore when building keyword queries, for terms so common in your store they carry no signal |
HYBRID_SEARCH_TEXT_WEIGHT | 0.05 | How much the keyword score influences ranking. Set from a benchmark sweep; accuracy peaks across 0.04-0.08 and falls off sharply above 0.10. At 0.0, keyword matching narrows which memories are considered but does not reorder them. |
FTS_LONE_HIT_SCORE | 1.0 | Keyword score used when only one memory matches, or when every match ties. Lower it on small stores if a single keyword match should not count as a perfect one. |
SEMANTIC_SEARCH_ENABLED | 1 | Set to 0 to run without the embedding model: nothing is loaded, no embeddings are written, and recall falls back to keyword matching. Useful on low-memory hosts, or to see how recall behaves when the model is unavailable. marm-memory doctor reports when it is off. |
TEMPORAL_WEIGHT / TEMPORAL_HALF_LIFE_DAYS | 0.1 / 30 | Strength and decay of the recency boost |
CONSOLIDATION_ENABLED | 0 | Write-time dedup + semantic merge |
CONSOLIDATION_THRESHOLD | 0.92 | Cosine similarity needed to merge near-duplicates. Compared against meaning-similarity alone, not the blended ranking score |
COMPACTION_ENABLED | 0 | Background cluster detection + agent-assisted compaction |
COMPACTION_TRIGGER_COUNT | 5 | Writes per session before a compaction pass. A maintenance pass also runs on the scheduler interval, so a session that stops being written to is still scanned once its memories age past COMPACTION_MIN_AGE_HOURS |
COMPACTION_SIMILARITY_THRESHOLD / COMPACTION_MIN_CLUSTER_SIZE / COMPACTION_MIN_AGE_HOURS | 0.88 / 3 / 24 | Cluster detection gates |
COMPACTION_STAGING_TTL_HOURS | 168 | How long staged summaries wait before expiring |
MARM_DISTILL_NUDGE | 1 | Whether MARM may attach a review request for one waiting proposal to a tool response, asking the agent to apply or discard it. Set 0 to never ask |
MARM_DISTILL_MAX_NUDGES | 3 | Times a single proposal may be asked about before it is marked nudge_exhausted and stops being offered |
MARM_DISTILL_NUDGE_COOLDOWN | 900 | Seconds between review requests. Server-wide, not per proposal or per session |
MARM_DISTILL_INJECTION_BYTES | 1536 | Byte budget for the nudge injected into the agent's context |
MARM_LLM_ENABLED | unset (off) | Switch optional local generation on. A choice saved from the Console overrides it in either direction. Finding a running model does not turn generation on |
MARM_LLM_URL | http://127.0.0.1:18080 | Local OpenAI-compatible endpoint for optional generation. A stated address wins over discovery, so one that is dead surfaces rather than being silently replaced |
MARM_LLM_ALLOW_REMOTE | unset | Must be the exact string i-understand-this-leaves-my-machine to permit a non-loopback endpoint. Anything else, including 1 or true, is refused |
MARM_LLM_TIMEOUT | 120 | Seconds to wait for a completion. Generous on purpose: a shared GPU makes a slow answer normal rather than broken |
MARM_LLM_MAX_RETRY_TOKENS | 8192 | Ceiling for the single wider retry issued when a model spends its whole budget without producing content |
GRAPH_ENABLED | true | Kill switch for the 6 code-graph tools |
GRAPH_AUTO_INDEX | true | Automatic re-indexing of repos already in the code graph. A saved switch from projects auto off or marm_graph_index(action="auto_off") overrides this, so a value set here cannot re-enable what a user turned off |
GRAPH_AUTO_INDEX_DEBOUNCE_SECONDS | 2 | Quiet period after a watcher event before a repo is evaluated, so a burst of saves becomes one re-index. Minimum 0.5 |
GRAPH_AUTO_INDEX_RECONCILE_SECONDS | 300 | Fallback pass that catches a missed watcher event, covers a filesystem that cannot be watched, and is the only trigger for a directory that is not a git repo. Minimum 60. Replaces the deprecated GRAPH_AUTO_INDEX_FULL_INTERVAL, whose value carries over automatically if this is unset. GRAPH_AUTO_INDEX_INTERVAL (the old fixed poll) is deprecated and no longer read for anything but a warning |
GRAPH_AUTO_INDEX_MODE | moderate | Index depth for automatic re-indexes: full, moderate, or fast. Anything else warns and falls back |
MARM_CODE_CONTEXT_DETAIL | 1 | Default detail for marm_code_context when a caller passes 0. Clamped to 1-3. Raising it is the lever for a client that renders the structured parts itself; leaving it at 1 keeps the response to the markdown block, which is what most agents read |
GRAPH_AUTO_INDEX_LEASE_SECONDS | 120 | How long the indexing gate stays owned once nothing is renewing it. A running index renews its own lease, so this bounds how long a killed process blocks indexing, not how long an index may take |
GRAPH_AUTO_INDEX_PROJECT_TTL | 300 | How long the list of watched projects is trusted before it is re-read from the engine |
CONCEPT_BUILD_ROW_CAP | 500 | Memory rows read per page during a concept-graph build. Not a cap on the build: every memory in scope is read either way |
CONCEPT_AUTO_INDEX | true | Automatic concept indexing of new memories. false, 0, no, or off stops the worker and leaves builds manual. Writes still record queue rows either way |
CONCEPT_INDEX_DEBOUNCE_SECONDS | 30 | Quiet period after a write before indexing starts, so a burst becomes one pass |
CONCEPT_INDEX_BATCH_SIZE | 20 | Memories indexed per batch, capped at 500. Lowering it does not reduce contention; it measured slightly worse |
CONCEPT_INDEX_BATCH_PAUSE_MS | 250 | Pause between batches while clearing a backlog. Cuts worst-case recall during indexing from ~270ms to ~80ms for about 18% longer drain. 0 disables it |
CONCEPT_INDEX_LEASE_SECONDS | 300 | How long a claimed indexing task stays owned once nothing is renewing it. Work in progress renews its own lease, so this bounds how long a killed process holds tasks, not how long a batch may take. Reclaimed tasks spend no attempt |
CONCEPT_INDEX_MAX_ATTEMPTS | 3 | Failed attempts before a memory is parked with its error instead of retried |
The Jina v2 Small default uses 512-dimensional embeddings; older all-MiniLM-L6-v2 data is 384-dimensional and must be re-embedded. Stop every MARM HTTP and STDIO process, then run:
It re-embeds memory, chunk, and any existing concept-graph vectors (notebook scratch entries no longer carry embeddings), reports progress, verifies both databases, and is resumable after an interruption. It refuses to start against a live HTTP server; STDIO processes cannot be detected reliably and must be stopped manually.
Memories over 500 words are also stored as smaller chunks. Chunk sizing changed across versions, and the migration above re-embeds chunks without re-splitting them, so older chunks keep stale boundaries. Stop every MARM process, then run:
It re-splits stale chunks, fills in any lost to an interrupted write, and drops chunks from memories now under the threshold. Memories already correct are skipped without loading the encoder, so rerunning costs nothing. Same live-server guard as above, plus it refuses when stored vectors do not match the configured embedding model: migrate first in that case. Recall works without this, just less accurately on long memories.
Server won't start
python --version (must be 3.10+)lsof -i :8001 (macOS/Linux) or netstat -ano | findstr :8001 (Windows)~/.marm/ must be readable/writable)STDIO connection fails
marm-mcp-stdio is on your PATH after pip install: marm-mcp-stdio --helppython -m marm_mcp_server.server_stdiopython -m marm_mcp_server.server_stdioAI client can't connect to MARM
curl http://localhost:8001/healthmarm-mcp-stdio (console script) or python -m marm_mcp_server.server_stdioTools not appearing in AI client
curl http://localhost:8001/healthGraph tools return graph backend unavailable
GRAPH_ENABLED is not set to false (affects both HTTP and STDIO; graph tools have full parity across both transports)Concept tools return entities_extracted: 0
marm-memory knowledge status; if it reports a missing runtime or model, repair the install with python -m pip install -U --force-reinstall marm-mcp-server.New memories are not showing up in the graph
marm-memory knowledge status. index_queue.pending is how many memories are waiting; index_queue.parked is how many gave up. auto_index: false means indexing is switched off.CONCEPT_AUTO_INDEX is not set to false, 0, no, or off.marm-memory knowledge status reports rebuild_required, run marm_concept_build(search_all=True) once; queued memories are picked up after it.Code changes are not showing up in the code graph
marm-memory projects auto status. enabled: false means automatic re-indexing is switched off; source: override means a saved switch is what turned it off, not the environment.marm-memory projects list shows what is enrolled.An index returns index_in_progress
A build returns build_in_progress
A build returns lock_lost
Memories not saving
~/.marm/ directory exists and has write permissionsmarm_log_showcurl http://localhost:8001/healthSearch returns no results
marm_log_show to list entriessearch_all=True to search across all sessionsMemories appear then disappear
~/.marm/)Lost or corrupted data
~/.marm/ directory for backup copies (if you created them)~/.marm/ back to the home directoryDatabase locked error
Ctrl+Ccp -r ~/.marm ~/.marm.backuplsof ~/.marm/marm_memory.db (macOS/Linux) or check Task Manager (Windows)sqlite3 ~/.marm/marm_memory.db "PRAGMA integrity_check;"Slow search results
limit=10 instead of unlimited resultsmarm_summary to compress old sessionsServer using too much memory
marm_notebook(action="clear") to prune active entriesmarm_compaction(action="review") to inspect staged compaction summaries when compaction is enabled| Error | Cause | Solution |
|---|---|---|
address already in use | Port 8001 occupied | Kill process on 8001 or use different port |
permission denied: ~/.marm/ | Database directory not writable | chmod 755 ~/.marm/ or check ownership |
module not found: core.memory | Missing dependencies | Reinstall from marm-mcp-server/: pip install -e ".[dev]" |
database is locked | Multiple processes accessing DB | Close other connections, restart server |
embedding model not found | Semantic search model didn't download | First run takes time; be patient, check internet connection |
For memory behavior, transports, supported clients, compaction, and backup questions, see the FAQ.
MARM welcomes contributors at every level. Code helps, but so do docs, setup notes, client testing, bug reports, benchmarks, and real workflow feedback from people using AI tools every day.
Good places to help:
💡 Want to get your name on this list? Check out our CONTRIBUTING.md guide to get started!
Help build the future of AI memory - no coding required!
Connect: MARM Discord | GitHub Discussions
Copyright © 2026 Ryan A. Lyell. MARM is released under the Apache 2.0 License (see NOTICE for the copyright statement), and forks, experiments, and integrations are welcome. MARM also wraps third-party open-source components such as codebase-memory-mcp under MIT; see THIRD_PARTY_NOTICES.md for attribution. If you build on it, please make unofficial versions easy to distinguish from releases published by the official MARM repository so users know what they are installing.