Skip to main content
doc0blade47/mem0documentation home
Sign in

Pi Agent Plugin

Last updated Oct 11, 2026100% grounded
Relevant source files

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

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.

PrioritySourceConditionFallback / Return Value
1configUserId parameterNon-empty stringReturns configUserId directly
2process.env.USEREnvironment variable is setReturns process.env.USER
3process.env.USERNAMEEnvironment variable is setReturns process.env.USERNAME
4os.userInfo().usernameSystem call succeedsReturns system username string
5Catch block / Final defaultSystem call throws errorReturns "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 the MEMORY_POLICY prompt 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 NameConfiguration RuleValue / Behavior
X-Mem0-SourceSet-once if empty or whitespacePLATFORM_SOURCE ("PI_AGENT")
X-ApplicationSet-once if empty or whitespacePLATFORM_APPLICATION ("pi")
X-Mem0-ClientAppend-only stackmem0-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 NameDescriptionRequired ArgumentsUnderlying Action
/mem0-rememberStore a memory verbatim (no inference)Text stringmem0.add([{ role: "user", content: text }], { infer: false, ... })
/mem0-forgetDelete memories matching a natural language queryNatural language querysearchMemories(query) followed by mem0.delete(target.id)
/mem0-searchSemantic search across memoriesSearch querymem0.search(query, { filters, threshold, topK: 10, rerank: true })
/mem0-tourBrowse all memories grouped by categoryOptional 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.

Sources: integrations/pi-agent-plugin/src/commands.ts:32-46

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 NameTypeAllowed Values / Description
actionStringEnum"search", "add", "get_all", "update", "delete", "delete_all"
queryStringSearch text required for action "search"
contentStringMemory text required for actions "add" and "update"
memory_idStringTarget memory ID required for actions "update" and "delete"
scopeStringEnum"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 ConstantThreshold ValueEnforcement Behavior
MAX_OUTPUT_LINES200 linesTruncates lists exceeding line count
MAX_OUTPUT_BYTES50,000 bytesTruncates 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

Was this page helpful?