Skip to main content
doc0blade47/mem0documentation home
Sign in

Claude Code Plugin

Last updated Oct 11, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

The Claude Code plugin integrates Mem0 memory management into the Claude Code developer agent via Model Context Protocol (MCP) server registration, transcript parsing, and host event hooks. It captures session conversations, extracts relevant coding insights, and injects persistent repository context into agent workflows.

Sources: integrations/claude-code-plugin/.mcp.json:1-10, integrations/claude-code-plugin/core/memory_core.py:2-7, integrations/claude-code-plugin/adapters/claude/transcript.py:1-20

MCP Server Configuration and Manifest

Manifest Registration and Environment Configuration

The Claude Code plugin configures its local Model Context Protocol (MCP) server through a declarative manifest file, .mcp.json, which defines the execution environment and runtime command. The manifest registers a single server named mem0 that executes via python3 targeting the plugin's core script at ${CLAUDE_PLUGIN_ROOT}/core/mcp_server.py. It injects the runtime data directory through the environment variable MEM0_CODE_DATA_DIR, mapped from ${CLAUDE_PLUGIN_DATA}.

{
  "mcpServers": {
    "mem0": {
      "command": "python3",
      "args": ["${CLAUDE_PLUGIN_ROOT}/core/mcp_server.py"],
      "env": {
        "MEM0_CODE_DATA_DIR": "${CLAUDE_PLUGIN_DATA}"
      }
    }
  }
}

Sources: integrations/claude-code-plugin/.mcp.json:1-10

MCP Server Protocol and Tool Definition

The local server implementation in core/mcp_server.py adheres to MCP protocol version 2024-11-05, exposing the search_memories tool. The server handles JSON-RPC request methods including initialize, ping, tools/list, and tools/call, returning capability listings and execution responses. The exposed tool carries annotations declaring it as read-only, idempotent, and open-world.

PROTOCOL_VERSION = "2024-11-05"
TOOL_NAME = "search_memories"
TOOL_DESCRIPTION = (
    "Search memories from earlier work in this repository. Use it before "
    "repeating investigation or when earlier decisions, fixes, commands, or "
    "results may help."
)

Sources: integrations/claude-code-plugin/core/mcp_server.py:21-27

if method == "tools/list":
    return {
        "jsonrpc": "2.0",
        "id": request_id,
        "result": {
            "tools": [
                {
                    "name": TOOL_NAME,
                    "description": TOOL_DESCRIPTION,
                    "inputSchema": TOOL_SCHEMA,
                    "annotations": {
                        "readOnlyHint": True,
                        "idempotentHint": True,
                        "openWorldHint": True,
                    },
                }
            ]
        },
    }

Sources: integrations/claude-code-plugin/core/mcp_server.py:173-191

The tool accepts five search parameters with explicit typing and validation rules enforced by _validate_arguments.

ParameterTypeValidation RulesPurpose
querystringNon-empty, max 2000 chars, requiredDirect question about earlier work in the repository
top_kinteger1 to 20, non-booleanMaximum memories to return
categorystringMust be in CODING_MEMORY_CATEGORY_NAMESOptional memory category filter
scopestringMust be in SEARCH_SCOPESMemory search scope (repo, dir, mine)
run_idstringNon-empty string when providedOptional coding-agent session ID restriction

Sources: integrations/claude-code-plugin/core/mcp_server.py:28-68

Warning

Boolean inputs passed to top_k are explicitly rejected by _validate_arguments even though Python treats booleans as integers; input validation requires strict integer checks between 1 and 20 to prevent malformed query parameters. Sources: integrations/claude-code-plugin/core/mcp_server.py

Transcript Parsing and Ingestion

Conversation Stream Reading and Row Parsing

The Claude Code transcript ingestion engine parses newline-delimited JSON records from conversation transcript files. The _transcript_rows function opens the transcript path in binary mode, seeks to an optional byte offset, and reads line by line. Each line is decoded as UTF-8 with error replacement, parsed via json.loads, and filtered to retain records containing a valid uuid field.

def _transcript_rows(
    path: str, offset: int = 0
) -> tuple[list[dict[str, Any]], int, bool]:
    """Parse transcript rows from a byte offset, returning rows, end offset, and whether the offset was honored."""
    if not path:
        return [], 0, False
    rows = []
    try:
        resolved = Path(path).expanduser()
        if not 0 <= offset <= resolved.stat().st_size:
            offset = 0
        end = offset
        with resolved.open("rb") as handle:
            handle.seek(offset)
            for line in handle:
                if not line.endswith(b"\n"):
                    break
                end += len(line)
                try:
                    row = json.loads(line.decode("utf-8", errors="replace"))
                except json.JSONDecodeError:
                    continue
                if isinstance(row, dict) and row.get("uuid"):
                    rows.append(row)
    except OSError:
        return [], offset, False
    return rows, end, offset > 0
 

