fix(active-memory): use bundled recall tool

Fixes #73502.

Active Memory now allows its hidden recall sub-agent to use both bundled memory tool contracts: memory_recall for memory-lancedb and memory_search/memory_get for memory-core. The prompt prefers memory_recall when available and falls back to the legacy tool pair when that is the active backend surface.

Also updates Active Memory docs, QA mock fixtures, and debug parsing compatibility for the two recall paths.
This commit is contained in:
Tak Hoffman
2026-04-28 09:03:47 -05:00
committed by GitHub
parent dd643c82b5
commit f256eeba43
6 changed files with 63 additions and 52 deletions

View File

@@ -1015,9 +1015,14 @@ describe("active-memory plugin", () => {
expect(runParams?.prompt).toContain(
"You receive conversation context, including the user's latest message.",
);
expect(runParams?.prompt).toContain("Use only memory_search and memory_get.");
expect(runParams?.prompt).toContain("Use only the available memory tools.");
expect(runParams?.prompt).toContain("Prefer memory_recall when available.");
expect(runParams?.prompt).toContain(
"When searching for preference or habit recall, use a permissive memory_search threshold before deciding that no useful memory exists.",
"If memory_recall is unavailable, use memory_search and memory_get.",
);
expect(runParams?.toolsAllow).toEqual(["memory_recall", "memory_search", "memory_get"]);
expect(runParams?.prompt).toContain(
"When searching for preference or habit recall, use a permissive recall limit or memory_search threshold before deciding that no useful memory exists.",
);
expect(runParams?.prompt).toContain(
"If the user is directly asking about favorites, preferences, habits, routines, or personal facts, treat that as a strong recall signal.",

View File

@@ -848,8 +848,10 @@ function buildRecallPrompt(params: {
"Another model is preparing the final user-facing answer.",
"Your job is to search memory and return only the most relevant memory context for that model.",
"You receive conversation context, including the user's latest message.",
"Use only memory_search and memory_get.",
"When searching for preference or habit recall, use a permissive memory_search threshold before deciding that no useful memory exists.",
"Use only the available memory tools.",
"Prefer memory_recall when available.",
"If memory_recall is unavailable, use memory_search and memory_get.",
"When searching for preference or habit recall, use a permissive recall limit or memory_search threshold before deciding that no useful memory exists.",
"Do not answer the user directly.",
`Prompt style: ${params.config.promptStyle}.`,
...buildPromptStyleLines(params.config.promptStyle),
@@ -1448,14 +1450,18 @@ function extractActiveMemorySearchDebugFromSessionRecord(
const record = asRecord(value);
const nestedMessage = asRecord(record?.message);
const topLevelMessage =
record?.role === "toolResult" || record?.toolName === "memory_search" ? record : undefined;
record?.role === "toolResult" ||
record?.toolName === "memory_search" ||
record?.toolName === "memory_recall"
? record
: undefined;
const message = nestedMessage ?? topLevelMessage;
if (!message) {
return undefined;
}
const role = normalizeOptionalString(message.role);
const toolName = normalizeOptionalString(message.toolName);
if (role !== "toolResult" || toolName !== "memory_search") {
if (role !== "toolResult" || (toolName !== "memory_search" && toolName !== "memory_recall")) {
return undefined;
}
const details = asRecord(message.details);
@@ -2072,7 +2078,7 @@ async function runRecallSubagent(params: {
timeoutMs: params.config.timeoutMs,
runId: subagentSessionId,
trigger: "manual",
toolsAllow: ["memory_search", "memory_get"],
toolsAllow: ["memory_recall", "memory_search", "memory_get"],
disableMessageTool: true,
bootstrapContextMode: "lightweight",
verboseLevel: "off",