Relevant source files
The following files were used as context for generating this wiki page:
- integrations/pi-agent-plugin/src/commands.ts
- integrations/pi-agent-plugin/src/attribution.ts
- integrations/opencode-plugin/opencode-mem0.ts
- integrations/pi-agent-plugin/src/memory/tools.ts
- integrations/pi-agent-plugin/src/entry.ts
- integrations/agent-plugin-core/python/memory_core.py
- integrations/mem0-agent-plugin/core/memory_core.py
- integrations/antigravity-plugin/core/memory_core.py
- integrations/claude-code-plugin/core/memory_core.py
- integrations/kimi-plugin/core/memory_core.py
- integrations/codex-plugin/core/memory_core.py
- integrations/cursor-plugin/core/memory_core.py
- integrations/pi-agent-plugin/src/prompt.ts
- cli/python/src/mem0_cli/output.py
- integrations/agent-plugin-core/python/mcp_server.py
- integrations/mem0-agent-plugin/core/mcp_server.py
- integrations/pi-agent-plugin/src/memory/formatting.ts
- integrations/pi-agent-plugin/src/index.ts
- integrations/mem0-agent-plugin/core/memory_cli.py
- integrations/claude-code-plugin/core/mcp_server.py
- integrations/antigravity-plugin/core/mcp_server.py
- integrations/agent-plugin-core/python/memory_cli.py
- cli/node/src/output.ts
- integrations/kimi-plugin/core/mcp_server.py
- integrations/codex-plugin/core/mcp_server.py
- integrations/antigravity-plugin/core/memory_cli.py
- integrations/claude-code-plugin/core/memory_cli.py
- integrations/agent-plugin-core/python/hook_runner.py
- integrations/cursor-plugin/core/mcp_server.py
- integrations/agent-plugin-core/python/telemetry.py
Overview
The Pi Agent Plugin integrates Mem0's persistent semantic memory directly into the Pi Coding Agent, managing the extension lifecycle, request attribution, tool execution, interactive user commands, and context injection. Designed to maintain long-term memory across sessions, the plugin automatically binds workspace project scopes, applies client attribution headers, and injects relevant memory policies into the agent's startup stream.
Extension Lifecycle and Startup Registration
Initialization and Configuration Binding
The extension entry point exports a default function, mem0Extension, which accepts an ExtensionAPI instance from @earendil-works/pi-coding-agent. Execution begins by invoking loadConfig() to read settings from the environment or disk configuration paths.
export default function mem0Extension(pi: ExtensionAPI): void {
const config = loadConfig();
if (!config.apiKey) {
console.warn("[mem0] No API key found. Set MEM0_API_KEY or add apiKey to ~/.pi/agent/mem0-config.json. Extension disabled.");
return;
}Sources: integrations/pi-agent-plugin/src/entry.ts:24-30
If config.apiKey is absent, a warning message is emitted to the console and the initialization terminates immediately, disabling the extension without throwing an unhandled exception.
Warning
Omitting MEM0_API_KEY or failing to supply an apiKey in ~/.pi/agent/mem0-config.json halts extension registration entirely, bypassing tool, command, and lifecycle event bindings.
Sources: integrations/pi-agent-plugin/src/entry.ts:27-30
User ID Resolution Strategy
When initializing scope context, the plugin resolves the active user identifier through resolveUserId(configUserId: string). The resolution sequence evaluates configuration overrides, environment variables, and system user data in a strict fallback order.
| Priority | Source | Condition | Fallback / Return Value |
|---|---|---|---|
| 1 | configUserId parameter | Non-empty string | Returns configUserId directly |
| 2 | process.env.USER | Environment variable is set | Returns process.env.USER |
| 3 | process.env.USERNAME | Environment variable is set | Returns process.env.USERNAME |
| 4 | os.userInfo().username | System call succeeds | Returns system username string |
| 5 | Catch block / Final default | System call throws error | Returns "default" |
export function resolveUserId(configUserId: string): string {
if (configUserId) return configUserId;
if (process.env.USER) return process.env.USER;
if (process.env.USERNAME) return process.env.USERNAME;
try { return os.userInfo().username; } catch { return "default"; }
}Sources: integrations/pi-agent-plugin/src/entry.ts:17-22
Client Setup and Event Handlers
Once authenticated, the plugin instantiates a MemoryClient with the verified API key and decorates it using applySurfaceHeaders(mem0). It constructs initial scope and telemetry contexts before registering memory tools, CLI commands, and auto-capture listeners.
const mem0 = new MemoryClient({ apiKey: config.apiKey });
// Every path below shares this client: automatic recall, capture, the memory
// tools and deletion as well as the slash commands. Attribution belongs here
// rather than on individual calls, or everything except the commands reports
// as generic SDK traffic.
applySurfaceHeaders(mem0);
const scopeCtx: ScopeContext = {
userId: resolveUserId(config.userId),
appId: "",
runId: "unknown",
};
function getScopeCtx(): ScopeContext {
return scopeCtx;
}
const telemetryCtx = { apiKey: config.apiKey };
const lifecycle = createMemoryLifecycle();
// ── Register tool + commands + auto-capture ─────────────────────────
registerMemoryTool(pi, mem0, config, getScopeCtx, telemetryCtx);
registerCommands(pi, mem0, config, getScopeCtx, telemetryCtx);
setupAutoCapture(pi, mem0, config, getScopeCtx, telemetryCtx, lifecycle);
captureEvent("pi.plugin.registered", {
auto_capture: config.autoCapture,
default_scope: config.defaultScope,
}, telemetryCtx);
Sources: integrations/pi-agent-plugin/src/entry.ts:32-61
The extension binds to three primary host agent lifecycle events:
session_start: Begins the memory lifecycle session, detects the application ID from the current working directory (ctx.cwd), extracts the run ID from the session file, and optionally overrides the user ID if configured.before_agent_start: Appends theMEMORY_POLICYprompt instruction and performs guaranteed memory recall by querying the memory client using resolved search filters.session_shutdown: Captures telemetry events signaling the termination of the active session.
// ── session_start: detect project + session, reconstruct scope ──────
pi.on("session_start", async (_event, ctx) => {
lifecycle.beginSession();
scopeCtx.appId = detectAppId(ctx.cwd);
const sessionFile = ctx.sessionManager?.getSessionFile?.();
scopeCtx.runId = detectRunId(sessionFile);
if (config.userId) {
scopeCtx.userId = config.userId;
}
captureEvent("pi.session.start", {}, telemetryCtx);
});
// ── before_agent_start: append memory policy and recall ─────────────
pi.on("before_agent_start", async (event, _ctx) => {
let extra = MEMORY_POLICY;
// Guaranteed retrieval: prefetch memories relevant to this prompt so the
// agent always has them, rather than depending on it to call the tool.
const recall = await lifecycle.recall(
event.prompt ?? "",
config.contextInjection,
(q) => mem0.search(q, { filters: resolveSearchFilters("project", scopeCtx) }),
);
if (recall) extra += "\n\n" + recall;
return {
systemPrompt: (event.systemPrompt ?? "") + "\n\n" + extra,
};
});
// ── session_shutdown ────────────────────────────────────────────────
pi.on("session_shutdown", async () => {
captureEvent("pi.session.stop", {}, telemetryCtx);
});Sources: integrations/pi-agent-plugin/src/entry.ts:62-98
Surface Attribution and Request Headers
Attribution Metadata and Stack Bounding
The Pi Agent Plugin decorates outbound requests through the MemoryClient instance to identify traffic originating from the extension. Surface identification constants declare the platform source and application identity, while package version resolution reads the plugin version dynamically from package.json with a fallback to "unknown".
export const PLATFORM_SOURCE = "PI_AGENT";
/** Host app the plugin runs inside. Allowlisted server-side. */
export const PLATFORM_APPLICATION = "pi";
const PLUGIN_VERSION = (() => {
try {
return JSON.parse(
fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"),
).version as string;
} catch {
return "unknown";
}
})();Sources: integrations/pi-agent-plugin/src/attribution.ts:5-18
To track client call chains without fragmenting client identifiers, boundedStack appends the plugin's client string while enforcing strict capacity constraints. It limits the stack to a maximum of 4 entries and 200 total characters by dropping whole entries rather than slicing strings mid-identifier.
Warning
Slicing client header strings would sever identifiers and leave malformed fragments that the platform parses as autonomous client names. Bounding drops entire entries to preserve token integrity.
Sources: integrations/pi-agent-plugin/src/attribution.ts:20-40
Request Header Application
The applySurfaceHeaders function stamps surface identity parameters onto the shared MemoryClient headers object once during extension startup. Setting headers directly on the client instance ensures that automatic recall, memory capture, tool executions, and deletions all transmit attribution metadata.
| Header Name | Configuration Rule | Value / Behavior |
|---|---|---|
X-Mem0-Source | Set-once if empty or whitespace | PLATFORM_SOURCE ("PI_AGENT") |
X-Application | Set-once if empty or whitespace | PLATFORM_APPLICATION ("pi") |
X-Mem0-Client | Append-only stack | mem0-pi-agent/${PLUGIN_VERSION} bounded by boundedStack |
export function applySurfaceHeaders(client: MemoryClient): void {
const headers = client.headers as Record<string, string>;
if (!headers["X-Mem0-Source"]?.trim()) headers["X-Mem0-Source"] = PLATFORM_SOURCE;
if (!headers["X-Application"]?.trim()) headers["X-Application"] = PLATFORM_APPLICATION;
const existing = (headers["X-Mem0-Client"] ?? "")
.split(",")
.map((part) => part.trim())
.filter(Boolean);
headers["X-Mem0-Client"] = boundedStack(existing, `mem0-pi-agent/${PLUGIN_VERSION}`);
}Sources: integrations/pi-agent-plugin/src/attribution.ts:55-65
Interactive User Commands and Diagnostics
Command Registration and Memory Manipulation
The plugin registers interactive commands via the ExtensionAPI interface to allow developers to manually store, search, delete, and browse memories directly within the terminal interface. Each command validates user input, resolves scope filters, interacts with the MemoryClient, and dispatches structured feedback messages to the UI.
| Command Name | Description | Required Arguments | Underlying Action |
|---|---|---|---|
/mem0-remember | Store a memory verbatim (no inference) | Text string | mem0.add([{ role: "user", content: text }], { infer: false, ... }) |
/mem0-forget | Delete memories matching a natural language query | Natural language query | searchMemories(query) followed by mem0.delete(target.id) |
/mem0-search | Semantic search across memories | Search query | mem0.search(query, { filters, threshold, topK: 10, rerank: true }) |
/mem0-tour | Browse all memories grouped by category | Optional scope (project, session, global) | mem0.getAll({ filters }) |
pi.registerCommand("mem0-remember", {
description: "Store a memory verbatim (no inference)",
handler: async (args, ctx) => {
const text = args?.trim();
if (!text) {
ctx.ui.notify("Usage: /mem0-remember <text>", "warning");
return;
}
const addParams = resolveAddParams(config.defaultScope, getScopeCtx());
const result = await mem0.add(
[{ role: "user", content: text }],
{ ...addParams, customCategories: DEFAULT_CUSTOM_CATEGORIES, infer: false, source: PLATFORM_SOURCE },
);
captureCommandEvent("mem0-remember", {}, telemetryCtx);
const storedItems = (Array.isArray(result) ? result : [])
.map((m) => (m as { memory?: string }).memory)
.filter((m): m is string => Boolean(m));
const items = storedItems.length > 0 ? storedItems : [text];
sendFeedback(
"mem0-remember",
[`**Stored to ${config.defaultScope} memory**`, ...items.map((m) => `- ${m}`)].join("\n"),
);
},
});Sources: integrations/pi-agent-plugin/src/commands.ts:48-73
Note
The search helper explicitly injects source: PLATFORM_SOURCE and rerank: true into the SearchMemoryOptions parameter block while utilizing a fixed top-K ceiling of 10.
Deletion and Browsing Workflows
When a user invokes /mem0-forget, the command searches for matching memories using the default scope. If multiple matching records are returned, the plugin invokes ctx.ui.select to present compact formatted labels so the developer can pick the exact record to remove. If a single match is found, it triggers a confirmation prompt via ctx.ui.confirm before executing the deletion.
pi.registerCommand("mem0-forget", {
description: "Delete memories matching a natural language query",
handler: async (args, ctx) => {
const query = args?.trim();
if (!query) {
ctx.ui.notify("Usage: /mem0-forget <query>", "warning");
return;
}
const memories = await searchMemories(query, config.defaultScope);
if (memories.length === 0) {
captureCommandEvent("mem0-forget", { result_count: 0 }, telemetryCtx);
sendFeedback("mem0-forget", `**No matches for "${query}"** — nothing to forget.`);
return;
}
const forgotten = (mem: Parameters<typeof formatMemoryCompact>[0]) => {
captureCommandEvent("mem0-forget", { deleted_count: 1 }, telemetryCtx);
sendFeedback(
"mem0-forget",
[`**Forgotten from ${config.defaultScope} memory**`, `- ${formatMemoryCompact(mem)}`].join("\n"),
);
};
if (memories.length === 1) {
const target = memories[0];
const confirmed = await ctx.ui.confirm("Delete this memory?", formatMemoryCompact(target));
if (!confirmed) {
sendFeedback("mem0-forget", "**Cancelled** — no memories deleted.");
return;
}
await mem0.delete(target.id);
forgotten(target);
return;
}
const labels = memories.map((m) => formatMemoryCompact(m));
const selected = await ctx.ui.select(
`Found ${pluralize(memories.length, "match", "matches")} for "${query}" — which should I delete?`,
labels,
);
if (!selected) {
sendFeedback("mem0-forget", "**Cancelled** — no memories deleted.");
return;
}
const idx = labels.indexOf(selected);
if (idx < 0) return;
const target = memories[idx];
await mem0.delete(target.id);
forgotten(target);
},
});Sources: integrations/pi-agent-plugin/src/commands.ts:75-127
The /mem0-tour command accepts an optional scope argument (project, session, or global). If omitted, it falls back to config.defaultScope. It validates the raw input against permitted scope strings, retrieves all records matching the scope filters, groups them by category, and sends formatted output to the telemetry and UI streams.
pi.registerCommand("mem0-tour", {
description: "Browse all memories grouped by category",
handler: async (args, ctx) => {
const raw = args?.trim().toLowerCase();
const validScopes: Scope[] = ["project", "session", "global"];
if (raw && !validScopes.includes(raw as Scope)) {
ctx.ui.notify(`Invalid scope "${raw}". Must be one of: ${validScopes.join(", ")}`, "warning");
return;
}
const scope: Scope = (raw as Scope) || config.defaultScope;
const filters = resolveSearchFilters(scope, getScopeCtx());
const result = await mem0.getAll({ filters });
const memories = result.results ?? [];
if (memories.length === 0) {
captureCommandEvent("mem0-tour", { memory_count: 0, scope }, telemetryCtx);Sources: integrations/pi-agent-plugin/src/commands.ts:157-172
Tool Execution and Agent Guidance
Tool Registration and Parameter Schema
The plugin registers the mem0_memory tool through the registerMemoryTool function, exposing six specific memory actions to the agent. Parameter validation relies on TypeBox schema definitions and StringEnum types that govern valid operation inputs and scoping.
| Parameter Name | Type | Allowed Values / Description |
|---|---|---|
action | StringEnum | "search", "add", "get_all", "update", "delete", "delete_all" |
query | String | Search text required for action "search" |
content | String | Memory text required for actions "add" and "update" |
memory_id | String | Target memory ID required for actions "update" and "delete" |
scope | StringEnum | "project", "session", "global" |
parameters: Type.Object({
action: StringEnum(
[
"search",
"add",
"get_all",
"update",
"delete",
"delete_all",
] as const,
{
description:
"Memory operation to run: \"search\" (semantic recall of earlier work in this repository), \"add\" (save a new fact/preference/decision), \"get_all\" (list everything in scope, no query needed), \"update\" (replace an existing memory's text by id), \"delete\" (remove one memory by id), \"delete_all\" (wipe every memory in the scope -- destructive, only on explicit request).",
},
),
query: Type.Optional(
Type.String({
description:
`Search text -- required for action "search". ${SEARCH_QUERY_DESCRIPTION}`,
}),
),
content: Type.Optional(
Type.String({
description:
"Memory text -- required for action \"add\" (the fact to store) and \"update\" (the replacement text).",
}),
),
memory_id: Type.Optional(
Type.String({
description:
"Target memory's ID -- required for \"update\" and \"delete\". Use an ID returned by a prior \"search\" or \"get_all\".",
}),
),
scope: Type.Optional(
StringEnum(["project", "session", "global"] as const, {
description:
"Where to read/write: \"project\" (default -- this repo), \"session\" (this run only), or \"global\" (across ALL projects; only when the user explicitly wants cross-project recall). Omit for normal queries.",
}),
),
}),Sources: integrations/pi-agent-plugin/src/memory/tools.ts:157-196
Warning
Action delete_all wipes every memory within the specified scope. Because this operation is destructive, execution requires explicit user authorization and must never be triggered implicitly by standard agent autonomy routines.
Sources: integrations/pi-agent-plugin/src/memory/tools.ts:124-132, integrations/pi-agent-plugin/src/memory/tools.ts:169-170
Execution Dispatch and Output Limits
The buildToolExecute factory returns an asynchronous handler that processes tool parameters, checks abort signals, resolves tool scopes, and dispatches calls to the underlying MemoryClient. Tool outputs are bound by strict output length constraints to protect agent context windows.
| Constraint Constant | Threshold Value | Enforcement Behavior |
|---|---|---|
MAX_OUTPUT_LINES | 200 lines | Truncates lists exceeding line count |
MAX_OUTPUT_BYTES | 50,000 bytes | Truncates string length exceeding byte limit |
const MAX_OUTPUT_LINES = 200;
const MAX_OUTPUT_BYTES = 50_000;
function normalizeMemoryId(id: string): string {
return id.replace(/^\[?mem0:([0-9a-f-]{36})\]?$/i, "$1");
}
function truncateOutput(text: string): string {
const lines = text.split("\n");
if (lines.length <= MAX_OUTPUT_LINES && text.length <= MAX_OUTPUT_BYTES) {
return text;
}
const kept = lines.slice(0, MAX_OUTPUT_LINES);
let result = kept.join("\n");
if (result.length > MAX_OUTPUT_BYTES) {
result = result.slice(0, MAX_OUTPUT_BYTES);
}
const dropped = lines.length - kept.length;
if (dropped > 0 || text.length > MAX_OUTPUT_BYTES) {
result += `\n\n[Output truncated: showing ${kept.length} of ${lines.length} lines]`;
}
return result;
}Sources: integrations/pi-agent-plugin/src/memory/tools.ts:19-43
Memory identifiers returned by search results or passed to updates are automatically normalized via normalizeMemoryId, which strips bracketed wrappers like [mem0:uuid] down to raw UUID strings. Telemetry wrappers track execution latency, success status, and result match counts across all tool invocations.
function normalizeMemoryId(id: string): string {
return id.replace(/^\[?mem0:([0-9a-f-]{36})\]?$/i, "$1");
}Sources: integrations/pi-agent-plugin/src/memory/tools.ts:22-24, integrations/pi-agent-plugin/src/memory/tools.ts:200-218
Note
Tool execution checks signal?.aborted at the start of actions such as search, add, and delete_all, throwing an immediate "Cancelled" error if the asynchronous operation is aborted.
Sources: integrations/pi-agent-plugin/src/memory/tools.ts:63, integrations/pi-agent-plugin/src/memory/tools.ts:75, integrations/pi-agent-plugin/src/memory/tools.ts:125
Agent Prompt Cues and Scoping Guidance
The plugin provides system prompt policies via MEMORY_POLICY and explicit prompt guidelines inside tool registrations to instruct the agent on proactive memory storage and proper scope selection.
export const MEMORY_POLICY = `<mem0-memory-policy>
You have persistent semantic memory via the mem0_memory tool, powered by Mem0. Relevant memories may be auto-injected under <mem0-relevant-memories>.
Use mem0_memory with action "search" ${SEARCH_WHEN}.
Be proactive about saving:
- Save important facts, preferences, goals, decisions, lessons learned, identity, relationships, and routines the user shares.
Scope (do not change unless explicitly asked):
- "project" (default): memories for this project — use for all normal queries
- "session": memories from this session only
- "global": all memories across projects — ONLY when the user explicitly asks for cross-project search
Memory persists across sessions and devices via Mem0's cloud.
</mem0-memory-policy>`;Sources: integrations/pi-agent-plugin/src/prompt.ts:3-17
The agent is instructed to use the "project" scope by default for all normal queries, restricting "global" cross-project searches strictly to instances where the user has explicitly requested it. Normal queries omit the scope parameter entirely to let the system fall back to the project default automatically.
"Always use the default project scope unless the user EXPLICITLY asks to search across all projects — only after the user selects /mem0-scope global use scope \"global\"",
"Do NOT pass scope at all for normal queries — omitting it uses the project default automatically",Sources: integrations/pi-agent-plugin/src/memory/tools.ts:154-155
Memory Formatting and Age Representation
Presentation Formats and List Renderers
The Pi agent plugin delegates memory formatting, prose rendering, and age duration calculations directly to shared helpers provided by the core TypeScript agent library TypeScript Agent Core. The local formatting.ts module re-exports formatAge, formatMemoryCompact, formatMemoryList, and groupByCategory alongside the MemoryLike interface type.
export {
formatAge,
formatMemoryCompact,
formatMemoryList,
groupByCategory,
} from "../../../agent-plugin-core/typescript/src/formatting.ts";Sources: integrations/pi-agent-plugin/src/memory/formatting.ts:1-6
Memories are rendered in different presentation structures depending on whether they appear in compact dialog selections, user command feedbacks, or structured multi-item lists. Compact representations format single memories for command prompts, confirmations, and deletion feedback workflows, whereas list formats organize multiple retrieved entries into structured Markdown outputs. When browsing memories via commands like /mem0-tour, groupByCategory sorts and aggregates memories into categorized sections using default custom categories.
import { formatMemoryList, formatMemoryCompact, groupByCategory } from "./memory/formatting.ts";Sources: integrations/pi-agent-plugin/src/commands.ts:7, integrations/pi-agent-plugin/src/commands.ts:92-97
Note
Formatting functions rely on the underlying MemoryLike schema definition to extract properties such as memory content, creation timestamps, and category groupings without coupling directly to specific platform transport schemas.
Sources: integrations/pi-agent-plugin/src/memory/formatting.ts:1-6
Related
On This Page
OverviewExtension Lifecycle and Startup RegistrationInitialization and Configuration BindingUser ID Resolution StrategyClient Setup and Event HandlersSurface Attribution and Request HeadersAttribution Metadata and Stack BoundingRequest Header ApplicationInteractive User Commands and DiagnosticsCommand Registration and Memory ManipulationDeletion and Browsing WorkflowsTool Execution and Agent GuidanceTool Registration and Parameter SchemaExecution Dispatch and Output LimitsAgent Prompt Cues and Scoping GuidanceMemory Formatting and Age RepresentationPresentation Formats and List RenderersRelated