Sources: integrations/claude-code-plugin/adapters/claude/transcript.py:39-66

Warning

If a trailing line in the transcript file does not end with a newline byte (b"\n"), it is immediately discarded by the parser loop to prevent ingestion of partial, atomically unwritten JSON writes. Sources: integrations/claude-code-plugin/adapters/claude/transcript.py

Active Chain Traversal and Message Extraction

The parser constructs an active conversation chain by identifying the latest leaf record matching the target session_id while ignoring sidechain records where isSidechain is true. Starting from this leaf node, it traverses backward through parent records using parentUuid references, collecting nodes into a sequence and reversing them into chronological order.

def _active_transcript_chain(
    rows: list[dict[str, Any]], session_id: str
) -> list[dict[str, Any]]:
    """Follow the current Claude conversation branch from its latest record."""
    by_uuid = {str(row["uuid"]): row for row in rows if row.get("uuid")}
    leaf = next(
        (
            row
            for row in reversed(rows)
            if not row.get("isSidechain")
            and str(row.get("sessionId") or "") == session_id
        ),
        None,
    )
    if leaf is None:
        return []
 
    chain = []
    seen = set()
    current = leaf
    while current is not None:
        uuid = str(current.get("uuid") or "")
        if not uuid or uuid in seen:
            break
        seen.add(uuid)
        chain.append(current)
        current = by_uuid.get(str(current.get("parentUuid") or ""))
    chain.reverse()
    return chain

Sources: integrations/claude-code-plugin/adapters/claude/transcript.py:68-96

User prompts and subagent interactions undergo redaction and prefix filtering before memory extraction. Human prompt text filtering strips attachment markers and ignores specific system or command prefixes.

Ignored Prompt PrefixPurpose
<local-command-caveat>Suppresses local shell execution warnings
<local-command-stdout>Suppresses raw stdout captures from tool runs
<local-command-stderr>Suppresses raw stderr captures from tool runs
<command-name>Suppresses internal command execution markers
<system-reminder>Suppresses injected system reminders
<task-notification>Suppresses asynchronous task notification banners

Sources: integrations/claude-code-plugin/adapters/claude/transcript.py:112-120

Claude Hook Dispatching and Lifecycle

Environment Setup and Syspath Bootstrap

The Claude Code hook adapter initializes the execution environment by configuring Python's module search path (sys.path). It resolves the directory containing hook_runner.py by inspecting the script location and falling back between bundled core directories.

 
_here = Path(__file__).resolve()
_bundled_core = _here.parents[2] / "core"
_core_dir = _bundled_core if (_bundled_core / "memory_core.py").is_file() else _bundled_core / "python"
sys.path.insert(0, str(_core_dir))
sys.path.insert(0, str(_here.parent))
 

Sources: integrations/claude-code-plugin/adapters/claude/hook.py:8-14

Once the path is resolved, the adapter registers the harness with the memory core and telemetry subsystems using the identifier claude-code.

configure_harness("claude-code", data_dir_name="claude-code-plugin", source_tag="claude_code_plugin")
telemetry.init(harness="claude-code", source_tag="CLAUDE_CODE_PLUGIN")

Sources: integrations/claude-code-plugin/adapters/claude/hook.py:25-26

Sidekick Subagent Actions

The Claude hook adapter defines specialized handlers for sidekick subagent lifecycle events. The _sidekick_start handler records the start of a subagent with the agent type mem0:sidekick and injects any returned context back into the agent runtime via structured hook output.

def _sidekick_start(store, hook_input):
    context = record_subagent_start(store, {"agent_type": "mem0:sidekick", **hook_input})
    if context:
        return {
            "hookSpecificOutput": {
                "hookEventName": "SubagentStart",
                "additionalContext": context,
            },
        }
 

Sources: integrations/claude-code-plugin/adapters/claude/hook.py:29-38

Conversely, _sidekick_stop records the conclusion of the sidekick subagent session without returning specialized hook output.

def _sidekick_stop(store, hook_input):
    record_subagent_stop(store, {"agent_type": "mem0:sidekick", **hook_input})

Sources: integrations/claude-code-plugin/adapters/claude/hook.py:40-41

Hook Entry Point Mapping

When invoked as the main script, the adapter delegates execution to hook_runner.entry_point, passing custom action bindings and configuration parameters.

