chore(integrations): plugin updates, pi-agent auto-recall, and version bumps (#6011)
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
"name": "mem0",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
|
||||
"version": "0.2.11"
|
||||
"version": "0.2.12"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"name": "mem0",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
|
||||
"version": "0.2.11"
|
||||
"version": "0.2.12"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1732,6 +1732,16 @@ A full-featured command-line interface for Mem0, available in both Python and No
|
||||
<Tabs>
|
||||
<Tab title="Mem0 Plugin">
|
||||
|
||||
<Update label="2026-06-30" description="mem0-plugin v0.2.12">
|
||||
|
||||
**New Features:**
|
||||
- **Query-driven prefetch:** The `UserPromptSubmit` hook now searches Mem0 with the current prompt and injects the top matches, so relevant memories are guaranteed in context instead of relying on the agent to search for them. Opt out with `MEM0_PREFETCH=false`.
|
||||
|
||||
**Improvements:**
|
||||
- **Multi-hop guidance:** The decision rubric now tells the agent to run follow-up searches for multi-part or comparative questions instead of stopping after one. Tool descriptions for every memory tool were sharpened with clearer "what + when to use" guidance.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-25" description="mem0-plugin v0.2.11">
|
||||
|
||||
**Fixes:**
|
||||
@@ -1972,6 +1982,13 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
|
||||
<Tab title="OpenCode">
|
||||
|
||||
<Update label="2026-06-30" description="OpenCode plugin v0.2.1">
|
||||
|
||||
**Improvements:**
|
||||
- **Sharper tool descriptions:** Every memory tool (`get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`, `get_event_status`) now states what it does and when to use it. `search_memories` nudges proactive, multi-hop retrieval and the destructive tools carry explicit irreversible-action warnings.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-22" description="OpenCode plugin v0.2.0">
|
||||
|
||||
**Breaking Changes:**
|
||||
@@ -2038,6 +2055,16 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
|
||||
<Tab title="Antigravity">
|
||||
|
||||
<Update label="2026-06-30" description="Antigravity plugin v0.1.4">
|
||||
|
||||
**New Features:**
|
||||
- **Query-driven prefetch:** The user-prompt hook now searches Mem0 with the current prompt and injects the top matches, so relevant memories are guaranteed in context instead of relying on the agent to search for them. Opt out with `MEM0_PREFETCH=false`.
|
||||
|
||||
**Improvements:**
|
||||
- **Multi-hop guidance:** The decision rubric now tells the agent to run follow-up searches for multi-part or comparative questions instead of stopping after one.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-25" description="Antigravity plugin v0.1.3">
|
||||
|
||||
**Fixes:**
|
||||
@@ -2075,6 +2102,13 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
|
||||
<Tab title="OpenClaw">
|
||||
|
||||
<Update label="2026-06-30" description="openclaw-mem0 v1.0.14">
|
||||
|
||||
**Improvements:**
|
||||
- **Sharper tool descriptions:** Every memory tool (`memory_search`, `memory_get`, `memory_list`, `memory_add`, `memory_update`, `memory_delete`) now states what it does and when to use it. `memory_search` nudges proactive, multi-hop retrieval; `memory_add` nudges proactive saves; and the delete tools carry explicit irreversible-action warnings.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-12" description="openclaw-mem0 v1.0.13">
|
||||
|
||||
**Fixes:**
|
||||
@@ -2321,6 +2355,17 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
|
||||
<Tab title="Pi Agent">
|
||||
|
||||
<Update label="2026-06-30" description="Pi Agent plugin v0.1.3">
|
||||
|
||||
**New Features:**
|
||||
- **Guaranteed retrieval:** Memories relevant to each prompt are now prefetched and injected as a `<mem0-relevant-memories>` block before the agent runs, so context no longer depends on the agent remembering to search. Context injection is on by default; set `contextInjection: false` to disable.
|
||||
|
||||
**Improvements:**
|
||||
- **Multi-hop retrieval:** The memory policy and the `mem0_memory` tool description now push proactive, multi-hop search -- several queries with different phrasings instead of stopping after one.
|
||||
- **Clearer tool schema:** The `mem0_memory` action and parameter descriptions (`action`, `query`, `content`, `memory_id`, `scope`) now explain each action and scope.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-12" description="Pi Agent plugin v0.1.2">
|
||||
|
||||
**Fixes:**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0",
|
||||
"version": "0.2.11",
|
||||
"version": "0.2.12",
|
||||
"description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.",
|
||||
"author": {
|
||||
"name": "Mem0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0",
|
||||
"version": "0.2.11",
|
||||
"version": "0.2.12",
|
||||
"description": "Persistent memory for Codex. Remembers decisions, patterns, and preferences across sessions.",
|
||||
"author": {
|
||||
"name": "Mem0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0",
|
||||
"version": "0.2.11",
|
||||
"version": "0.2.12",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.",
|
||||
"author": {
|
||||
"name": "Mem0",
|
||||
|
||||
@@ -463,7 +463,7 @@ Identity context (resolved at plugin startup):
|
||||
}),
|
||||
|
||||
search_memories: tool({
|
||||
description: "Search through stored memories.",
|
||||
description: "Search stored memories by semantic meaning. Use this proactively before answering when the request may depend on the user's past work, preferences, decisions, or environment -- relevant memories are not always auto-injected. For multi-part or comparative questions, run several searches with different phrasings and combine the results rather than stopping after one (multi-hop).",
|
||||
args: {
|
||||
query: tool.schema.string().describe("Search query"),
|
||||
user_id: tool.schema.string().optional().describe("User ID"),
|
||||
@@ -489,7 +489,7 @@ Identity context (resolved at plugin startup):
|
||||
}),
|
||||
|
||||
get_memories: tool({
|
||||
description: "List all memories in the memory store, optionally filtered.",
|
||||
description: "List or browse stored memories without a search query -- useful for auditing what is remembered or paging through everything in a scope. To find memories relevant to a question, use search_memories instead (it ranks by semantic relevance).",
|
||||
args: {
|
||||
user_id: tool.schema.string().optional().describe("User ID"),
|
||||
app_id: tool.schema.string().optional().describe("App/Project ID"),
|
||||
@@ -513,7 +513,7 @@ Identity context (resolved at plugin startup):
|
||||
}),
|
||||
|
||||
get_memory: tool({
|
||||
description: "Retrieve a specific memory by its ID.",
|
||||
description: "Fetch one memory by its exact ID (e.g. an ID returned by search_memories or get_memories) to read its full content and metadata.",
|
||||
args: {
|
||||
id: tool.schema.string().describe("The ID of the memory to retrieve"),
|
||||
},
|
||||
@@ -525,7 +525,7 @@ Identity context (resolved at plugin startup):
|
||||
}),
|
||||
|
||||
update_memory: tool({
|
||||
description: "Update the content or metadata of a specific memory.",
|
||||
description: "Update an existing memory in place when a stored fact has changed -- requires the memory ID. Preserves the ID and history, so prefer this over deleting and re-adding.",
|
||||
args: {
|
||||
id: tool.schema.string().describe("The ID of the memory to update"),
|
||||
text: tool.schema.string().optional().describe("New text content for the memory"),
|
||||
@@ -542,7 +542,7 @@ Identity context (resolved at plugin startup):
|
||||
}),
|
||||
|
||||
delete_memory: tool({
|
||||
description: "Delete specific memories by their ID.",
|
||||
description: "Delete one or more memories by ID when they are wrong, obsolete, or the user asks to forget them. Irreversible -- only delete what is clearly no longer wanted.",
|
||||
args: {
|
||||
id: tool.schema.string().describe("The ID of the memory to delete"),
|
||||
},
|
||||
@@ -555,7 +555,7 @@ Identity context (resolved at plugin startup):
|
||||
}),
|
||||
|
||||
delete_all_memories: tool({
|
||||
description: "Delete all memories.",
|
||||
description: "Delete ALL memories in the given scope. Destructive and irreversible -- only use when the user explicitly asks to wipe their memory. Never call speculatively.",
|
||||
args: {
|
||||
user_id: tool.schema.string().optional().describe("User ID whose memories to delete"),
|
||||
app_id: tool.schema.string().optional().describe("App ID whose memories to delete"),
|
||||
@@ -577,7 +577,7 @@ Identity context (resolved at plugin startup):
|
||||
}),
|
||||
|
||||
delete_entities: tool({
|
||||
description: "Delete user/agent/app/run entities and all their associated memories.",
|
||||
description: "Delete entire user/agent/app/run entities and every memory attached to them. Destructive and irreversible -- only on explicit user request to remove a whole user, agent, or project.",
|
||||
args: {
|
||||
user_id: tool.schema.string().optional().describe("User ID of the entity to delete"),
|
||||
agent_id: tool.schema.string().optional().describe("Agent ID of the entity to delete"),
|
||||
@@ -597,7 +597,7 @@ Identity context (resolved at plugin startup):
|
||||
}),
|
||||
|
||||
list_entities: tool({
|
||||
description: "List all user/agent/app/run entities.",
|
||||
description: "List the user/agent/app/run entities that have memories. Use to discover which scopes exist before searching, listing, or deleting within a specific one.",
|
||||
args: {
|
||||
page: tool.schema.number().optional().describe("Page number"),
|
||||
page_size: tool.schema.number().optional().describe("Page size"),
|
||||
@@ -613,7 +613,7 @@ Identity context (resolved at plugin startup):
|
||||
}),
|
||||
|
||||
get_event_status: tool({
|
||||
description: "Check the status of an asynchronous memory operation by event_id.",
|
||||
description: "Check whether an asynchronous memory write (add/update/delete) finished, using the event_id that call returned. Poll this when you need to confirm a write was persisted before relying on it.",
|
||||
args: {
|
||||
event_id: tool.schema.string().describe("The ID of the event/async operation to check"),
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/opencode-plugin",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"type": "module",
|
||||
"description": "Mem0 persistent memory plugin for OpenCode — add, search, and manage memories across sessions",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "mem0",
|
||||
"name": "mem0",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"description": "Persistent semantic memory for Antigravity agents. Cross-session, user-level recall via the Mem0 Platform MCP server. 16 slash commands, lifecycle hooks for auto-capture and metadata enforcement.",
|
||||
"author": { "name": "Mem0", "email": "support@mem0.ai" },
|
||||
"publisher": "mem0ai",
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Hook: UserPromptSubmit
|
||||
#
|
||||
# Fires on every user message. Instead of pre-searching mem0 with the
|
||||
# raw prompt, this injects a decision rubric telling the agent when
|
||||
# and how to search itself. The agent has more context than this
|
||||
# script does -- let it decide.
|
||||
# Fires on every user message. Prefetches memories relevant to the current
|
||||
# prompt (so relevant context is guaranteed) and also injects a decision
|
||||
# rubric telling the agent when to search further itself -- including
|
||||
# follow-up searches for multi-part questions. Prefetch can be disabled
|
||||
# with MEM0_PREFETCH=false.
|
||||
#
|
||||
# Input: JSON on stdin (prompt, session_id, cwd, transcript_path)
|
||||
# Output: Decision rubric injected into Claude's context (exit 0)
|
||||
@@ -152,12 +153,37 @@ else:
|
||||
fi
|
||||
fi
|
||||
|
||||
# Query-driven prefetch: search mem0 with the current prompt and inject the top
|
||||
# matches so relevant memories are guaranteed in context, not left for the agent
|
||||
# to fetch. Skipped on resume (handled above with targeted queries) and when
|
||||
# MEM0_PREFETCH=false.
|
||||
if [ -z "$HAS_RESUME" ] && [ "${MEM0_PREFETCH:-true}" != "false" ]; then
|
||||
PREFETCH_RESULTS=$(PYTHONPATH="$SCRIPT_DIR" MEM0_SEARCH_USER="$USER_ID" MEM0_SEARCH_QUERY="$PROMPT" python3 -c "
|
||||
import os, sys
|
||||
sys.path.insert(0, os.environ.get('PYTHONPATH', '.'))
|
||||
from _search import search_memories, format_results_for_context, should_rerank
|
||||
|
||||
api_key = os.environ.get('MEM0_API_KEY', '')
|
||||
user_id = os.environ.get('MEM0_SEARCH_USER', 'default')
|
||||
project_id = os.environ.get('MEM0_PROJECT_ID', 'unknown')
|
||||
query = os.environ.get('MEM0_SEARCH_QUERY', '')
|
||||
|
||||
results = search_memories(api_key, user_id, project_id, query, top_k=5, rerank=should_rerank())
|
||||
if results:
|
||||
print(format_results_for_context(results, heading='Relevant memories (auto-retrieved for this request)'))
|
||||
" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PREFETCH_RESULTS" ]; then
|
||||
_PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}${PREFETCH_RESULTS}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$HAS_REMEMBER" ]; then
|
||||
_PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}Remember intent detected. The /mem0:remember skill auto-classifies, sets confidence=1.0, and stores verbatim."
|
||||
fi
|
||||
|
||||
if [ -z "$RUBRIC_ALREADY_SHOWN" ]; then
|
||||
_PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}Mem0 searches apply when user references past work, decision questions, errors, or non-trivial tasks. Queries use noun-phrases, 2-4 parallel calls with different metadata.type filters, and include user_id + app_id."
|
||||
_PROMPT_CTX="${_PROMPT_CTX:+${_PROMPT_CTX}\n}Mem0 searches apply when user references past work, decision questions, errors, or non-trivial tasks. Queries use noun-phrases, 2-4 parallel calls with different metadata.type filters, and include user_id + app_id. For multi-part or comparative questions, run follow-up searches and combine results before answering -- one search is rarely enough."
|
||||
touch "$RUBRIC_FLAG" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ def _run_hook(prompt: str, env_overrides: dict | None = None, session_id: str =
|
||||
"MEM0_RESOLVED_USER_ID": "testuser",
|
||||
"MEM0_PROJECT_ID": "test-project",
|
||||
"MEM0_BRANCH": "main",
|
||||
"MEM0_PREFETCH": "false",
|
||||
}
|
||||
if env_overrides:
|
||||
env.update(env_overrides)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"id": "openclaw-mem0",
|
||||
"name": "Memory (Mem0)",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform (mem0.ai cloud) or self-hosted open-source. Auto-recall and auto-capture are opt-in (disabled by default). Supports OpenAI, Anthropic, Ollama (fully local), Qdrant, and PGVector providers.",
|
||||
"version": "1.0.11",
|
||||
"version": "1.0.14",
|
||||
"kind": "memory",
|
||||
"skills": ["skills"],
|
||||
"commandAliases": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/openclaw-mem0",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.14",
|
||||
"type": "module",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -11,7 +11,7 @@ export function createMemoryAddTool(deps: ToolDeps) {
|
||||
return {
|
||||
name: "memory_add",
|
||||
label: "Memory Add",
|
||||
description: "Save important information in long-term memory via Mem0. Use for preferences, facts, decisions, and anything worth remembering.",
|
||||
description: "Save important information to long-term memory via Mem0. Do this proactively, without being asked, whenever the user shares a preference, fact, decision, rule, or anything worth recalling in future sessions. Group related facts under one category.",
|
||||
parameters: Type.Object({
|
||||
text: Type.Optional(Type.String({ description: "Single fact to remember" })),
|
||||
facts: Type.Optional(Type.Array(Type.String(), { description: "Array of facts to store. ALL must share the same category." })),
|
||||
|
||||
@@ -8,7 +8,7 @@ export function createMemoryDeleteTool(deps: ToolDeps) {
|
||||
return {
|
||||
name: "memory_delete",
|
||||
label: "Memory Delete",
|
||||
description: "Delete memories. Provide memoryId, query to search-and-delete, or all:true for bulk deletion (requires confirm:true).",
|
||||
description: "Delete memories that are wrong, obsolete, or that the user asks to forget. Provide memoryId, a query to search-and-delete, or all:true for bulk deletion (requires confirm:true). Irreversible -- never delete speculatively or wipe everything without an explicit request.",
|
||||
parameters: Type.Object({
|
||||
memoryId: Type.Optional(Type.String({ description: "Specific memory ID to delete" })),
|
||||
query: Type.Optional(Type.String({ description: "Search query to find and delete" })),
|
||||
|
||||
@@ -7,7 +7,7 @@ export function createMemoryGetTool(deps: ToolDeps) {
|
||||
return {
|
||||
name: "memory_get",
|
||||
label: "Memory Get",
|
||||
description: "Retrieve a specific memory by its ID from Mem0.",
|
||||
description: "Fetch one memory by its exact ID (e.g. an ID returned by memory_search or memory_list) to read its full content and metadata.",
|
||||
parameters: Type.Object({
|
||||
memoryId: Type.String({ description: "The memory ID to retrieve" }),
|
||||
}),
|
||||
|
||||
@@ -8,7 +8,7 @@ export function createMemoryListTool(deps: ToolDeps) {
|
||||
return {
|
||||
name: "memory_list",
|
||||
label: "Memory List",
|
||||
description: "List all stored memories for a user or agent.",
|
||||
description: "List or browse all stored memories for a user or agent without a query -- useful for auditing what is remembered. To find memories relevant to a question, use memory_search instead (it ranks by semantic relevance).",
|
||||
parameters: Type.Object({
|
||||
userId: Type.Optional(Type.String({ description: "User ID (default: configured)" })),
|
||||
agentId: Type.Optional(Type.String({ description: "Agent ID namespace" })),
|
||||
|
||||
@@ -8,7 +8,7 @@ export function createMemorySearchTool(deps: ToolDeps) {
|
||||
return {
|
||||
name: "memory_search",
|
||||
label: "Memory Search",
|
||||
description: "Search through long-term memories stored in Mem0.",
|
||||
description: "Search long-term memories stored in Mem0 by semantic meaning. Use this proactively before answering when the request may depend on the user's past work, preferences, or decisions -- relevant memories are not always already in context. For multi-part or comparative questions, run several searches with different phrasings and combine the results rather than stopping after one (multi-hop).",
|
||||
parameters: Type.Object({
|
||||
query: Type.String({ description: "Search query" }),
|
||||
limit: Type.Optional(Type.Number({ description: `Max results (default: ${cfg.topK})` })),
|
||||
|
||||
@@ -8,7 +8,7 @@ export function createMemoryUpdateTool(deps: ToolDeps) {
|
||||
return {
|
||||
name: "memory_update",
|
||||
label: "Memory Update",
|
||||
description: "Update an existing memory's text in place. Atomic and preserves history.",
|
||||
description: "Update an existing memory's text in place when a stored fact has changed -- requires the memory ID. Atomic and preserves the ID and history, so prefer this over deleting and re-adding.",
|
||||
parameters: Type.Object({
|
||||
memoryId: Type.String({ description: "The memory ID to update" }),
|
||||
text: Type.String({ description: "The new text (replaces old)" }),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/pi-agent-plugin",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"type": "module",
|
||||
"description": "Mem0 memory extension for Pi Agent persistent, scoped, semantic memory across sessions and projects",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -20,7 +20,7 @@ const DEFAULT_CONFIG: Mem0Config = {
|
||||
userId: "",
|
||||
autoCapture: true,
|
||||
defaultScope: "project",
|
||||
contextInjection: false,
|
||||
contextInjection: true,
|
||||
searchThreshold: 0.3,
|
||||
dream: DEFAULT_DREAM,
|
||||
};
|
||||
|
||||
@@ -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("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>`;
|
||||
|
||||
Reference in New Issue
Block a user