chore(integrations): plugin updates, pi-agent auto-recall, and version bumps (#6011)

This commit is contained in:
Kartik
2026-07-01 20:57:32 +05:30
committed by GitHub
parent a36a392cd3
commit 41c8f00851
25 changed files with 215 additions and 56 deletions
@@ -20,7 +20,7 @@ const DEFAULT_CONFIG: Mem0Config = {
userId: "",
autoCapture: true,
defaultScope: "project",
contextInjection: false,
contextInjection: true,
searchThreshold: 0.3,
dream: DEFAULT_DREAM,
};
+32 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { resolveUserId } from "./entry.ts";
import { resolveUserId, buildRecallContext } from "./entry.ts";
describe("resolveUserId", () => {
const originalEnv = { ...process.env };
@@ -32,3 +32,34 @@ describe("resolveUserId", () => {
expect(result.length).toBeGreaterThan(0);
});
});
describe("buildRecallContext", () => {
const search = async () => ({
results: [{ id: "m1", memory: "User prefers pnpm over npm", categories: ["preferences"] }],
});
it("returns empty when disabled", async () => {
expect(await buildRecallContext("which pm?", false, search)).toBe("");
});
it("returns empty for a blank prompt", async () => {
expect(await buildRecallContext(" ", true, search)).toBe("");
});
it("returns empty when no memories match", async () => {
expect(await buildRecallContext("hi", true, async () => ({ results: [] }))).toBe("");
});
it("injects recalled memory text when enabled and matches exist", async () => {
const out = await buildRecallContext("which pm?", true, search);
expect(out).toContain("User prefers pnpm over npm");
expect(out).toContain("mem0-relevant-memories");
});
it("swallows search errors so the turn is never blocked", async () => {
const out = await buildRecallContext("hi", true, async () => {
throw new Error("boom");
});
expect(out).toBe("");
});
});
+35
View File
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import MemoryClient from "mem0ai";
import { loadConfig, CONFIG_DIR } from "./config/index.ts";
import { detectAppId, detectRunId, resolveSearchFilters } from "./memory/scoping.ts";
import { formatMemoryList } from "./memory/formatting.ts";
import { registerMemoryTool } from "./memory/tools.ts";
import { registerCommands } from "./commands.ts";
import { setupAutoCapture } from "./capture/index.ts";
@@ -26,6 +27,31 @@ export function resolveUserId(configUserId: string): string {
try { return os.userInfo().username; } catch { return "default"; }
}
/**
* Build the auto-recall context block for a turn: search memory with the user's
* prompt and format the top matches so they are guaranteed in context instead of
* relying on the agent to call the tool. Best-effort — returns "" when disabled,
* the prompt is blank, nothing matches, or the search fails; it must never block
* the turn.
*/
export async function buildRecallContext(
prompt: string,
enabled: boolean,
search: (query: string) => Promise<{ results?: unknown[] }>,
): Promise<string> {
if (!enabled) return "";
const q = prompt.trim();
if (!q) return "";
try {
const res = await search(q);
const memories = (res.results ?? []) as Parameters<typeof formatMemoryList>[0];
if (memories.length === 0) return "";
return `<mem0-relevant-memories>\nRetrieved automatically for the current request. This is a shallow first pass — search mem0_memory for more if you need it.\n${formatMemoryList(memories)}\n</mem0-relevant-memories>`;
} catch {
return "";
}
}
export default function mem0Extension(pi: ExtensionAPI): void {
const config = loadConfig();
@@ -84,6 +110,15 @@ export default function mem0Extension(pi: ExtensionAPI): void {
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 buildRecallContext(
event.prompt ?? "",
config.contextInjection,
(q) => mem0.search(q, { filters: resolveSearchFilters("project", scopeCtx) }),
);
if (recall) extra += "\n\n" + recall;
if (config.dream.enabled && config.dream.auto && !dreamTriggered && !dreamChecked) {
const gates = checkCheapGates(CONFIG_DIR, config.dream);
if (gates.proceed) {
@@ -138,35 +138,54 @@ export function registerMemoryTool(
name: "mem0_memory",
label: "Mem0 Memory",
description:
"Search, add, update, and manage persistent semantic memories powered by Mem0. Memories persist across sessions and devices. Output is truncated to 200 lines / 50KB.",
"Search, add, update, and manage persistent semantic memories powered by Mem0. Memories persist across sessions and devices. Use action \"search\" proactively -- before answering anything that may depend on what the user told you earlier -- and run multiple searches with different phrasings for multi-part questions. Output is truncated to 200 lines / 50KB.",
promptSnippet: "Semantic memory search and storage via Mem0",
promptGuidelines: [
'Use mem0_memory with action "search" when the user asks about past conversations, preferences, or decisions',
'Use mem0_memory with action "search" proactively whenever the request may depend on the user\'s past work, preferences, decisions, or environment -- not only when they explicitly mention the past',
'For multi-part or comparative questions, run several searches with different phrasings and combine the results before answering -- one search is rarely enough',
'Use mem0_memory with action "add" to save important facts, preferences, goals, decisions, or lessons the user shares',
'Use mem0_memory with action "update" to modify an existing memory — requires memory_id and content. Preserves the memory ID',
"Always use the default project scope unless the user EXPLICITLY asks to search across all projects — only then use scope \"global\"",
"Do NOT pass scope at all for normal queries — omitting it uses the project default automatically",
],
parameters: Type.Object({
action: StringEnum([
"search",
"add",
"get_all",
"update",
"delete",
"delete_all",
] as const),
action: StringEnum(
[
"search",
"add",
"get_all",
"update",
"delete",
"delete_all",
] as const,
{
description:
"Memory operation to run: \"search\" (semantic recall -- use proactively before answering; run several with different phrasings for multi-part questions), \"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 query or memory text" }),
Type.String({
description:
"Search text -- required for action \"search\". Use a focused noun-phrase; for multi-part questions run several searches with different phrasings.",
}),
),
content: Type.Optional(
Type.String({ description: "Memory content to store or updated text" }),
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: "Memory ID for update or delete" }),
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),
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.",
}),
),
}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
+12 -10
View File
@@ -1,16 +1,18 @@
export const MEMORY_POLICY = `<mem0-memory-policy>
You have persistent semantic memory via the mem0_memory tool, powered by Mem0.
You have persistent semantic memory via the mem0_memory tool, powered by Mem0. Relevant memories may be auto-injected under <mem0-relevant-memories>, but that retrieval is shallow — treat it as a starting point, not the full picture.
Memory is scoped to the current project by default. Do not change the scope unless explicitly asked.
- "project" (default): memories for this project — use this for all normal queries
Be proactive about retrieval:
- Search memory BEFORE answering whenever the request could depend on the user's past work, preferences, decisions, environment, or anything they told you earlier — don't wait to be asked.
- Check memory before asking the user something they may have already told you.
- For multi-part, comparative, or "how did we…" questions, run SEVERAL searches with different phrasings and combine the results. One search is rarely enough — keep going until you have what you need (multi-hop).
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 use when the user explicitly asks for cross-project search
When to use memory:
- Search when the user references past conversations, preferences, or decisions
- Save important facts, user preferences, key decisions, and lessons learned
- Check memory before asking the user something they may have already told you
- Save identity information, goals, relationships, and routines the user shares
- "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>`;