[PLUR — local-first shared memory for AI agents. Haiku + PLUR beats Opus without it, at ~10× less cost.]
PLUR — Your agents share the same memory
[MCP Toplist]
[npm version]
[CI]
[License: Apache-2.0]
[GitHub stars]
[Glama score]
Persistent, open memory for AI agents — local-first, zero-cost, shared across MCP tools (Claude Code, Codex, Cursor, OpenCode, Hermes, OpenClaw). Your agent's memory is plain-text engrams you can read, correct, and delete — not weights you can't.
plur.ai · Benchmark · Engram Spec · npm · Comparisons
Benchmarks
PLUR is memory, not just retrieval — so we measure it on more than one axis, on the full corpus, and we publish the harness so you can reproduce every number.
Retrieval recall — full LongMemEval-S (N=500), R@5, fully local:
Numbers come from plur-ai/plur-bench,
which is the source of truth for every benchmark figure PLUR publishes. Where
an in-repo number and a plur-bench number disagree, plur-bench wins — it is the
reproducible harness, and it is what CI regression-checks.
Chunk granularity, canonical-doc scoring, corpus SHA256 pinned — reproduce it in plur-ai/plur-bench. No cloud call is required for any of these numbers (an optional cloud embedder, openai-3-large, reaches 97.0% hybrid). A faster reranker — ms-marco-minilm-l6 (p50≈245ms vs BGE's ≈5s on CPU) — trades a little recall for sub-second latency.
Run it yourself — and tell us what you get. The harness is plur-ai/plur-bench: CPU-runnable, no API key needed for the local path, corpus auto-fetched and SHA-verified. If you run it, we'd genuinely love to see your numbers — open an issue or discussion with your results, especially if they don't match ours. Independent reproduction is worth more than any number we publish, and we'll gladly credit you.
Retrieval ≠ answer accuracy — and we report them separately, never conflated. End-to-end (LLM-judge) answer accuracy with the reranker stack is 60.5%, versus 52.0% for dumping full context into the prompt and 5.5% with no memory at all.
Agent-task impact — same task, with memory vs without: Haiku + PLUR outperforms Opus without it at roughly 10× less cost; house rules 12–0 across Haiku, Sonnet, and Opus.
Operational — local-first, zero-cost search, data-sovereign by design.
More in progress: LoCoMo, agentic task suites, cross-tool portability, decay / contradiction correctness. Full methodology →
The idea
You correct your agent's coding style on Monday. On Tuesday, it makes the same mistake. You explain your architecture in Cursor. That night, Claude Code has no idea.
PLUR fixes this. Install it once, and corrections, preferences, and conventions persist — across sessions, tools, and machines. Your memory is stored as plain YAML on your disk. No cloud, no API calls, no black box.
The interesting part: in our tool-routing and local-knowledge benchmark, Haiku with PLUR memory outperformed Opus without it — 2.6x better on tool routing, at roughly 10x less cost. Turns out the bottleneck isn't model intelligence. It's context.
The model is rented; your memory is owned. Swap Haiku for Opus for whatever ships next month — the reasoning is a commodity you don't control. The part that's yours — everything the agent has learned about your work, your corrections, your conventions — shouldn't live in someone else's cloud or be baked into weights you can't read. PLUR keeps it in plain files on your disk, in an open format you can inspect, correct, and delete. That's what owning your intelligence actually means.
Install
Requires Node.js 20 or newer. Install both packages so editor configurations can use the installed MCP server:
Restart your editor after setup. plur doctor checks the configuration and MCP handshake, and downloads the search model if it is missing and downloads are allowed. Search runs locally after that download; without the model it falls back to keyword search.
Tell your agent
Claude Code
plur init creates the local store at ~/.plur/, registers the user-scoped MCP server in ~/.claude.json, and writes global hooks to ~/.claude/settings.json. A project .mcp.json is a separate project-scoped configuration; ~/.claude/mcp.json is not the user-scoped registration path. The hooks inject memory and close session state when Claude Code emits SessionEnd. Run init again after upgrading PLUR to refresh its configuration.
For multi-project setups, use domain/scope to separate knowledge:
This creates a .plur.yaml in the project with defaults that hooks apply automatically. Engrams learned in that project are tagged; recall filters by scope but always includes global knowledge.
Set scope per engram, by content. Scope is not a once-per-session setting — every plur_learn call takes its own scope, chosen from what the engram is about. Team/shared knowledge goes to a team scope (e.g. group:<org>/<team>, used by PLUR Enterprise); project details to project:<name>; personal preferences stay local. Don't let team-relevant knowledge fall back to global by omitting scope — global leaks into every project and (with a team store configured) never reaches the team. plur_session_start lists the remote scopes a token can write to.
Upgrade
Restart the editor so it launches the updated MCP server.
Cursor
From your project root:
This writes .cursor/mcp.json, .cursor/hooks.json and .cursor/rules/plur-memory.mdc. Cursor uses the reduced cursor tool profile: core tools are direct, and other operations are available through plur_admin. Call plur_admin with { "action": "help" } to discover their current names and arguments.
Codex
Registers a new MCP server via codex mcp add in ~/.codex/config.toml, writes lifecycle hooks to ~/.codex/hooks.json (CODEX_HOME overrides the directory), and adds a PLUR section to AGENTS.md. Auto-detected when ~/.codex/ exists.
To upgrade an existing Codex installation:
Init updates recognized older PLUR launch pins in place, preserving environment settings, timeouts, tool policies and comments. It preserves newer pins and custom or HTTP servers. Use --keep-codex-mcp to retain an intentional older launch entry. Restart Codex after changing its configuration.
For remote stores using plur remote --token-env NAME, both remote and init --codex add the required variable names to Codex's env_vars; they never copy token values into its config. The variable must also be available to the process that launches Codex. A variable exported in PowerShell does not prove that a separately launched desktop app inherited it.
plur doctor --codex starts the MCP command from the Codex user config with Codex-style environment filtering, checks its reported version against the CLI, and reports missing token forwarding or values. It does not check project/profile overrides, another running application's environment, hook trust, or a remote write. --no-handshake reports the runtime as unverified and exits nonzero. Verify saving and recalling a test memory in the actual Codex session before treating desktop setup as complete. Native Windows and WSL2 have separate home directories and registrations; run setup in each environment you use.
Injection uses hybrid search (BM25 + embeddings) with an automatic BM25 fallback if the embedder is slow or unavailable. Set PLUR_HOOK_HYBRID=0 to force BM25 (applies to the Antigravity hooks too; PLUR_CODEX_HYBRID is honoured as an alias). PLUR_HOOK_HYBRID_DEADLINE_MS tunes the fallback deadline — keep it below your harness's hook timeout (Codex 25s, Antigravity 20s).
One manual step after install: open Codex, run /hooks, and trust the PLUR entries. Codex fingerprints every hook and refuses to run untrusted ones — silently, with no warning and a zero exit code. Until you trust them, memory simply never loads. plur doctor says so too.
OpenCode
This adds the memory plugin and MCP tools to ~/.config/opencode/opencode.json, or to the existing opencode.jsonc when no .json exists. OPENCODE_CONFIG_DIR takes precedence; otherwise XDG_CONFIG_HOME/opencode overrides the default directory.
The MCP command uses Node and the installed @plur-ai/mcp entry when available. Init updates an older exact @plur-ai/opencode pin to the plugin version shipped with the CLI while preserving comments, formatting and unrelated settings. Newer pins, tags and unpinned entries remain unchanged. Use --keep-opencode-plugin to retain an intentional old pin. Restart OpenCode to load the changed configuration. Plugin details.
Which integration you get
Every MCP client can call PLUR's tools. Only some have an adapter — the hooks
and always-on context that make memory load automatically instead of waiting for
the agent to think of it. Without one, recall and learning depend entirely on the
model choosing to call the tools, which degrades badly under context pressure.
If your harness is in the last row, paste the PLUR section from CLAUDE.md into
its own context file (AGENTS.md, GEMINI.md, …) as an interim measure — that
restores the instruction layer, though not automatic injection.
Antigravity CLI (agy)
Writes hooks and the MCP server into agy's global config (~/.gemini/config/) and adds a PLUR section to AGENTS.md. Auto-detected when ~/.gemini/antigravity-cli/ exists. No trust step — agy runs configured hooks on first invocation; just restart agy.
Antigravity has no session-start event and no per-prompt hook, so PLUR drives everything from PreInvocation: per-prompt recall is read from the conversation transcript, and the turn's memory is re-injected as an ephemeral message on every model invocation so it survives tool calls without accumulating in history.
Gemini CLI users: Google is transitioning Gemini CLI to Antigravity — install agy and run the command above. Gemini CLI itself remains tools-only.
OpenClaw
That's it. PLUR works in the background from here. No workflow changes needed — just use your tools as usual. Corrections accumulate automatically.
DeepSeek Harness
Native, not an MCP bridge. PLUR mounts as a Cordis plugin and writes your
engrams straight into the system prompt, so the model reads them the way it
reads its own instructions — no tool call, no round trip, and no turn spent
deciding whether to look. The section is re-rendered on each assembly rather
than appended, so memory does not accumulate in the context as a session runs.
Five tools (plur_recall, plur_learn, plur_forget, plur_feedback,
plur_status) are still registered for when the agent wants to reach for
memory deliberately. Scope defaults closed — each workspace gets its own,
resolved from its .plur.yaml.
/plur reports status; /plur-memory opens the memory viewer below.
Hermes Agent
The plugin registers automatically via Hermes' plugin system. It injects relevant memories before each LLM call, extracts learnings from agent responses, and exposes all PLUR tools to the agent. Hermes shells out to the PLUR CLI.
Python SDK (LangChain, llama.cpp, scripts)
For Python environments that aren't Hermes:
Note: plur-ai is not yet on PyPI — use the git install above until #915 is resolved.
plur-ai bridges to the same on-disk store as Claude Code and OpenClaw — memory written from Python is immediately visible across all your tools. See packages/python/examples/ for LangChain and llama.cpp integration examples.
Verify it works
Run plur doctor, then ask your agent to call plur_status and show its engram count and storage path. The default lean MCP profile exposes core tools directly; use plur_admin with { "action": "help" } for the remaining operations and their schemas.
Read your memory
Opens a local page listing every engram: what was learned, what actually gets
recalled, and how often. Read-only, loopback-only, and served from your own
machine — nothing is uploaded. --port moves it, --no-open skips the
browser. Inside DeepSeek Harness the same page is one /plur-memory away.
Available in English and 中文; it follows your browser, or ?lang=zh.
See it in action
Once it's running, teach your agent something once:
"Always use pnpm in this project — npm install breaks the lockfile in CI."
Start a new session the next day and ask:
New session. No reminder. The correction was there.
That's the moment PLUR pays off — the agent remembers a project convention you mentioned once, without it being in any file it can read.
How it works
PLUR has two storage primitives:
Engrams — learned knowledge that persists across sessions. Each engram is a typed assertion ("always use blue-green deploys", "never force-push to main") with:
- Activation — retrieval strength that decays over time (ACT-R model) and strengthens on access. Stale facts naturally fade from injection without manual cleanup.
- Feedback signals — positive/negative ratings that train injection quality over time
- Scope — hierarchical namespace (
global, project:myapp, cluster:prod, service:api) controlling where the engram applies
- Polarity — automatic classification of "do" vs "don't" rules, so constraints are injected separately from directives
- Associations — links to other engrams, including co-access edges that form automatically when engrams are recalled together
Episodes — timestamped event records for "what happened when." Each episode captures a summary, timestamp, agent attribution, and channel. Use episodes for incident timelines, session logs, and operational history. Query by time range, agent, or channel.
Search is fully local: BM25 (with IDF weighting, TF saturation, length normalization) + BGE embeddings + Reciprocal Rank Fusion. Zero API calls, zero per-query cost. Benchmark methodology →
Plugins (OpenClaw, Hermes) automatically capture learnings from agent conversations — no manual saving needed. The agent's corrections become engrams without you doing anything.
See the full engram spec for schema details, activation model, and injection algorithm.
Open format
The engram is an open, versioned format — not a black box. Every engram is plain YAML validated against a published JSON Schema, generated from the same Zod source the engine uses (the schemas live in spec/). Read it, diff it in git, write your own tooling against it, or build a different engine on the same format — your memory isn't locked to PLUR.
Usage
Tools
The outbox
A write to a team scope goes to that team's remote store. When the store cannot
be reached — VPN off, server down, token expired — the engram is not lost and
not silently dropped: it is written locally with queue metadata and retried on
the next session start, on plur sync, or on demand.
The queue is not a directory. It lives as structured_data._outbox inside the
affected engrams in engrams.yaml, which is why it needs a command to see:
The same thing is available to agents as plur_outbox ({flush: true} to
retry), and plur status reports the pending count. Neither surface prints the
target URL or the token.
The folder map
~/.plur/folders.yaml records what PLUR does in each folder, and every
integration follows it:
The file starts with a commented example of every setting, and the CLI keeps
your comments and order when it writes. Details, including exactly what a
remote-only folder refuses and what happens when the team server is down:
docs/folder-map.md.
The memory receipt
plur receipt (and the plur_receipt MCP tool) show what your memory actually did — counted from PLUR's own retrieval history, never estimated:
(REUSE stats and coverage caveats are also shown; trimmed here for length.)
It is local and read-only, and carries no dollar or token figure by design: on a subscription your marginal token cost is zero, and the value of an avoided rediscovery is not measurable from this data. The receipt reports only what it can count. Activation rate is store coverage over the logging window, not a quality score — it is naturally low and falls as you add engrams. --days N narrows the window; --json emits the raw shape. (The plur_receipt MCP tool returns the same figures plus a one-line summary that carries this framing to the agent.)
Syncing across devices
plur.sync(remote) is git underneath: it commits your engram store and pushes it to the remote you give it. What gets pushed depends on the remote's declared type (sync.remote_type in config.yaml, or the remote_type argument):
personal (default) — your own backup/mirror across your machines. The remote receives everything that is pushed, including visibility: private engrams: private visibility means "don't share this in a pack", not "don't mirror it to my own devices", so private engrams intentionally follow you from machine to machine. Because of that, always use a private git remote (a private GitHub/GitLab repo, or your own server). PLUR surfaces a warning in the sync result whenever private engrams are present. Never point a personal sync at a public repository.
shared — a team-visible remote. Only engrams with a shared-family scope (group:/project:/space:/team:/org:/public) and a non-private visibility are pushed; personal-family engrams (local, global, user:*, agent:*) and private-visibility engrams never reach the remote, by construction. Note the default visibility is private, so a shared remote receives only engrams whose visibility was set deliberately — teammates get what you chose to share, nothing else. The same guarantee covers the sibling store files (#686): an episode, candidate, or tension record is pushed only when every engram it references is itself in the push set — records derived from personal or private engrams (a tension's statement snapshots, a failure-report episode) stay local, as does any record referencing an engram the filter cannot resolve.
In both modes scope: local engrams are machine-specific by design (paths, local ports, per-host quirks), so they are stripped from every commit and never reach any remote. Stripping happens on the staged blob: your local working copy always keeps every engram.
Benchmark details
Per-category retrieval recall, from an earlier in-repo run — full
LongMemEval-S (N=500), fully local (BGE-small + BGE-reranker-v2-m3, chunk
granularity). Its overall figure (98.0%) predates the current plur-bench
measurement of the same stack (97.6%) and has not been re-run per category;
treat the shape as indicative and the headline table above as current.
Retrieval recall (finding the right memory) and end-to-end answer accuracy (whether the model then answers correctly) are different axes — PLUR measures and reports them separately, never conflated. The agent-impact figures above come from a same-task A/B run (memory vs none).
Full methodology →
PLUR vs other agent-memory tools
Mem0, Letta (MemGPT), and Zep solve real problems — a drop-in memory API (Mem0), a self-managing agent OS (Letta), a temporal knowledge graph (Zep). PLUR's bet is a combination none of them ship together:
- Plain-text you own — engrams are human-readable YAML you can read,
git diff, edit, and provably delete. Not opaque vectors, agent-state blocks, or graph nodes you need tooling to inspect.
- Local-first, zero-cost — hybrid BM25 + local embeddings, fully offline, no API bill (98% R@5 on the full LongMemEval-S corpus with no cloud call — see above).
- Team-shareable via git —
plur sync is git underneath, so the same memory follows you across machines and across a team. Most tools are single-user-local or cloud-team; PLUR is both, and you keep the data.
- Cross-tool — the same
~/.plur/ store works in Claude Code, Cursor, Windsurf, OpenClaw, and Hermes. Your memory isn't trapped in one vendor.
- It learns and forgets — feedback-trained retrieval with ACT-R decay and an on-demand contradiction scan, not a grow-forever store.
If you need a hosted memory API or a temporal knowledge graph, use the tool built for that. If you want memory you can read, own, share with your team, and move between tools, that's PLUR. Side-by-side detail: comparisons/.
What PLUR is — and isn't
PLUR is agent memory — it stores corrections, preferences, conventions, and architectural decisions that an AI agent learns during work sessions, and injects them back when they're relevant.
PLUR is not a general-purpose search engine, a codebase indexer, or a replacement for code intelligence tools. It doesn't parse ASTs, navigate class hierarchies, or search your source files. If you need code-aware search (tree-sitter, language server features, symbol lookup), tools like claude-mem or your IDE's built-in search are the right choice.
The two are complementary:
While search is a core part of PLUR (finding the right engram to inject), the search targets are always engrams — not files, not code, not documents. PLUR's hybrid search (BM25 + embeddings + RRF) is optimized for short natural-language assertions, not source code.
Packages
packages/ui is internal — the memory viewer's pages, bundled into the CLI and
the DeepSeek Harness plugin rather than published. It is not on npm.
Architecture
Storage
Everything is plain YAML. Open it, read it, edit it.
PLUR_PATH overrides the default location.
Who wrote this engram
Every engram records the actor that wrote it, in provenance.origin. Set the
identity for this machine in config.yaml:
With no identity configured, engrams are stamped agent:unidentified. That is
recorded honestly rather than guessed, so a store written by several actors
still shows that it was — but the attribution is only useful once each writer
names itself. plur status reports the value in effect.
For a store several agents write to (a shared team store, or one machine
running an MCP server alongside a CLI), give each writer its own identity.
Indexing is on by default (index: true) and the backend is chosen from the
size of your store, so there is normally nothing to configure:
YAML stays the source of truth in every tier except postgres (ADR-0001,
ADR-0005) — the index is a cache that rebuilds automatically, and you can delete
it anytime. Set backend: in config.yaml to pin a tier explicitly.
One caveat on the postgres tier, stated here because it is this table's
headline row: core does not write embeddings to a Postgres primary store
(ADR-0005 amendment). Keyword/BM25 recall runs in SQL, but engram_embeddings
stays empty unless your deployment populates it, so semantic and hybrid recall
fall back to loading engrams and scoring in memory — correct results, at the
O(N) cost this tier otherwise avoids. The adapter says so once at schema init;
vectorIndex: 'exact' acknowledges and silences it.
sqlite (engrams.db, via better-sqlite3) is the legacy index and is no
longer selected automatically.
Requirements
- Node.js 22+ (22.21+ or 24+ behind an HTTPS proxy, with
NODE_USE_ENV_PROXY=1)
- 2GB RAM minimum — the embedding model (ONNX runtime) needs ~1GB for installation. On servers with less RAM, embeddings are skipped and search falls back to BM25 keyword matching.
Development
~3500 tests across ~200 files. pnpm test:watch for development.
Contributing
- Bug reports — issue with reproduction steps
- Feature requests — issue describing the use case
- Code — fork, branch, PR. Tests required.
- Integrations — build PLUR support for other tools
Before submitting: pnpm test passes, pnpm build succeeds, no new external deps in core without discussion.
Conventions: TypeScript, Zod validation, Vitest, no external APIs in core, YAML storage, zero-cost search by default.
License
Apache-2.0