Relevant source files
The following files were used as context for generating this wiki page:
- integrations/opencode-plugin/opencode-mem0.ts
- integrations/claude-code-plugin/.mcp.json
- integrations/claude-code-plugin/core/hook_runner.py
- integrations/claude-code-plugin/adapters/claude/transcript.py
- integrations/codex-plugin/core/hook_runner.py
- integrations/antigravity-plugin/core/hook_runner.py
- integrations/agent-plugin-core/python/hook_runner.py
- integrations/kimi-plugin/core/hook_runner.py
- integrations/cursor-plugin/core/hook_runner.py
- integrations/claude-code-plugin/core/mcp_server.py
- integrations/claude-code-plugin/core/memory_core.py
- integrations/claude-code-plugin/adapters/claude/hook.py
- integrations/codex-plugin/core/mcp_server.py
- integrations/antigravity-plugin/core/mcp_server.py
- integrations/codex-plugin/core/memory_core.py
- integrations/antigravity-plugin/core/memory_core.py
- integrations/agent-plugin-core/python/memory_core.py
- integrations/kimi-plugin/core/memory_core.py
- integrations/kimi-plugin/core/mcp_server.py
- integrations/claude-code-plugin/core/flush_worker.py
- integrations/antigravity-plugin/core/flush_worker.py
- integrations/hermes-plugin-mem0/init.py
- integrations/cursor-plugin/core/mcp_server.py
- integrations/kimi-plugin/core/flush_worker.py
- integrations/cursor-plugin/core/flush_worker.py
- integrations/codex-plugin/core/flush_worker.py
- integrations/agent-plugin-core/python/flush_worker.py
- integrations/mem0-agent-plugin/core/mcp_server.py
- integrations/agent-plugin-core/python/mcp_server.py
- integrations/antigravity-plugin/hooks/adapter.py
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.
| Parameter | Type | Validation Rules | Purpose |
|---|---|---|---|
query | string | Non-empty, max 2000 chars, required | Direct question about earlier work in the repository |
top_k | integer | 1 to 20, non-boolean | Maximum memories to return |
category | string | Must be in CODING_MEMORY_CATEGORY_NAMES | Optional memory category filter |
scope | string | Must be in SEARCH_SCOPES | Memory search scope (repo, dir, mine) |
run_id | string | Non-empty string when provided | Optional 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 chainSources: 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 Prefix | Purpose |
|---|---|
<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.
| Parameter | Value | Purpose |
|---|---|---|
record_stop_fn | record_stop | Transcript 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_start | Initializes sidekick subagent session |
extra_actions["sidekick-stop"] | _sidekick_stop | Concludes 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 Name | Value | Purpose |
|---|---|---|
MAX_COMMAND_CHARS | 2000 | Maximum character length for captured command strings |
MAX_RESULT_CHARS | 2500 | Maximum character length for command execution results |
MAX_EPISODE_CHARS | 12000 | Maximum character length for accumulated episode payloads |
CHECKPOINT_EXCHANGES | 5 | Exchange count threshold for checkpoint triggers |
CHECKPOINT_MESSAGES | 10 | Message count threshold for checkpoint triggers |
CHECKPOINT_SOURCE_CHARS | 40000 | Maximum source text size for checkpoint extraction |
DEFAULT_MAX_CONTEXT_CHARS | 4000 | Default character budget for injected memory context |
MAX_EXTRACTION_INPUT_TOKENS | 24000 | Token limit for LLM memory extraction inputs |
MAX_FLUSH_ATTEMPTS | 5 | Maximum retry attempts for asynchronous flush workers |
FORGET_PAGE_SIZE | 100 | Pagination size for memory deletion operations |
FORGET_MAX_PAGES | 50 | Maximum 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 Identifier | Description |
|---|---|
project_knowledge | What the project is and how its code, APIs, data, files, and components work. |
decisions_and_constraints | Why an approach was chosen, what must remain true, and rules future work must follow. |
workflows | How to run, test, debug, deploy, configure, or otherwise work on the project. |
problems_and_fixes | Bugs, failures, known pitfalls, their causes, and how to fix or avoid them. |
results | Outcomes 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 textSources: 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 0Sources: 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 Identifier | Meaning |
|---|---|
semantic-succeeded | Memory extraction and remote persistence completed successfully. |
explicitly-stored | Checkpoint data was stored directly without additional semantic parsing. |
nothing-to-flush | No 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:
passSources: integrations/claude-code-plugin/core/flush_worker.py:85-96
Related
On This Page
OverviewMCP Server Configuration and ManifestManifest Registration and Environment ConfigurationMCP Server Protocol and Tool DefinitionTranscript Parsing and IngestionConversation Stream Reading and Row ParsingActive Chain Traversal and Message ExtractionClaude Hook Dispatching and LifecycleEnvironment Setup and Syspath BootstrapSidekick Subagent ActionsHook Entry Point MappingCore Memory Management and StateHarness Configuration and ConstantsMemory Instructions and CategorizationSanitization and Security RedactionAsynchronous Remote Checkpoint FlushingDetached Worker Initialization and HandoffIn-Flight Concurrency and Checkpoint ExecutionCleanup and File State ManagementRelated