feat(claude-code-plugin): move the Claude Code plugin to its own integration and ship it as 0.3.0 - #7106
Open
kartik-mem0 wants to merge 23 commits into
Open
feat(claude-code-plugin): move the Claude Code plugin to its own integration and ship it as 0.3.0#7106kartik-mem0 wants to merge 23 commits into
kartik-mem0 wants to merge 23 commits into
Conversation
…ons/claude-code-plugin 0.3.0 Local evidence capture via fail-open lifecycle hooks, checkpointed background extraction through a detached worker with a durable retry queue, a local read-only search_memories MCP tool, six /mem0:* skills, and the mem0:sidekick agent. Pure-stdlib Python, zero runtime dependencies. The identity scheme is byte-compatible with the 0.2.x plugin so existing memories carry over. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ugin BREAKING CHANGE: the 0.2.x command surface is replaced by six /mem0:* skills and one read-only MCP tool; memories and configuration env vars carry over. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… old plugin Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # .claude-plugin/marketplace.json # integrations/AGENTS.md
Contributor
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…ocument the 0.2.x losses Findings from a review pass over the 0.3.0 plugin, with regression tests. Code: - `hook.py` detached the flush worker with `start_new_session=True`, which CPython ignores on Windows. Background extraction could therefore die with the Claude Code session on a platform that ships a native installer. Added `detached_process_kwargs()`, which returns `DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP` there and keeps `start_new_session` on POSIX. - `recover_pending_handoffs()` relaunched every pending packet at once with no cap and no expiry. After a long outage a session start could `Popen` one worker per accumulated packet, each able to block for `MEM0_CODE_EXTRACTION_WAIT_SECONDS`. Now capped at 5 oldest-first per session, with a 7-day TTL and stale `.running` reclaim. - `memory_core.doctor()` reached `sys` through `os.sys`. Imported it properly. Docs: - Added `docs/CONTRACT.md`, which `integrations/AGENTS.md` already told future plugin authors to read but which did not exist. It pins the core/adapter split, the `hook_input` keys core reads, the scope rules (including why `mcp_server.py` calls `resolve_repo` directly), and the contract tests. - `test_contract_doc_names_only_tests_that_exist` guards that document against the same drift. - README "Upgrading from 0.2.x" claimed no configuration is lost. It is: the whole `~/.mem0/settings.json` key set, the `mem0.md` policy sections, and 28 `MEM0_*` variables. New "What stops working" subsection names them. - `docs/llms.txt` still routed Claude Code to `integrations/mem0-plugin/` and its nine MCP tools. - Root `marketplace.json` was missing its trailing newline.
Keep 0.3.0 scoped to the plugin itself. The contract document is a design note for future per-harness plugins, not something this release needs, so it stays a local working file until there is a second harness to write it against. Removes the document and `test_contract_doc_names_only_tests_that_exist`, which guarded it.
Claude Code now installs 0.3.0 from integrations/claude-code-plugin, so the 0.2.x Claude manifest, hooks, and MCP config in this directory are dead weight that can only conflict with it. Removes the three Claude-Code-only files: .claude-plugin/plugin.json Claude Code plugin manifest hooks/hooks.json Claude Code lifecycle hooks .mcp.json Claude Code MCP server config No scripts or skills are removed. Codex, Cursor, Kimi, Antigravity, and OpenCode reach the same scripts through their own manifests, and Antigravity still sets CLAUDE_PLUGIN_ROOT as the path variable the shared scripts read. Telemetry stops claiming a claude-code surface: detect_platform() no longer maps CLAUDECODE / CLAUDE_PLUGIN_ROOT to "claude-code", and the version lookup reports "unknown" for a surface with no manifest instead of falling back to the deleted .claude-plugin manifest.
…h wording The search section claimed explicit searches never repeat a memory. Only the automatic first-prompt search deduplicates; every explicit search runs fresh.
Hooks run on a 3-6 second budget and fire on every tool call, so recording and sending are split. record() appends one JSON line to a local spool and returns; a detached sender drains it in batched PostHog requests, primarily from the flush worker that is already detached and already doing network I/O. Nine events (install, session_start, context_injected, handoff_recovered, search, sidekick, flush, forget, control) carry durations, counts, coarse outcome buckets, and sha256-truncated repo/session hashes. Prompts, memory text, queries, file paths, repository names, and API keys are never sent. An atomic rename to telemetry-<pid>.sending claims a batch so exactly one sender owns it; a crashed sender leaves its claim to be reclaimed rather than losing events. Identity resolves to the account email when an API key is configured, with a persisted anonymous id before that and a one-shot $identify to merge the pre-auth history. Opt out with MEM0_TELEMETRY=false. Pure stdlib, no new dependencies.
…ng memories A wildcard was a legal value for the scope environment variables and nothing questioned it. MEM0_CODE_USER_ID=* with a real repository returned other users' memories, because the repository still satisfied the API's "one real entity ID" rule and the wildcard did the rest. MEM0_PROJECT_ID=* leaked the same way across repositories. Confirmed live against the platform. Wildcards are now refused as identities when read from config, so resolution falls through to the next source, and refused again on the outbound request in search_memories and flush_session. The outbound guard is the load-bearing one: guarding only the config read leaves callers that build a RepoContext directly still able to send one. /mem0:forget used DELETE /v1/memories/?user_id=&app_id=, which the platform honours only on user_id, so forgetting one repository wiped that user's memories in every repository. It now lists the ids matching both scopes and deletes each by id, reports partial failures, and refuses an unscoped delete outright. Verified live that selection picks up only the target scope and leaves same-user-other-repo and other-user-same-repo memories untouched. An empty session posted a zero-message request and logged the resulting 400. Empty batches are now dropped and the flush is a silent no-op. Adds 10 regression tests, each confirmed to fail without its fix (117 -> 127). result.md records the 4,794 live cases behind these findings.
result.md is a local test report, not a shipped artifact.
…emory Search always sent `AND [user_id, app_id]`, both required, so a memory was only reachable when the searcher matched both the person who wrote it and the repo it came from. That trapped team knowledge inside one person and personal preferences inside one repo. Add a `scope` argument that relaxes exactly one of the two conjuncts to the `*` wildcard: repo (default) AND [user_id, app_id] unchanged behaviour team AND [app_id, user_id: "*"] everyone's memories, this repo mine AND [user_id, app_id: "*"] your memories, every repo all OR [team, mine] both Every branch keeps at least one label pinned to something the searcher owns, so no scope relaxes both conjuncts at once. A test asserts that property for every entry in SEARCH_SCOPES. `*` stays a filter value and is never accepted as an identity: _scope_value already rejects it on both the read and write paths. The write path is unchanged. Configurable via the `search_scope` option or MEM0_CODE_SEARCH_SCOPE, defaulting to `repo`, and exposed as an enum on the search_memories MCP tool and `/mem0:search --scope`.
`claude plugin validate --strict integrations/claude-code-plugin` failed with `userConfig.search_scope: Unrecognized key: "enum"`. The Claude Code plugin manifest validates each `userConfig` option against a strict object that accepts exactly `type`, `title`, `description`, `required`, `default`, `multiple`, `sensitive`, `min`, and `max`. There is no enum, choices, options, or oneOf key to declare a closed set of strings with, so any extra key fails the whole manifest rather than being ignored. Nothing changes at runtime. `search_scope()` already returns the configured value only when it is one of the four known scopes and falls back to `repo` otherwise, so the closed set was enforced by the engine and never by the manifest. The allowed values stay documented in the option's own description, which is the text the installer shows.
…cope The plugin knew one kind of thing: what this repository is. It did not know what it takes to work in this repository. A session that lost twenty minutes to `pytest tests/payments` failing on an import error, then found that `pip install -e .` first makes it pass, wrote nothing that would stop the next session losing the same twenty minutes. That lesson is not a fact about the code, so the repository extraction prompt was right to drop it, and there was nowhere else for it to go. Add a second write lane. A session in which a command failed sends one extra add call carrying that session's commands and their short results, scoped to `agent_id: "claude-code"` instead of the user, with `agent_custom_instructions` asking for operating lessons: the failing invocation, what it returned, and the invocation that worked. Mem0 routes instructions by entity, so `user_id` alone gets `custom_instructions` and `agent_id` alone gets `agent_custom_instructions`. That is what makes two extraction policies possible in one project. The split is by content, not by speaker. Sending both IDs on one call would make the Platform partition by who spoke rather than by what was learned, and would move existing repository knowledge onto the agent. The agent lane carries the failure evidence only, is skipped entirely when every command in a session succeeded, and can be switched off with the new `agent_memory` option or `MEM0_CODE_AGENT_MEMORY`. Reads stay one search. `_search_filters` widens the repo-local branches from `AND [app_id, user_id]` to `AND [app_id, OR [user_id, agent_id]]`, so `repo` and `team` return both lanes and the cross-repository `mine` branch is untouched. `team` names the agent explicitly because a wildcard matches any value but not a missing one, and an operating note has no `user_id`. `_scoped_memory_ids` gets the same widening, so `/mem0:forget` deletes the notes it can see. Operating notes are short and numerous, so on the way out `_reserve_agent_slots` gives them a fixed share of the result window rather than letting rank alone decide: one slot in three when Claude searched, two in five on automatic recall, backfilled in rank order when either lane is short. Injected lines carry an `[operating note]` label so Claude can tell a claim about the code from a claim about running it. Memories written before this change carry no lane and count as repository knowledge, so nothing needs migrating. Verified end to end against the Platform: a session that hit an import error and recovered wrote three memories across two lanes, the next session's first prompt was injected with the operating note labelled, and `forget --remote` deleted all three.
… persona tests - narrow team/all scopes in folders without a git remote so strangers sharing a folder name never see each other - forget deletes only the caller's memories; --include-operating-notes opts into wiping the shared agent lane - keep one operating-note slot at small top_k, best score wins at top_k=1 - add min_score (default 0.15) filtered client-side, the API ignores threshold - default search scope is team - expose run_id on search_memories, the MCP tool and /mem0:search - doctor flags a wildcard user_id
run_id is the session key on every write; a flush that arrives without a session id now returns no-session-id and keeps the evidence local instead of writing it under the unknown-session placeholder.
…al lanes Every flush now writes project memory under agent_id (repository facts and operating lessons, shared by everyone in the repository) and personal memory under user_id (preferences that follow the user between repositories). The team scope and its user_id wildcard are gone: repo (default) is the project memory plus your preferences, mine is your preferences everywhere, all is both. Folders without a git remote keep project memory under the user. Forget deletes personal memory by default; --include-project-memory wipes the shared lane.
Project memory is stamped agent_id=<repo slug> and app_id=<repo>/<directory>, never a user_id; personal memory stays under user_id. Search scopes become repo (default), dir and mine, each OR-ing the user's own memories, with run_id and category nested as AND. Plain folders share under local-<folder>-<path hash>. Adds live integration tests and a scoping benchmark under dev/scoping_tests.
…ke forget reliable - write the folder chain to metadata.dirs and filter dir scope by membership, so a parent directory sees its subdirectories and a child never sees the parent - resolve symlinked cwd and git root while keeping legacy project_map lookups - fold failed commands into the project lane, two add calls per flush - skip memories that only name the repository or say nothing was learned - list each forget branch separately to work around the list endpoint returning nothing for OR filters with an empty user_id branch - run the production preflight in a throwaway repository and clean up
…d, and per-branch forget
…claude-code-plugin/dev
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked Issue
Closes #7105
Description
Moves the Claude Code plugin out of
integrations/mem0-plugin/into its own integration atintegrations/claude-code-plugin/, and ships it as 0.3.0.The old plugin served Claude Code, Cursor, Codex, Kimi, Antigravity and OpenCode from one directory over the remote MCP server at
mcp.mem0.ai. Claude Code's plugin surface (lifecycle hooks, skills, agents,userConfig) had outgrown what a shared MCP-only plugin could express, so it gets its own package.integrations/mem0-plugin/stays exactly as it is for the other five harnesses.What the new plugin does:
pending/, recovered on the next session start).search_memories, instead of the old nine remote ones./mem0:*skills and themem0:sidekickagent.The
core/+adapters/claude/split is deliberate:core/is the engine,adapters/claude/is harness glue. A future per-harness plugin starts by copyingcore/and keeping the contract tests verbatim.Also in this PR:
.github/workflows/claude-code-plugin-checks.ymlplus itsci-gate.ymlwiring, both Claude marketplace manifests repointed at the new path, and the docs page rewritten for 0.3.0 with a deprecation note on the old one.Type of Change
AI Assistance
Breaking Changes
mem0@mem0-pluginsnow resolves to./integrations/claude-code-pluginat 0.3.0 instead of./integrations/mem0-pluginat 0.2.15. Anyone on the marketplace entry picks up the new plugin on their next update.For users upgrading from 0.2.x:
search_memories./mem0:*equivalents.~/.mem0/settings.jsonandmem0.mdare no longer read.Do not run
mem0-pluginandclaude-code-pluginat the same time in Claude Code, they both capture the same sessions.Test Coverage
87 tests under
integrations/claude-code-plugin/tests/, all passing locally in 2.9s.ruff check .clean. All five marketplace/plugin manifests parse. The contract tests are the piece that matters for thecore/split, they are what a future harness adapter has to keep passing verbatim.End-to-end verification against the live Platform (
dev/prod_preflight.py, needsMEM0_API_KEY) is tracked separately and is not claimed here.Checklist