Files
mem0/integrations/mem0-agent
Deshraj Yadav 10b8fc4bed fix(mem0-agent): read the key from the plugin config, add cross-surface verification
The desktop app never sources a shell rc, so a key that exists only as MEM0_API_KEY
in .zshrc is invisible there and the plugin silently no-ops. Claude Code injects the
userConfig value as CLAUDE_PLUGIN_OPTION_API_KEY (v1 read it, v2 did not).

- resolve_api_key() -> (key, source): env, plugin config, legacy plugin config, keychain
- health now reports WHERE the key came from, which is how you tell the two apart
- every logged event records the editor, and a new 'sessions' command lists which
  surfaces have actually built packs and written memories

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 20:17:17 -07:00
..

mem0-agent

Coding memory for Claude Code and friends, built on the Mem0 platform.

It remembers the things that change how an assistant should behave next time — your preferences, the decisions you made and why, the team conventions that live nowhere in the repo, the gotchas you root-caused, the procedures you verified. It deliberately does not remember what you did today.

Zero runtime dependencies (stdlib only; keyring is optional). Every hook fails open — if the API is down, your session is unaffected.

Install

pip install 'mem0-agent[keyring]'      # keyring extra stores the API key in the OS keychain
mem0-agent onboard                     # asks for a key, picks a memory mode, pushes project config

Onboarding never reads your shell rc files and never writes a key into a .env. The key comes from MEM0_API_KEY or the OS keychain; a key you type at the prompt goes to the keychain and nowhere else. Get one at https://app.mem0.ai/dashboard/api-keys.

To wire the hooks into Claude Code, point your plugin/settings config at the generated manifest:

hooks/hooks.json

Non-interactive install (CI, dotfiles, provisioning):

from mem0_agent.onboard import run_onboard
run_onboard(interactive=False, memory_mode="dual", capture="conservative")

The two memory modes

Chosen once per project at onboard, stored per project in ~/.mem0/v2/settings.json. The default is DUAL when the repo already has memory files (CLAUDE.md, AGENTS.md, .cursorrules, MEMORY.md, .claude/memory/), FULL when it has none.

DUAL FULL
Repo memory files authoritative for repo-local notes not used
mem0 holds durable, cross-machine, cross-repo knowledge everything
MEMORY.md write-blocker off on
Native auto-memory leave it on turn it off

DUAL is the honest default for a repo that already documents itself: two memory layers that each know their job. FULL is for people who want one place to look, and it installs a write gate so the assistant stops appending to MEMORY.md behind your back.

Change it later:

mem0-agent config set memory_mode dual|full

The two aggressiveness dials

mem0-agent config set capture   conservative|balanced|aggressive
mem0-agent config set retrieval conservative|balanced|aggressive
  • capture — how eagerly a moment becomes a candidate memory. conservative stores only explicit "remember this" style signals; aggressive also stores inferred decisions and conventions.
  • retrieval — how much context is injected at session start (roughly 600 / 1500 / 2500 characters) and how confident an error match must be before it is surfaced. conservative disables error assist entirely.

How the write gate works

Nothing is stored just because it happened. A candidate has to survive three gates:

  1. Trigger — a local, network-free detector on UserPromptSubmit decides whether the moment is even a candidate. This runs on the hot path of every turn, so it is local-only by contract and enforced with MEM0_LOCAL_ONLY=1 in the generated hook.
  2. Turn boundary — candidates are buffered and judged at Stop, PreCompact and SessionEnd, when it is finally clear how the turn ended. One turn costs at most one write, and all three writes are backgrounded so they are never on your critical path.
  3. Platform policy — the project's custom instructions are the real gate. They name the six types (preference, decision, convention, insight, runbook, session_state) and explicitly exclude progress updates, status heartbeats, file and commit lists, repo file contents, session-only facts, one-off instructions and secrets. mem0-agent onboard pushes them and verifies the round-trip; the policy version is stamped into every memory's metadata so a quality regression can be traced back to the revision that caused it.

Reads are pinned to the project and always latest_only, so a superseded memory never comes back beside the one that replaced it.

Hooks are generated, not hand-written

hooks/hooks.spec.yaml is the single source of truth. It declares each hook's event, matcher, command, timeout, background/blocking flags, local-only contract, and one line of why.

python3 hooks/generate.py           # write hooks/hooks.json
python3 hooks/generate.py --check   # exit 1 if the committed manifest drifted (run in CI)
Event Matcher Command Behavior
SessionStart startup|resume|compact context blocking, injects project knowledge + open threads
UserPromptSubmit — observe --source prompt blocking, local-only, no network
PostToolUse Bash assist-error detached; a hit lands in the session buffer
Stop — flush detached
PreCompact — flush --reason precompact detached
SessionEnd — flush --reason end detached

Cursor, Codex and Antigravity are declared in the spec as unsupported rather than deleted, so the gap stays visible instead of turning back into a hand-maintained file.

Why v2

v1 wrote roughly 98 memories a day, and almost all of it was heartbeat spam: "started task X", "80% complete", "modified 3 files", "opened PR #123" — activity you can already get from git, restated in a memory store where it drowned out the things you actually wanted back. Retrieval got worse the longer you used it.

v2 targets under 15 memories a day, all durable knowledge. The changes that get it there:

  • The write gate above, validated against the real polluted v1 corpus.
  • Writes at turn boundaries in batches, instead of one write per tool call.
  • No network call on the per-prompt hot path.
  • Session state per session ID under ~/.mem0/v2/, not /tmp keyed by $USER, so concurrent sessions stop corrupting each other's counters.
  • Project and org pinned in the request body, so coding memories can no longer leak into whatever project the API key happens to default to.
  • One hook spec, generated manifests, drift caught by CI.

License

Apache-2.0