fix(agent-plugins): match memo prompts and tool descriptions, release 0.3.3 (#7420)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ This directory is the single source of shared memory behavior for Mem0 coding-ag
|
||||
integrations/
|
||||
├── agent-plugin-core/ # Shared source; never installed as a plugin
|
||||
│ ├── python/ # Claude-derived capture, recall, MCP, scoping, and telemetry
|
||||
│ ├── typescript/ # Shared lifecycle, formatting, identity, scoping, and telemetry
|
||||
│ ├── typescript/ # Shared lifecycle, search prompts, formatting, identity, scoping, and telemetry
|
||||
│ ├── skills/ # The only source for the six generated memory skills
|
||||
│ ├── build/ # Bundle builder, schemas, and validation
|
||||
│ ├── conformance/ # One offline/live verification entry point
|
||||
@@ -45,7 +45,7 @@ New Git repository writes use a hashed remote identity for shared `agent_id`. Se
|
||||
|
||||
Captured prompts and responses preserve their full text after secret redaction. Python extraction splits oversized input across requests without dropping message text. The session-end worker flushes the conversation already collected by hooks without adding the final answer again. Search queries, retrieved context, and tool evidence have separate limits.
|
||||
|
||||
TypeScript hosts reuse redaction and lifecycle utilities but retain their own tools, scopes, and capture events. They do not inherit the Python `repo`/`dir`/`mine` contract or its background batching. OpenCode captures selected user prompts; Pi and DeepSeek capture completed conversation turns; OpenClaw selects recent messages and earlier summaries, then filters noise. Removing message-length truncation does not turn these integrations into complete transcript archives.
|
||||
TypeScript hosts reuse redaction, lifecycle utilities, and the search prompts in `typescript/src/prompts.ts`, which a test keeps identical to the Python core. They retain their own tools, scopes, and capture events. They do not inherit the Python `repo`/`dir`/`mine` contract or its background batching. OpenCode captures selected user prompts; Pi and DeepSeek capture completed conversation turns; OpenClaw selects recent messages and earlier summaries, then filters noise. Removing message-length truncation does not turn these integrations into complete transcript archives.
|
||||
|
||||
For installation, follow the host guides: [Claude Code](../../docs/integrations/claude-code.mdx), [Cursor](../../docs/integrations/cursor.mdx), [Codex](../../docs/integrations/codex.mdx), [Kimi](../../docs/integrations/kimi.mdx), and [Antigravity](../../docs/integrations/antigravity.mdx).
|
||||
|
||||
|
||||
@@ -21,16 +21,9 @@ from memory_core import (
|
||||
PROTOCOL_VERSION = "2024-11-05"
|
||||
TOOL_NAME = "search_memories"
|
||||
TOOL_DESCRIPTION = (
|
||||
"Search memories from earlier work in this repository. ALWAYS call this "
|
||||
"tool before answering anything that could depend on prior context: the "
|
||||
"user's preferences, facts about this codebase, history, people, projects, "
|
||||
"or earlier decisions. Do not rely on the chat window alone. The "
|
||||
"repository's memory is shared by everyone who works in it and includes "
|
||||
"what it took to run, test, or build here, so search before assuming an "
|
||||
"invocation works. The scope argument changes what is searched: 'repo' "
|
||||
"(default) is the whole repository's shared memory plus your own "
|
||||
"preferences, 'dir' narrows the shared part to the directory you are "
|
||||
"working in, and 'mine' is your preferences alone."
|
||||
"Search memories from earlier work in this repository. Use it before "
|
||||
"repeating investigation or when earlier decisions, fixes, commands, or "
|
||||
"results may help."
|
||||
)
|
||||
TOOL_SCHEMA = {
|
||||
"type": "object",
|
||||
|
||||
@@ -29,7 +29,7 @@ from typing import Any, Iterable
|
||||
import telemetry
|
||||
|
||||
DEFAULT_API_URL = "https://api.mem0.ai"
|
||||
PLUGIN_VERSION = "0.3.2"
|
||||
PLUGIN_VERSION = "0.3.3"
|
||||
|
||||
_harness_name: str = "generic"
|
||||
_harness_env_prefix: str = "MEM0_PLUGIN"
|
||||
@@ -71,15 +71,13 @@ MAX_FLUSH_ATTEMPTS = 5
|
||||
FORGET_PAGE_SIZE = 100
|
||||
FORGET_MAX_PAGES = 50
|
||||
|
||||
PROJECT_MEMORY_INSTRUCTIONS = """Save concise repository facts that will help anyone with future coding work in this repository.
|
||||
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.
|
||||
|
||||
A command that failed and was then made to work should produce one memory naming the failing invocation, the error it returned, and the invocation that succeeded. Do not save one-off errors caused by an edit still in progress, transient network failures, or anything a rerun would fix on its own.
|
||||
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.
|
||||
|
||||
Use the current 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 save personal preferences. Do not save a memory that only states which repository, branch, or directory the session worked in. Do not include test results, documentation updates, release notes, or temporary state.
|
||||
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."""
|
||||
|
||||
|
||||
@@ -12,11 +12,9 @@ Call `search_memories` with the user's question. Treat `--top-k`, `--category`,
|
||||
query.
|
||||
|
||||
Omit `top_k` to use Mem0's configured default. Omit `category` to search every
|
||||
category; a category is a best-effort label Mem0 assigned when it saved the
|
||||
memory, so if a category search misses, repeat it without the category. Omit
|
||||
`scope` to use the configured default, normally `repo`: this repository's
|
||||
shared memory, which everyone who works in it contributes to, plus your own
|
||||
preferences.
|
||||
category. Omit `scope` to use the configured default, normally `repo`: this
|
||||
repository's shared memory, which everyone who works in it contributes to,
|
||||
plus your own preferences.
|
||||
|
||||
Pass `scope` when the question needs something else: `dir` to narrow the
|
||||
shared memory to the directory you are working in (a package inside a
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { MemoryLike } from "./formatting.ts";
|
||||
import { formatMemoryCompact } from "./formatting.ts";
|
||||
import { RECALL_HEADING } from "./prompts.ts";
|
||||
|
||||
const MAX_RECALL_QUERY_CHARS = 6_000;
|
||||
export const DEFAULT_MAX_CONTEXT_CHARS = 4_000;
|
||||
@@ -70,12 +71,14 @@ export function extractConversation(
|
||||
}
|
||||
|
||||
interface RecallOptions {
|
||||
heading?: string;
|
||||
maxChars?: number;
|
||||
seenIds?: Set<string>;
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
interface MemoryLifecycleOptions {
|
||||
recallHeading?: string;
|
||||
maxContextChars?: number;
|
||||
recallTimeoutMs?: number;
|
||||
}
|
||||
@@ -109,6 +112,7 @@ class MemoryLifecycle {
|
||||
search: (query: string) => Promise<{ results?: unknown[] }>,
|
||||
): Promise<string> {
|
||||
return buildRecallContext(prompt, enabled, search, {
|
||||
heading: this.#options.recallHeading,
|
||||
maxChars: this.#options.maxContextChars,
|
||||
seenIds: this.#seenMemoryIds,
|
||||
timeoutMs: this.#options.recallTimeoutMs,
|
||||
@@ -148,8 +152,7 @@ export async function buildRecallContext(
|
||||
const unseen = memories.filter((memory) => !options.seenIds?.has(memory.id));
|
||||
if (!unseen.length) return "";
|
||||
|
||||
const prefix =
|
||||
"<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";
|
||||
const prefix = `<mem0-relevant-memories>\n${options.heading ?? RECALL_HEADING}\n`;
|
||||
const suffix = "\n</mem0-relevant-memories>";
|
||||
const maxChars = options.maxChars ?? DEFAULT_MAX_CONTEXT_CHARS;
|
||||
const lines: string[] = [];
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export const SEARCH_WHEN =
|
||||
"before repeating investigation or when earlier decisions, fixes, commands, or results may help";
|
||||
|
||||
export const SEARCH_TOOL_DESCRIPTION = `Search memories from earlier work in this repository. Use it ${SEARCH_WHEN}.`;
|
||||
export const SEARCH_QUERY_DESCRIPTION = "A direct question about earlier work in this repository.";
|
||||
export const RECALL_HEADING = "Mem0 found these relevant memories from earlier work in this repository:";
|
||||
|
||||
export const USER_SEARCH_TOOL_DESCRIPTION = `Search memories from earlier work. Use it ${SEARCH_WHEN}.`;
|
||||
export const USER_SEARCH_QUERY_DESCRIPTION = "A direct question about earlier work.";
|
||||
export const USER_RECALL_HEADING = "Mem0 found these relevant memories from earlier work:";
|
||||
@@ -0,0 +1,31 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildRecallContext } from "../src/lifecycle.ts";
|
||||
import {
|
||||
RECALL_HEADING,
|
||||
SEARCH_QUERY_DESCRIPTION,
|
||||
SEARCH_TOOL_DESCRIPTION,
|
||||
USER_RECALL_HEADING,
|
||||
} from "../src/prompts.ts";
|
||||
|
||||
const pythonSource = (name: string) =>
|
||||
readFileSync(new URL(`../../python/${name}`, import.meta.url), "utf8").replace(/"\s*\n\s*"/g, "");
|
||||
|
||||
test("search prompts match the Python core", () => {
|
||||
assert.ok(pythonSource("mcp_server.py").includes(SEARCH_TOOL_DESCRIPTION));
|
||||
assert.ok(pythonSource("mcp_server.py").includes(SEARCH_QUERY_DESCRIPTION));
|
||||
assert.ok(pythonSource("hook_runner.py").includes(RECALL_HEADING));
|
||||
});
|
||||
|
||||
test("recall context uses the repository heading unless the host overrides it", async () => {
|
||||
const search = async () => ({ results: [{ id: "m1", memory: "Use pnpm" }] });
|
||||
|
||||
assert.ok((await buildRecallContext("package manager", true, search)).includes(RECALL_HEADING));
|
||||
assert.ok(
|
||||
(await buildRecallContext("package manager", true, search, { heading: USER_RECALL_HEADING })).includes(
|
||||
USER_RECALL_HEADING,
|
||||
),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user