ParameterValuePurpose
record_stop_fnrecord_stopTranscript parsing function imported from transcript.py
extra_actions["post-tool-failure"]lambda s, h: record_tool(s, h, failed=True)Records tool execution failures
extra_actions["sidekick-start"]_sidekick_startInitializes sidekick subagent session
extra_actions["sidekick-stop"]_sidekick_stopConcludes sidekick subagent session
data_dir_env"MEM0_CODE_DATA_DIR"Environment variable override for data directory storage
automatic_flush_reasons{"session-end", "pre-compact"}Triggers automatic asynchronous flush workers

Sources: integrations/claude-code-plugin/adapters/claude/hook.py:44-54

Note

Automatic checkpoint flushing is restricted to explicit lifecycle milestones (session-end and pre-compact) to prevent unnecessary background worker spawn overhead during incremental tool executions. Sources: integrations/claude-code-plugin/adapters/claude/hook.py

Core Memory Management and State

Harness Configuration and Constants

The shared memory core initializes runtime metadata via configure_harness(), establishing default identifiers and environment variables across execution harnesses.

DEFAULT_API_URL = "https://api.mem0.ai"
PLUGIN_VERSION = "0.3.3"
 
_harness_name: str = "generic"
_harness_env_prefix: str = "MEM0_PLUGIN"
_harness_data_dir_name: str = "mem0-plugin"
_harness_source_tag: str = "mem0_plugin"
 

Sources: integrations/claude-code-plugin/core/memory_core.py:31-38

def configure_harness(
    name: str,
    env_prefix: str = "",
    data_dir_name: str = "",
    source_tag: str = "",
) -> None:
    global _harness_name, _harness_env_prefix, _harness_data_dir_name, _harness_source_tag
    _harness_name = name
    _harness_env_prefix = env_prefix or f"MEM0_{name.upper().replace('-', '_')}"
    _harness_data_dir_name = data_dir_name or f"{name}-plugin"
    _harness_source_tag = source_tag or f"{name.replace('-', '_')}_plugin"
 

Sources: integrations/claude-code-plugin/core/memory_core.py:40-51

Memory persistence and extraction operations adhere to explicit character, exchange, and retry limits defined at the module level.

Constant NameValuePurpose
MAX_COMMAND_CHARS2000Maximum character length for captured command strings
MAX_RESULT_CHARS2500Maximum character length for command execution results
MAX_EPISODE_CHARS12000Maximum character length for accumulated episode payloads
CHECKPOINT_EXCHANGES5Exchange count threshold for checkpoint triggers
CHECKPOINT_MESSAGES10Message count threshold for checkpoint triggers
CHECKPOINT_SOURCE_CHARS40000Maximum source text size for checkpoint extraction
DEFAULT_MAX_CONTEXT_CHARS4000Default character budget for injected memory context
MAX_EXTRACTION_INPUT_TOKENS24000Token limit for LLM memory extraction inputs
MAX_FLUSH_ATTEMPTS5Maximum retry attempts for asynchronous flush workers
FORGET_PAGE_SIZE100Pagination size for memory deletion operations
FORGET_MAX_PAGES50Maximum pagination depth for memory deletion

Sources: integrations/claude-code-plugin/core/memory_core.py:62-72

Note

The harness configuration exposes harness_config(), which returns a dictionary containing the active plugin name, environment prefix, data directory name, and source tracking tag used by downstream storage and telemetry components. Sources: integrations/claude-code-plugin/core/memory_core.py

Memory Instructions and Categorization

Project memory extraction relies on strict prompt instructions that guide the agent to save concise repository facts regarding behavior, implementation locations, constraints, and reasoning.

PROJECT_MEMORY_INSTRUCTIONS = """Save concise repository facts that will help with future coding work.
 
A completed change should produce one memory explaining the resulting behavior, where it is implemented when useful, and any important constraints or reasoning. Exploration or accepted decisions may produce separate memories only when they are independently useful.
 
Use the coding agent's final response for conclusions about current repository behavior. Do not save proposed or recommended changes unless the user accepted them or the coding agent completed them. Treat subagent responses as supporting repository evidence, not as decisions.
 
Write about the repository, not the user, assistant, session, or task. Do not include test results, documentation updates, release notes, or temporary state.
 
If nothing useful was established, return no memories."""

Sources: integrations/claude-code-plugin/core/memory_core.py:74-82

Personal memory extraction uses distinct instructions focused on user preferences, written in the third person.

PERSONAL_MEMORY_INSTRUCTIONS = """Save concise facts about the user that will help in any repository: preferred tools, package managers, languages, coding style, review and communication preferences, and anything the user explicitly asked to be remembered about themselves.
 
Write in the third person about the user, not about the repository, the assistant, the session, or the task. Do not save repository facts, project decisions, commands, or what was built.
 
Never save that the user has no preferences or that nothing was learned. If nothing was learned about the user, return no memories."""

