{
  "markdown": "# Compartment\n\n**Encrypted, fully offline memory for AI agents.** One vault on your own\ncomputer, read and written by Claude Code, Claude Desktop, Hermes Agent,\nOpenClaw, Cursor, Codex and any other MCP client. No API key, no account, no\nnetwork, no telemetry.\n\n[![PyPI](https://img.shields.io/pypi/v/compartment)](https://pypi.org/project/compartment/)\n[![Downloads](https://static.pepy.tech/badge/compartment)](https://pepy.tech/project/compartment)\n[![CI](https://github.com/MaxFreedomPollard/Compartment/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/MaxFreedomPollard/Compartment/actions/workflows/ci.yml)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)\n\n[![MCP Toplist](https://mcptoplist.com/badge/io.github.MaxFreedomPollard%2Fcompartment.svg)](https://mcptoplist.com/server/io.github.MaxFreedomPollard%2Fcompartment)\n[![Cursor Directory](https://img.shields.io/badge/Cursor_Directory-listed-111111)](https://cursor.directory/plugins/compartment)\n[![Glama](https://img.shields.io/badge/Glama-listed-2ea44f)](https://glama.ai/mcp/servers/MaxFreedomPollard/Compartment)\n[![MCP Market](https://img.shields.io/badge/MCP_Market-listed-0ea5e9)](https://mcpmarket.com/server/compartment)\n[![mcpservers.org](https://img.shields.io/badge/mcpservers.org-listed-8b5cf6)](https://mcpservers.org/servers/maxfreedompollard/compartment)\n[![LobeHub](https://lobehub.com/badge/mcp/maxfreedompollard-compartment)](https://lobehub.com/mcp/maxfreedompollard-compartment)\n\n**One-click install** (after `pip install compartment && compartment init`):\n\n<p>\n<a href=\"https://cursor.com/install-mcp?name=compartment&config=eyJjb21tYW5kIjoiY29tcGFydG1lbnQiLCJhcmdzIjpbIi0tY2FsbGVyIiwiY3Vyc29yIiwic2VydmUiXX0=\"><img src=\"https://cursor.com/deeplink/mcp-install-dark.svg\" alt=\"Add to Cursor\" height=\"32\"></a>\n<a href=\"https://vscode.dev/redirect/mcp/install?name=compartment&config=%7B%22command%22%3A%22compartment%22%2C%22args%22%3A%5B%22--caller%22%2C%22vscode%22%2C%22serve%22%5D%7D\"><img src=\"https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white\" alt=\"Install in VS Code\" height=\"32\"></a>\n<a href=\"https://insiders.vscode.dev/redirect/mcp/install?name=compartment&config=%7B%22command%22%3A%22compartment%22%2C%22args%22%3A%5B%22--caller%22%2C%22vscode%22%2C%22serve%22%5D%7D&quality=insiders\"><img src=\"https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=for-the-badge&logo=visualstudiocode&logoColor=white\" alt=\"Install in VS Code Insiders\" height=\"32\"></a>\n<a href=\"https://maxfreedompollard.github.io/Compartment/add/lmstudio\"><img src=\"https://files.lmstudio.ai/deeplink/mcp-install-dark.svg\" alt=\"Add to LM Studio\" height=\"32\"></a>\n<a href=\"https://maxfreedompollard.github.io/Compartment/add/goose\"><img src=\"https://img.shields.io/badge/goose-Install_Extension-1f1f1f?style=for-the-badge\" alt=\"Install in goose\" height=\"32\"></a>\n<a href=\"https://kiro.dev/launch/mcp/add?name=compartment&config=%7B%22command%22%3A%22compartment%22%2C%22args%22%3A%5B%22--caller%22%2C%22kiro%22%2C%22serve%22%5D%7D\"><img src=\"https://kiro.dev/images/add-to-kiro.svg\" alt=\"Add to Kiro\" height=\"32\"></a>\n</p>\n\nClaude Code, Claude Desktop, Hermes Agent and OpenClaw are wired by one\ncommand instead: `compartment integrate claude`, `hermes` or `openclaw`.\n\nCompartment is persistent memory for AI agents, stored on your own computer.\nWhat an agent learns in one session is available in every later session, in\nevery project, to every agent on the machine, and nothing leaves the machine.\n\nEach memory is a single claim, recorded with its source and the date it was\nlearned. Memories can expire: set `expires` and the memory is removed after\nthat date. When a preference changes, the new one replaces the old one.\nRecall is a hybrid vector and keyword search over an in-memory index. It\nanswers in about 12 ms and returns only what is relevant.\n\nThe embedding model is included in the package. Everything on disk is\nencrypted, including the embedding vectors, and only your passphrase opens\nit. A new vault comes with about 6,700 reference facts about hardware,\noperating systems, ports, encodings and shell tools. They are ordinary\nmemories, and one switch removes them from search.\n\n## How it compares with other memory servers\n\nWhere each server keeps memory and what protects it, as documented by each\nproject on 2 September 2026. Sources and the full table are in\n[docs/COMPARISON.md](docs/COMPARISON.md); corrections are welcome as a PR\nagainst that file.\n\n| | Memory at rest | Encrypted | Account / API key | Network at runtime |\n|---|---|---|---|---|\n| **Compartment** | one encrypted file; index in RAM | **yes, vectors too** | none | none, CI-enforced |\n| `@modelcontextprotocol/server-memory` | plaintext `memory.jsonl`, substring search | no | none | none |\n| mem0 (open source) | vector store + LLM-extracted facts; its MCP server is hosted only | not documented | LLM key | LLM calls; telemetry on by default |\n| Graphiti (Zep) / Letta | Neo4j / server + database | not documented | LLM key | LLM calls; telemetry on by default |\n| claude-mem | local SQLite + Chroma | not documented | sign-in required | account + provider calls; telemetry on by default |\n| basic-memory (AGPL) | Markdown + SQLite | not documented | none | telemetry on by default |\n| Hindsight (Vectorize) | one container with embedded PostgreSQL | not documented | LLM key (local models configurable) | LLM calls; vendor states no telemetry |\n| Supermemory | cloud service, or a self-hosted prebuilt binary | not documented | account (cloud) or LLM key (self-host) | cloud calls; self-host: vendor states no telemetry |\n| Cognee | SQLite + LanceDB + Kuzu locally, or cloud | not documented | LLM key | LLM calls; telemetry on by default |\n| MemOS | Neo4j + Qdrant self-hosted, or cloud | not documented | LLM key | LLM calls; telemetry on by default |\n\n## The memory logic\n\n**Almost everything is stored.** Only empty turns are dropped. A bare \"OK\" is\na decision, not noise: when the agent asks *\"Want me to send this reply to\nthe client now?\"* and the user answers *\"OK\"*, Compartment stores the\ndecision together with the question it answered. Small talk is kept but\nranked last.\n\n**Importance is assigned by fixed tiers.** Decisions and consent 0.90,\npersonal facts and preferences 0.80, the user's machine and configuration\n0.75, other substantive statements 0.55, small talk 0.20. Importance\nmultiplies a match score rather than adding to it, so it breaks near-ties in\nfavour of what matters and can never surface a memory that did not match the\nquestion.\n\n**One claim per memory, enforced.** The store rejects anything longer than\n200 characters (the `max_memory_chars` setting), and anything containing\nlists, headings or paragraphs, with an error that says how to split it.\nInstructions alone did not work: on a real vault, the median memory written\nby an agent was 1,938 characters of bulleted session log. `memory_store_many`\nstores a batch in one call. `compartment atomize` splits over-limit memories\nin an existing vault; each piece keeps the original's dates, and the original\nis marked superseded but stays readable by id.\n\n**Every memory records its source and date.** `source` is required: \"from\nchat\", \"read from pyproject.toml\", \"web search\". `discovered` is the date the\nfact was learned, separate from the date it was saved. Both are appended to\nthe text as a short clause, for example `[web search, 2026-08-01]`.\n\n**Memories can expire.** For a fact that stops being true on a known date,\nsuch as a sale price, a booking or a door code, set `expires` to that date\n(`2026-09-03`) or to a duration (`14d`, `2w`, `3m`, `1y`). The memory is\nremoved after that date. `compartment expire` runs the sweep by hand;\n`expire_memories` turns it off. Most facts should not expire; a wrong expiry\ndeletes a memory the user wanted.\n\n**Facts accumulate; opinions update.** A new fact is added beside the\nothers: the door code changed, a script lives at a path, a release shipped.\nAn opinion replaces one. When a preference is stored with `kind=\"opinion\"`,\nthe vault looks for a similar live opinion first. If it finds one, it returns\nthe old record instead of inserting, and the caller resends with\n`supersedes=[old id]` to replace it, or `supersedes=[]` to keep both.\nRestating a live opinion refreshes its date instead of storing a copy.\nSuperseded records are removed from search but kept in the audit chain and\nreadable by id, with a pointer to their replacement. `supersedes` also works\non facts, for corrections. Opinion ranking weights recency much more than\nfact ranking, so the newest opinion wins. `compartment opinions audit` finds\noverlapping live opinions in older vaults and keeps the newest, or reports\nthem for manual merging.\n\n**Capture does not depend on the model.** A host that declares its own\nmemory in its system prompt can override any tool instruction. So\n`integrate claude` installs a `PostToolUse` hook that writes each memory\nfile Claude Code saves into the vault, whether or not the model calls the\ntool. The hook leaves your other hooks untouched, backs up `settings.json`\nfirst, always exits successfully so it can never break your editor, and does\nnothing while the vault is locked. `compartment hook status | install |\nuninstall`, or `integrate claude --no-hooks`. `compartment import-claude`\nimports anything the hook missed.\n\n**Search returns what is relevant, not a fixed number.** Compartment returns\nevery memory whose score holds up against the best result for the same\nquestion, up to a generous cap. The cut is relative because scores are not\ncomparable between questions: on a real vault, the nonsense query \"how to\nbake sourdough bread\" scored higher than the real query \"what did Max decide\nabout Airtable\". A question the vault knows nothing about returns nothing.\nPass `top_k` to get exactly that many.\n\n**Tags are kept current.** What a memory is about never changes; what it is\nrelevant to does. Suppose that while working on a project called Northwind\nyou learn that the client wants figures before conclusions. The agent tags\nthe memory `northwind`. Two years later the same client, now called Harbour,\nhires you again, and the agent searches with the tag `harbour`. The memory\nis still true, but a tag filter cannot find it. So a background pass gives\nevery memory the tags its nearest neighbours in embedding space carry,\nweighted by similarity: as Harbour memories accumulate near that old one, it\npicks up the `harbour` tag. Two more signals run alongside: tags that almost\nalways occur together imply each other, and an existing tag whose phrase\nappears in a memory's text is attached. The pass writes only tags, never\ntext, dates or embeddings. It only adds tags unless you pass `--prune`,\n`tags_origin` preserves the original tags, and `compartment retag --dry-run`\nshows what would change.\n\n**A graph as well as a list.** `memory_link` records a relation: subject,\npredicate, object, optionally tied to a memory and to a validity window.\n`memory_relations` answers by entity, by predicate, or as of a date.\nCompartment stores and matches relations deterministically; the host model\ndecides what to link.\n\n**Memories are data, not instructions.** Recalled memories are wrapped with\na notice that they are stored data. Content from an untrusted source can be\nmarked `quarantined`, which adds a warning to every recall of it. The host\nagent must still treat memory as data.\n\n**One embedding model per vault.** The model's SHA-256 is recorded in the\nvault and checked on open, so similarity scores stay comparable. To change\nmodel, run `compartment reindex --re-embed`.\n\n**No LLM inside.** Embeddings run locally with a bundled 384-dimension int8\nONNX model in under 300 MB of RAM. The host model decides what to store and\nforget; Compartment captures, encrypts and recalls. That split keeps the\noffline guarantee absolute and every decision reproducible. With an offline\nLLM, the whole agent runs with no network.\n\n**See what it learned.** `compartment recent` lists the newest memories,\nhiding the reference facts so your own memories are visible.\n`compartment status` reports `organic_records` beside the total.\n`memory_recent` is the same view over MCP.\n\n## The app and the dashboard\n\n<p align=\"center\">\n  <img src=\"docs/images/menubar-panel.png\" width=\"360\" alt=\"The macOS panel: vault state, settings, connected agents, the last five memories\">\n</p>\n\nThe same panel on each system: the **menu bar** on macOS, the\n**notification area** on Windows, and an ordinary window on **Linux**, listed\nin the applications menu. Linux gets a window on purpose: a tray icon may\nnever appear on GNOME or Wayland, and the control that unlocks your memories\nmust not fail silently.\n\nThe panel shows whether the vault is open, how many memories it holds and how\nmany you stored, the three settings worth changing (capture hook, whether\nreference facts appear in search, auto-lock), which agents are connected with\nbuttons to connect Claude, Hermes Agent or OpenClaw, and the last five\nmemories. You can unlock, lock and change your passphrase there without a\nterminal. The app keeps no vault in memory; it reads state from the CLI, so\nit costs nothing when idle. It is meant to be one of the many apps on your\ncomputer, not something you have to learn: every function is a button or a\nswitch, and the defaults were chosen by measurement.\n\nThe **Dashboard** button opens the whole vault in your browser: growth over\ntime, the relation graph with every entity named, tags, per-agent counts and\nlive search. It is served from RAM on 127.0.0.1 only, read-only, with no\noutbound requests.\n\n<p align=\"center\">\n  <img src=\"docs/images/dashboard-tables.png\" width=\"820\" alt=\"compartment dash: namespaces, memories per agent, relation types, top tags and search\">\n</p>\n\n<p align=\"center\">\n  <img src=\"docs/images/dashboard.png\" width=\"820\" alt=\"compartment dash on a 51,000-memory vault: growth over time and the relation graph\">\n</p>\n\n## The mathematics\n\nEverything below is in one file,\n[`src/compartment/ranking.py`](src/compartment/ranking.py), used by the\nvault, the dashboard and the benchmark. A benchmark score therefore measures\nthe product itself.\n\n### Storage: long memories are embedded in windows\n\nThe encoder reads 512 tokens. Text past that is not seen at all, so a long\nmemory used to be searchable only by its opening. On a real 6,705-memory\nvault, 40% of records exceeded the window and **57.6% of the text was\ninvisible to semantic search**.\n\nSo each record is embedded as overlapping windows of `W = 448` tokens with a\nstride of `S = 384`, giving 64 tokens of overlap so no fact is cut in half,\nand the record is scored by its best window:\n\n```\nwindows(d) = ceil( max(0, tokens(d) - W) / S ) + 1        capped at 64\n\ns_vec(d)   = max over windows w of d :  cos(q, w)\n```\n\nMax, not average: a memory is relevant if any part of it is, and an average\nwould penalise a long memory for its other parts. With one window per record\nthis is identical to the old behaviour, so short memories are unaffected.\nMost memories are short: 6,705 records produced 6,785 windows. Windows are\nmeasured in model tokens, not characters, because a character budget is off\nby a factor of three between prose and a hex digest.\n\n### Recall: two channels, combined as evidence\n\nTwo indexes answer different questions. The vector index answers what a\nmemory means; the keyword index answers what it says. Their scores are not on\nthe same scale, and combining them is the whole problem.\n\nUntil 4.7, Compartment added them. Adding lets a merely-good semantic match\noutvote conclusive literal evidence: searching a real vault for a commit SHA\nthat appears in exactly one memory returned that memory **below ten\nparaphrases of it**, because the sum buried a first-place keyword hit.\n\nThe two channels are alternatives, not addends: either one alone can\nestablish relevance. That is a soft OR over independent evidence,\n\n```\nP(relevant) = 1 - (1 - p_vec)(1 - p_lex)\n```\n\nand the score is its logarithm, which ranks identically but keeps spreading\nresults near the top instead of saturating at 1:\n\n```\nscore(d) = - w_vec · log(1 - p_vec(d))  -  w_lex · log(1 - p_lex(d))\n\nw_vec = 0.75      w_lex = 0.25\n```\n\nEither channel near certainty carries the memory alone; neither can veto the\nother.\n\n**Turning a cosine into a probability.** An L2-normalized encoder gives\ncosines that are comparable across queries, so fixed bounds map them.\nPer-query min-max normalization would rescale the best hit of a hopeless\nquery up to 1.0 and throw that information away.\n\n```\np_vec(d) = clamp( (cos(q, d) - 0.25) / (0.85 - 0.25),  0,  0.88 )\n```\n\nThe 0.88 ceiling matters. A cosine is a similarity, never an identity: an\nencoder can say *this is about the same thing*, never *this is the record you\nnamed*. A literal match on a string unique to one memory can. So the semantic\nchannel is capped below what the literal channel can reach, and the cap is\nforced by the weights: the literal channel tops out at\n`0.25 · -log(1 - 0.999) = 1.727`, so `0.75 · -log(1 - cap) < 1.727`, giving\n`cap < 0.90`.\n\n**Turning a keyword hit into a probability, without BM25.** BM25 measures\nhow well a document matches, which does not settle a contest against a\nsemantic hit. What settles it is how unlikely the match was by chance. Each\nquery term carries its self-information over the vault, and a memory scores\nthe fraction of the query's information it accounts for:\n\n```\nI(t)     = log( N / (1 + df(t)) )                     N = records in the vault\n\np_lex(d) = ( Σ I(t) for query terms t present in d ) / ( Σ I(t) for all t )\n```\n\nA term unique to one memory is near-conclusive; a term in a tenth of the\nvault is almost nothing, whatever its BM25. This is what makes literal and\nsemantic hits comparable.\n\nThe keyword index is queried as AND first, because an exact phrase is the\nstrongest signal. FTS5's implicit AND requires a nine-word question to appear\nword for word, so when AND finds nothing it falls back to OR over the\ninformative terms only: anything in more than 10% of records is dropped. That\nthreshold is measured from the vault, not taken from an English stopword\nlist, so it works the same for code, names or other languages.\n\nA small rank-agreement term is added, the one thing reciprocal-rank fusion\ndoes well, sized to break ties:\n\n```\n+ w_rrf · k · [ 1/(k + rank_vec) + 1/(k + rank_lex) ]      w_rrf = 0.10, k = 20\n```\n\n### Importance and recency multiply the score\n\n```\nfinal(d) = score(d) · ( 1 + w_imp · (2·importance(d) - 1)\n                          + w_rec · 2^( -age_days(d) / half_life ) )\n\nfacts:     w_imp = 0.15   w_rec = 0.10   half_life = 180 days, from `created`\nopinions:  w_imp = 0.15   w_rec = 0.30   half_life = 30 days,  from the last\n                                         re-affirmation (`affirmed`)\n```\n\n**Multiplicative, so a prior can only reorder memories that already\nmatched.** An additive prior would let an important memory surface for an\nunrelated question. A memory that matched nothing scores zero and stays\nthere.\n\n**Centred on the 0.5 default**, hence `2·importance - 1`. Every unweighted\nmemory carries 0.5, including the thousands of reference facts, so without\ncentring they would all get the same boost and importance would do nothing.\nCentred, an unweighted memory is neutral and only a deliberate weight moves\nit.\n\nA fact's recency bonus halves every 180 days from when it was stored. An\nopinion's halves every 30 days from when it was last re-affirmed, at three\ntimes the weight, so the newest opinion on a subject wins.\n\n### Retrieval order\n\nNamespace, tag, date and reference-fact filters run after ranking, so a pool\nsized to the requested number of results could be emptied by them while\nmatching memories sit just past the cut. The pool starts at 200 per channel\nand widens up to three times when filtering leaves too few. Below 20,000\nrecords the vector search is exact (SIMD matrix math, recall 1.0); above\nthat, HNSW at about 99% recall.\n\n## Security and the lock model\n\nThe primitives: XChaCha20-Poly1305 encryption on everything at rest,\nincluding embedding vectors, because vectors can be inverted back to text ·\nArgon2id key slots, LUKS-style · a key per record, so `forget --shred`\ndestroys the key and the content is unrecoverable rather than marked deleted\n· an fsync'd sealed journal, atomic compaction, and tested kill -9 recovery ·\na hash-chained audit log (`compartment audit verify`) · signed vault\nmanifests and packs · stdio transport with no open ports · a runtime guard\nthat aborts on any socket attempt (`--assert-offline`), with CI running the\nwhole suite under it on Linux, macOS and Windows. The full threat model,\nincluding what Compartment cannot protect against, is in\n[SECURITY.md](SECURITY.md).\n\n### From the app\n\nEverything you do day to day is a button. **Unlock** asks for your\npassphrase; **Lock** closes the vault and clears every stored credential;\n**Change password** rekeys it; **Auto-lock** chooses 15, 30 or 60 idle\nminutes, or never. Compartment never generates a password, seed or recovery\nphrase, and holds no credential you do not.\n\nAfter an unlock the vault stays open across processes, logouts and logins\nfor as long as you leave it, until a restart or power loss, until the\nauto-lock timer fires, or until you lock it. A restart or power loss always\nlocks it: the unlock credential is the master key wrapped with a random\nper-boot secret that lives only in kernel memory and is never written to\ndisk, so a new boot cannot open it. A copy of the credential file on its own\nis useless.\n\n### From the command line\n\nThe same controls, plus two that only exist here:\n\n- **`compartment unlock`** and **`compartment lock`** do what the buttons do.\n  Agents can lock with the `memory_lock` tool. (Vaults from older versions\n  that were issued a recovery phrase still accept it.)\n- **`compartment 2fa enable`** adds a second factor: your passphrase plus a\n  keyfile, for example on a USB stick. Both feed Argon2id together, so the\n  requirement is enforced by the cryptography, not by a setting; a stolen\n  vault file plus your passphrase opens nothing without the keyfile. The\n  keyfile's location is remembered, so unlocking feels the same while it is\n  present.\n- **`compartment unlock --keychain`** on macOS is an explicit opt-in that\n  survives reboots.\n\nThe `memory_unlock` MCP tool exists but is off by default, because enabling\nit puts the passphrase in the model's context.\n\n## One vault, many agents, any machine\n\n### Without the command line\n\nEvery agent on the machine uses the same vault, and none of that needs\nsetting up: the app's **Connect an agent** buttons wire Claude, Hermes Agent\nand OpenClaw, and what one agent stores the others recall. Claude, Hermes\nAgent, Cursor and the CLI can use the vault at the same time: writes are\nserialised by a file lock, every process notices writes by others and\nreloads, and each agent has its own identity and namespace.\n\nA locked vault is one file, `memory.vault` in the `.compartment` folder of\nyour home directory. To move to another machine, lock the vault, copy the\nfile there, install Compartment and unlock it in the app with your\npassphrase.\n\n### From the command line\n\nThe same move, signed so the recipient can check it, plus the escape\nhatches:\n\n```bash\ncompartment lock --sign\nscp ~/.compartment/memory.vault other-machine:\ncompartment --vault memory.vault unlock     # your passphrase (+ keyfile if 2FA)\n```\n\n`lock --sign` adds an Ed25519 manifest that the recipient can check with\n`compartment verify` and no credential. `export --plaintext` writes the vault\nas JSONL and `import` reads it back, so you are never locked in.\n[FORMAT.md](FORMAT.md) specifies the `.vault` and `.mpack` files byte by\nbyte. Per-agent namespaces take `rw`, `ro` or `none` grants in the settings\nfile, so a scratch agent can read without writing.\n\n**Memory packs** are signed, read-only bundles of curated memories\n(`compartment pack build | install | remove | list | export`). They install\nunder `packs/<name>`, read-only for every caller, and\n`include_packs_in_search` toggles them. A pack's signature is checked against\na key you trust, never against the key inside the pack. The reference facts\nare the one pack that lives in `main` as ordinary memories.\n[PACKS.md](PACKS.md) covers authoring.\n\n`compartment setup airgap-bundle` prepares an install for a machine with no\nnetwork; `setup download-model` and `setup download-longmemeval` fetch what\nthe optional benchmarks need.\n\n## Measured, on an 8 GB baseline laptop\n\nEvery number below is reproducible on your machine with `compartment\nselftest` and `compartment bench` (`--longmemeval` runs the retrieval\nbenchmark).\n\n| Metric | Measured |\n|---|---|\n| Fresh install → open vault, offline | seconds, zero network |\n| Vector search, 20k records (HNSW) | p95 0.68 ms |\n| Full hybrid search (embed + windows + keywords + evidence fusion) | median 11.6 ms, p95 14.7 ms |\n| Peak RSS, model + vault + index resident | 319 MB |\n| Store one memory (embed + encrypt + fsync journal) | ~40 ms |\n| Wheel size, model included | ~30 MB |\n| Test suite (crypto, tamper, crash, offline, concurrency, 2FA, graph, dash, ranking) | 800+ tests, offline guard active |\n\n## Install\n\n**No command line needed.** On a Mac, download **Compartment.pkg** from the\n[latest release](https://github.com/MaxFreedomPollard/Compartment/releases/latest)\nand open it. Python, the embedding model and every dependency are inside it.\nIt asks you to choose a passphrase, creates the vault, and puts Compartment\nin your menu bar, where the **Connect an agent** buttons do the rest.\n\nFrom the command line, on any system:\n\n| | |\n|---|---|\n| **pip** (macOS, Linux, Windows) | `pip install compartment && compartment init` |\n| **pipx / uv** | `pipx install compartment` or `uv tool install compartment`, then `compartment init` |\n| **Claude Code plugin** | after `pip install compartment && compartment init`: `/plugin marketplace add MaxFreedomPollard/Compartment`, then `/plugin install compartment@maxfreedompollard`. Codex reads the same marketplace file |\n| **Docker** | `docker build -t compartment .` from a checkout; see [Wiring each agent](#wiring-each-agent) |\n\nThe pip route needs Python 3.11 or newer. The app runs on macOS 13 or\nnewer, on Windows with the Microsoft Visual C++ runtime installed, and on\nany Linux desktop.\n\n`init` asks you to choose a passphrase, creates the vault, loads the\nreference facts, connects Claude Code, Hermes Agent or OpenClaw if they are\ninstalled, and starts the app: a menu bar item on macOS, a tray icon on\nWindows, a window on Linux. Restart your agent and it has a memory.\n\nTo connect an agent later, or any other client:\n\n```bash\ncompartment integrate claude      # Claude Code + Claude Desktop\ncompartment integrate hermes      # Hermes Agent\ncompartment integrate openclaw    # OpenClaw\ncompartment integrate --list      # the 28 MCP clients it can wire: Cursor, VS Code, Cline, Roo Code, Zed, OpenCode, Codex CLI, Gemini CLI, Oh My Pi, LM Studio, AnythingLLM, BoltAI ...\ncompartment integrate --all       # every one of them that is installed here\n```\n\n`claude`, `hermes` and `openclaw` also get the **`/compartmentalize`** skill\ninstalled in their skills directories. Any other MCP client uses this block\n(stdio transport, no environment variables):\n\n```json\n{ \"mcpServers\": { \"compartment\": { \"command\": \"compartment\", \"args\": [\"serve\"] } } }\n```\n\n## Wiring each agent\n\nNone of this needs a terminal: the **Connect an agent** buttons in the app\nrun the same steps for Claude, Hermes Agent and OpenClaw. The commands below\nare for people who prefer them, and for wiring a client the app does not\nlist. On Windows, run them in PowerShell with `py -m pip install compartment`\nin place of `pip install compartment`.\n\n**Claude (Code + Desktop)**\n\n```bash\npip install compartment && compartment init && compartment integrate claude\n```\n\nRegisters the MCP server with the Claude Code CLI (user scope, all\nprojects), imports the memories Claude Code has already written to its\nmemory files (copy-only and repeatable; `--no-import` skips it), installs the\ncapture hook (`--no-hooks` skips it), installs the `/compartmentalize` skill,\nwrites a managed block into `CLAUDE.md`, and prints the Claude Desktop config\nblock. The server also describes itself in the MCP handshake, telling the\nmodel to recall before answering and to store durable facts, credentials,\nnames and decisions, so Claude uses Compartment as its memory without any\nmanual instruction.\n\n**Hermes Agent**\n\n```bash\npip install compartment && compartment init && compartment integrate hermes\n```\n\nInstalls the provider plugin into the Hermes environment and runs\n`hermes memory setup compartment`; verify with `hermes memory status`.\nHermes Agent 0.20.0 and newer also read the portable\n[Agent Plugins](https://agent-plugins.org) format, and this repository is\none. That route installs the MCP server and the `/compartmentalize` skill\nfrom GitHub:\n\n```bash\npip install compartment && compartment init\nhermes plugins install MaxFreedomPollard/Compartment\nhermes plugins enable compartment\n```\n\nThe provider is the fuller integration, because it recalls and stores on\nevery turn; the portable package works only when the model calls its tools.\nOn macOS and Windows both install to the same plugin directory name, so use\none or the other.\n\n**OpenClaw**\n\n```bash\npip install compartment && compartment init && compartment integrate openclaw\n```\n\nWrites the `mcpServers` entry into `~/.openclaw/openclaw.json`, with a\nbackup. Then run `openclaw gateway restart` and check with\n`openclaw mcp list`.\n\n**Any MCP client**\n\n`compartment integrate <client>` wires any of the 28 clients in `--list`.\nEach config write takes a byte-exact backup first, merges rather than\nreplaces, writes atomically, and refuses to touch a file it cannot parse (it\nprints the block to paste instead). To do it by hand, use the block in\n[Install](#install); VS Code uses the key `servers` with `\"type\": \"stdio\"`,\nZed uses `context_servers`, Codex uses TOML under\n`[mcp_servers.compartment]`. `--vault` and `--caller` are optional; the\ndefaults are `~/.compartment/memory.vault` and caller `user`.\nClient-by-client walkthroughs are in\n[docs/INTEGRATIONS.md](docs/INTEGRATIONS.md).\n\n**Docker**\n\n`docker build -t compartment .` from a checkout builds a headless image:\nstdio only, no port, unprivileged user, vault on a bind mount at `/data`.\nCreate the vault on the host first with `compartment init`, because that\nstep prompts for the passphrase.\n\n## Configuration\n\nNothing here is required. Compartment installs configured; this is the whole\nsurface if you want to change something.\n\n### In the app\n\nThe panel behind the icon: **Unlock** and **Lock**, **Change password**,\n**Create memories automatically** (the capture hook), **Search starter\nfacts**, **Auto-lock** (15, 30, 60 minutes or never), the **CONNECT AN\nAGENT** buttons for Claude, Hermes Agent and OpenClaw, **Refresh** and\n**Quit**.\n\n### Commands\n\nGlobal flags, before the command: `--vault PATH`, `--caller NAME`,\n`--keyfile PATH`, `--assert-offline`, `--version`.\n\n| Command | What it does |\n|---|---|\n| `init` | create the vault. `--passphrase`, `--creator`, `--keychain`, `--no-session`, `--no-app` |\n| `unlock` / `lock` | open or close it. `--passphrase-stdin`, `--keyfile`, `--keychain`, `--once`; `lock --sign --identity` |\n| `status` / `verify` / `selftest` | what is in it, is it intact, does it work |\n| `store` / `get` / `forget` | one memory. `--source` (required), `--discovered`, `--expires`, `--namespace`, `--tag`, `--importance`, `--kind fact\\|opinion`, `--supersedes ID`, `--keep-both`, `--quarantined`, `--raw`; `forget --shred` |\n| `search` / `recent` | find things. `--namespace`, `--tag`, `--top-k`, `--limit`, `--all`, `--json` |\n| `expire` | remove expired memories |\n| `atomize` | list over-limit blob memories as JSONL (`--out` + `--plaintext`), apply an agent-written split plan (`--apply`) |\n| `opinions audit` | backfill `kind` on opinion-shaped records, cluster overlapping live opinions, resolve with `--keep-newest`. `--threshold`, `--no-backfill`, `--json` |\n| `link` / `relations` / `unlink` | the relation graph, with validity windows (`--from`, `--to`, `--as-of`) |\n| `panel` (`menubar`, `tray`) | the app. `--show`, `--self-check`, `--render`, `--login` |\n| `integrate <agent>` | wire claude, hermes, openclaw or any listed client, and install `/compartmentalize`. `--list`, `--all`, `--no-import`, `--no-hooks` |\n| `hook` | the Claude Code capture hook: `install --pin-vault`, `uninstall`, `status`, `capture` |\n| `import-claude` | pull in what Claude Code already wrote. `--dir`, `--namespace`, `--dry-run` |\n| `serve` | the MCP server, over stdio |\n| `dash` | read the vault in a browser: 127.0.0.1, one-time token, GET only |\n| `export` / `import` | `export --plaintext` writes it unencrypted; `import` reads it back |\n| `rekey` | change the passphrase. `--new-passphrase-stdin` |\n| `2fa` | `enable`, `disable`, `status`: a keyfile as a second factor |\n| `audit` | `verify`, `repair` the hash-chained history |\n| `retag` | recompute tags from the current vault (`--dry-run`, `--prune`); never changes memory text |\n| `reindex` | rebuild the index, and give long records the embedding windows they are missing. `--int8`, `--f32`, `--re-embed`, `--model` |\n| `pack` | `build`, `install`, `remove`, `list`, `export` signed memory packs (`--trusted-key`) |\n| `bench` | `--records`, `--longmemeval`, `--variant`, `--limit` |\n| `setup` | `download-model`, `download-longmemeval`, `airgap-bundle` |\n| `update` | upgrade in place. `--source` takes GitHub main, `--no-app` skips the restart |\n| `uninstall` | remove it. The vault is kept unless you pass `--purge` |\n\n`compartment panel --login on | off | status` controls starting at login (on\nLinux, the applications menu entry). `init --no-app` skips the app on\nheadless machines and in CI.\n\n`compartment dash` is the Dashboard button from the terminal: the whole vault\nin your browser, growth over time, the relation graph with every entity\nnamed, tags, per-agent counts, live search. It serves from RAM on 127.0.0.1\nonly, behind a random URL token, read-only, with no outbound requests and no\nconfiguration. Ctrl-C closes it.\n\n### The /compartmentalize skill\n\n`compartment integrate <agent>` writes one file into that agent's own skills\ndirectory, and `compartment uninstall` takes it back:\n\n| Agent | Path |\n|---|---|\n| Claude Code | `~/.claude/skills/compartmentalize/SKILL.md` |\n| Hermes Agent | `$HERMES_HOME` or `~/.hermes/skills/compartmentalize/SKILL.md` |\n| OpenClaw | `$OPENCLAW_HOME` or `~/.openclaw/skills/compartmentalize/SKILL.md` |\n\nAll three use the same Agent Skills layout, so it is one file. Only the user\nruns it. Type it before compacting, or at any time, and the agent stores the\nwhole conversation in the vault: people and contacts, credentials and where\nthey live, URLs and hosts, decisions and the reasons for them, and a record\nof the session itself. It makes many `memory_store` calls. You can edit your\ncopy; a later install backs up a changed copy rather than overwriting it.\n\n### Settings file\n\n`<vault>.config.json`, beside the vault, holding grants per caller and:\n\n| Setting | Default | Meaning |\n|---|---|---|\n| `auto_lock_minutes` | `30` | idle time before it locks. `0` never locks |\n| `search_starter_facts` | `true` | whether the seeded facts join search results |\n| `include_packs_in_search` | `true` | the same, for installed packs |\n| `expire_memories` | `true` | remove expired memories automatically |\n| `duplicate_threshold` | `0.97` | cosine similarity at which a store is a duplicate |\n| `max_memory_chars` | `200` | the one-claim length limit for authored memories. `0` disables the length and layout checks |\n| `opinion_update_threshold` | `0.80` | similarity at which a new opinion is an update of a live one and needs a supersedes decision |\n| `opinion_reaffirm_threshold` | `0.97` | similarity at which a restated opinion re-affirms the live record instead of storing |\n| `retag_interval_hours` | `6` | how often the background pass recomputes tags. `0` turns it off |\n| `retag_prune` | `false` | whether that pass may also remove tags |\n| `index_precision` | `\"f32\"` | `\"int8\"` uses a quarter of the RAM |\n| `unlock_tool_enabled` | `false` | lets an agent unlock the vault. Off because the passphrase would cross the model's context |\n\n### Environment\n\n`COMPARTMENT_VAULT` which vault to use, `COMPARTMENT_PASSPHRASE` for scripts\nand CI, `COMPARTMENT_SESSION_DIR` where the unlock credential lives,\n`COMPARTMENT_UI_SCALE` panel scale, `COMPARTMENT_ASSERT_OFFLINE` abort on any\nnetwork attempt. `HERMES_HOME`, `OPENCLAW_HOME` and `XDG_DATA_HOME` are read\nwhere they apply. Anything exported as `ENGRAM_*` still works.\n\n### MCP tools\n\nEvery tool has a title and a read-only or destructive annotation, so a\nclient can tell the seven read-only tools from the ones that write before\ncalling anything. `memory_search`, `memory_store`, `memory_store_many`,\n`memory_get`, `memory_recent`, `memory_forget`, `memory_link`,\n`memory_relations`, `memory_unlink`, `memory_list_namespaces`,\n`memory_status`, `memory_lock`, `memory_selftest`. `memory_unlock` exists but\nis off unless you turn it on above.\n\n## Documentation\n\n| | |\n|---|---|\n| [docs/MEMORY.md](docs/MEMORY.md) | how memory is stored, what gets remembered, and the ranking design |\n| [docs/INTEGRATIONS.md](docs/INTEGRATIONS.md) | selecting Compartment in Hermes Agent, OpenClaw, Claude, everything else |\n| [docs/COMPARISON.md](docs/COMPARISON.md) | other memory servers, with sources |\n| [SECURITY.md](SECURITY.md) | the full threat model and its limits |\n| [FORMAT.md](FORMAT.md) | byte-level `.vault` and `.mpack` specs (language-agnostic) |\n| [PACKS.md](PACKS.md) | authoring and shipping signed memory packs |\n| [CONTRIBUTING.md](CONTRIBUTING.md) | setup, good issues, and the guarantees to keep |\n| [RELEASING.md](RELEASING.md) | how a release is cut |\n\n## Privacy Policy\n\nCompartment collects no data: no telemetry, no analytics, no account, and no\nnetwork at runtime. Memories are stored only on your machine, AEAD-encrypted\nat rest with a passphrase that never leaves it, and nothing is shared with\nanyone. The full policy, covering collection, storage, network access,\nthird-party sharing, retention, and contact, is at\n<https://maxfreedompollard.github.io/Compartment/privacy>.\n\n## Where to find it\n\nCompartment is listed on [PyPI](https://pypi.org/project/compartment/), the\n[official MCP registry](https://maxfreedompollard.github.io/Compartment/registry),\nthe [Cursor Directory](https://cursor.directory/plugins/compartment),\n[Glama](https://glama.ai/mcp/servers/MaxFreedomPollard/Compartment),\n[LobeHub](https://lobehub.com/mcp/maxfreedompollard-compartment),\n[MCP Toplist](https://mcptoplist.com/server/io.github.MaxFreedomPollard%2Fcompartment),\n[MCP Market](https://mcpmarket.com/server/compartment),\n[mcpservers.org](https://mcpservers.org/servers/maxfreedompollard/compartment),\n[TensorBlock](https://www.tensorblock.co/mcp/servers/github-maxfreedompollard-compartment-4ab11161),\nthe [toolsdk.ai registry](https://github.com/toolsdk-ai/toolsdk-mcp-registry/blob/main/packages/knowledge-memory/compartment.json),\n[Libraries.io](https://libraries.io/pypi/compartment),\n[Snyk Advisor](https://snyk.io/advisor/python/compartment) and\n[deps.dev](https://deps.dev/pypi/compartment), and in the curated lists\n[abordage/awesome-mcp](https://github.com/abordage/awesome-mcp),\n[TensorBlock/awesome-mcp-servers](https://github.com/TensorBlock/awesome-mcp-servers/blob/main/docs/knowledge-management--memory.md)\nand [Jenqyang/Awesome-AI-Agents](https://github.com/Jenqyang/Awesome-AI-Agents).\n\n[![MCP Toplist](https://mcptoplist.com/badge/io.github.MaxFreedomPollard%2Fcompartment.svg)](https://mcptoplist.com/server/io.github.MaxFreedomPollard%2Fcompartment)\n[![LobeHub](https://lobehub.com/badge/mcp/maxfreedompollard-compartment)](https://lobehub.com/mcp/maxfreedompollard-compartment)\n\n<a href=\"https://glama.ai/mcp/servers/MaxFreedomPollard/Compartment\"><img width=\"380\" height=\"200\" src=\"https://glama.ai/mcp/servers/MaxFreedomPollard/Compartment/badge\" alt=\"Compartment MCP server\" /></a>\n\nBugs and feature requests: [Issues](https://github.com/MaxFreedomPollard/Compartment/issues).\nSupport and questions: [Discussions](https://github.com/MaxFreedomPollard/Compartment/discussions);\nsecurity reports: [SECURITY.md](SECURITY.md).\nQuestions and ideas: [Discussions](https://github.com/MaxFreedomPollard/Compartment/discussions).\n\n---\n\nmcp-name: io.github.MaxFreedomPollard/compartment\n",
  "bytes": 40787,
  "sha": "462fad354b696b9ed21a028d62a9ddfe9e3073a7eccd6ff4a5b6c8ba5576f2e6",
  "repo_slug": "maxfreedompollard/compartment",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_maxfreedompollard_compartment_16b7c3b9/readme"
}