Sources: integrations/claude-code-plugin/core/memory_core.py:84-88

Coding memories are classified into explicit structural categories to organize project knowledge.

Category IdentifierDescription
project_knowledgeWhat the project is and how its code, APIs, data, files, and components work.
decisions_and_constraintsWhy an approach was chosen, what must remain true, and rules future work must follow.
workflowsHow to run, test, debug, deploy, configure, or otherwise work on the project.
problems_and_fixesBugs, failures, known pitfalls, their causes, and how to fix or avoid them.
resultsOutcomes and measurements from tests, benchmarks, experiments, or investigations.

Sources: integrations/claude-code-plugin/core/memory_core.py:90-121

Warning

CODING_MEMORY_CATEGORY_NAMES is programmatically derived as a tuple of keys from CODING_MEMORY_CATEGORIES. Modifying the dictionary structure directly impacts category validation downstream. Sources: integrations/claude-code-plugin/core/memory_core.py

Sanitization and Security Redaction

Command matching rules detect test and build invocations using regular expressions across multiple testing frameworks and build tools.

TEST_COMMAND_RE = re.compile(
    r"(?:^|\s)(?:pytest|py\.test|jest|vitest|go\s+test|cargo\s+test|"
    r"npm\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|yarn\s+test|"
    r"mvn\s+test|gradle\s+test|make\s+test)(?:\s|$)",
    re.IGNORECASE,
)
BUILD_COMMAND_RE = re.compile(
    r"(?:^|\s)(?:npm|pnpm|yarn)\s+(?:run\s+)?build(?:\s|$)|"
    r"(?:^|\s)(?:cargo|go|mvn|gradle|make)\s+build(?:\s|$)",
    re.IGNORECASE,
)

Sources: integrations/claude-code-plugin/core/memory_core.py:128-138

To prevent credential leakage, the memory core scans strings and serialized payloads against a comprehensive list of secret patterns, replacing matched secrets with [REDACTED].

SECRET_PATTERNS = [
    re.compile(r"(?i)(authorization\s*[:=]\s*(?:bearer|token)\s+)[^\s\"']+"),
    re.compile(
        r"(?i)((?:api[_-]?key|secret[_-]?access[_-]?key|session[_-]?token)\s*[:=]\s*)[^\s\"']+"
    ),
    re.compile(
        r"(?i)((?:access[_-]?token|refresh[_-]?token|password|credential)"
        r"\s*[:=]\s*)[^\s&\"']+"
    ),
    re.compile(r"\b(?:sk|m0|mem0_sk|psk)-[A-Za-z0-9_\-]{12,}\b"),
    re.compile(r"\b(?:ASIA|AKIA)[A-Z0-9]{12,}\b"),
    re.compile(r"\b(?:ghp_|github_pat_|xox[baprs]-)[A-Za-z0-9_\-]{12,}\b"),
    re.compile(
        r"-----BEGIN [^-]*PRIVATE KEY-----.*?-----END [^-]*PRIVATE KEY-----",
        re.DOTALL,
    ),
    re.compile(
        r'(?i)("(?:api[_-]?key|password|secret(?:[_-]?access[_-]?key)?'
        r'|(?:access|refresh|session)[_-]?token|token|authorization|credential'
        r')"\s*:\s*")(?:\\.|[^"\\])*'
    ),
]

Sources: integrations/claude-code-plugin/core/memory_core.py:140-161

The utility function redact() processes arbitrary input values, serializing non-string types via JSON and applying all defined secret regular expression patterns sequentially.

def redact(value: Any) -> str:
    text = (
        value
        if isinstance(value, str)
        else json.dumps(value, ensure_ascii=False, default=str)
    )
    for pattern in SECRET_PATTERNS:
        if pattern.groups:
            text = pattern.sub(r"\1[REDACTED]", text)
        else:
            text = pattern.sub("[REDACTED]", text)
    return text

Sources: integrations/claude-code-plugin/core/memory_core.py:168-179

Furthermore, bounded() enforces strict character length constraints on redacted text, appending truncation notices when size limits are exceeded.

def bounded(value: Any, limit: int) -> str:
    text = redact(value).strip()
    if len(text) <= limit:
        return text
    return text[:limit] + f"\n...[truncated {len(text) - limit} chars]"
 

Sources: integrations/claude-code-plugin/core/memory_core.py:182-187

Asynchronous Remote Checkpoint Flushing

Detached Worker Initialization and Handoff

When session end hooks in print-mode processes risk being terminated prematurely, the hook runner persists its state locally and delegates processing to a detached background worker. The worker entry point expects a single argument representing the path to the handoff JSON payload. It configures the runtime environment and telemetry using environment variables such as MEM0_PLUGIN_HARNESS, MEM0_PLUGIN_SOURCE_TAG, and MEM0_PLUGIN_ENV_PREFIX.

def main() -> int:
    if len(sys.argv) != 2:
        return 2
    handoff_path = Path(sys.argv[1])
    os.environ["MEM0_CODE_HANDOFF_PATH"] = str(handoff_path)
    harness = os.environ.get("MEM0_PLUGIN_HARNESS")
    if harness:
        source_tag = os.environ.get("MEM0_PLUGIN_SOURCE_TAG", "")
        configure_harness(
            harness,
            env_prefix=os.environ.get("MEM0_PLUGIN_ENV_PREFIX", ""),
            data_dir_name=os.environ.get("MEM0_PLUGIN_DATA_DIR_NAME", ""),
            source_tag=source_tag,
        )
        telemetry.init(harness=harness, source_tag=source_tag.upper())

Sources: integrations/claude-code-plugin/core/flush_worker.py:25-39

Payloads can specify a delay before execution occurs. If delay_seconds is greater than zero, the worker writes the configuration to a temporary process-specific file, replaces the original handoff path, and sleeps for the designated duration.

try:
    payload = json.loads(handoff_path.read_text(encoding="utf-8"))
    delay = float(payload.get("delay_seconds") or 0)
    if delay > 0:
        payload.pop("delay_seconds", None)
        temporary = handoff_path.with_suffix(f".{os.getpid()}.tmp")
        try:
            temporary.write_text(json.dumps(payload), encoding="utf-8")
            temporary.replace(handoff_path)
        finally:
            temporary.unlink(missing_ok=True)
        time.sleep(delay)
        if not handoff_path.exists():
            return 0

Sources: integrations/claude-code-plugin/core/flush_worker.py:41-54

Note

If the handoff file is removed during the delay interval, the worker terminates immediately with a status code of 0. Sources: integrations/claude-code-plugin/core/flush_worker.py

In-Flight Concurrency and Checkpoint Execution

When wait_for_inflight is enabled in the payload, the worker tracks active session operations prior to executing the checkpoint session. It computes a deadline using MEM0_CODE_EXTRACTION_WAIT_SECONDS (defaulting to 120 seconds) and periodically touches the handoff heartbeat while waiting for active flushes to clear.

if wait_for_inflight:
    session_id = str(
        hook_input.get("session_id") or "unknown-session"
    )
    repo = store.repo_for_session(session_id, hook_input.get("cwd"))
    deadline = time.monotonic() + float(
        os.environ.get("MEM0_CODE_EXTRACTION_WAIT_SECONDS", "120")
    )
    while (
        store.has_inflight_flush(repo.identity, session_id)
        and time.monotonic() < deadline
    ):
        touch_handoff_heartbeat()
        time.sleep(0.25)

Sources: integrations/claude-code-plugin/core/flush_worker.py:60-73

Once conditions are satisfied, the worker invokes checkpoint_session() with the EvidenceStore, hook inputs, and reason string. Completion status is evaluated against a fixed set of recognized terminal states.

# Hooks capture the conversation before handoff; the worker only flushes it.
result = checkpoint_session(store, hook_input, reason)
print(json.dumps(result, sort_keys=True), flush=True)
completed = result.get("status") in {
    "semantic-succeeded",
    "explicitly-stored",
    "nothing-to-flush",
}

Sources: integrations/claude-code-plugin/core/flush_worker.py:74-81

Status IdentifierMeaning
semantic-succeededMemory extraction and remote persistence completed successfully.
explicitly-storedCheckpoint data was stored directly without additional semantic parsing.
nothing-to-flushNo new conversation evidence accumulated since the previous checkpoint.

Sources: integrations/claude-code-plugin/core/flush_worker.py:77-81

Cleanup and File State Management

Upon completion of the flush cycle, telemetry data is flushed, and handoff files are managed based on success metrics. Successfully completed checkpoints unlink the handoff file, whereas interrupted running states revert their file suffix for future recovery.

finally:
    telemetry.flush()
    if completed:
        try:
            handoff_path.unlink()
        except OSError:
            pass
    elif handoff_path.suffix == ".running":
        try:
            handoff_path.replace(handoff_path.with_suffix(".json"))
        except OSError:
            pass

Sources: integrations/claude-code-plugin/core/flush_worker.py:85-96

Was this page helpful?