Skip to content

Commit 629adb9

Browse files
committed
fix: move memory from user prompt to system prompt
1 parent 3c0083d commit 629adb9

1 file changed

Lines changed: 39 additions & 15 deletions

File tree

apps/memos-local-openclaw/index.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,6 +1779,31 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
17791779
{ name: "network_skill_pull" },
17801780
);
17811781

1782+
// ─── Inject recall context as system message (hidden from chat UI) ───
1783+
// `prependContext` (from before_agent_start) is prepended to user messages and
1784+
// therefore visible in the chat box. To keep injected memories invisible to the
1785+
// user while still feeding them to the model, we stash the recall result in
1786+
// `pendingRecallContext` and inject it as a system-level message via the
1787+
// `before_context_send` hook, which fires synchronously during prompt assembly.
1788+
let pendingRecallContext: string | null = null;
1789+
1790+
api.on("before_context_send", (event: { messages: Array<{ role: string; content: string | unknown }> }) => {
1791+
if (!pendingRecallContext) return;
1792+
const memoryContext = pendingRecallContext;
1793+
pendingRecallContext = null;
1794+
// Insert after the last system message (before the first user/assistant turn).
1795+
// This keeps the static system prompt at the very beginning of the sequence so
1796+
// KV-cache prefixes stay stable across requests — the provider can reuse the
1797+
// cached keys/values for the system prompt even though the memory block changes.
1798+
const firstNonSystemIdx = event.messages.findIndex((m) => m.role !== "system");
1799+
if (firstNonSystemIdx === -1) {
1800+
event.messages.push({ role: "system", content: memoryContext });
1801+
} else {
1802+
event.messages.splice(firstNonSystemIdx, 0, { role: "system", content: memoryContext });
1803+
}
1804+
ctx.log.info(`before_context_send: injected recall context as system message at idx=${firstNonSystemIdx === -1 ? event.messages.length - 1 : firstNonSystemIdx} (${memoryContext.length} chars)`);
1805+
});
1806+
17821807
// ─── Auto-recall: inject relevant memories before agent starts ───
17831808

17841809
api.on("before_agent_start", async (event: { prompt?: string; messages?: unknown[] }, hookCtx?: { agentId?: string; sessionKey?: string }) => {
@@ -1884,7 +1909,8 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
18841909
"\n\nYou SHOULD call `skill_get` to retrieve the full guide before attempting the task.";
18851910
ctx.log.info(`auto-recall-skill (no-memory path): injecting ${topSkills.length} skill(s)`);
18861911
try { store.recordApiLog("skill_search", { type: "auto_recall_skill", query }, JSON.stringify(topSkills), dur, true); } catch { /* best-effort */ }
1887-
return { prependContext: skillContext };
1912+
pendingRecallContext = skillContext;
1913+
return {};
18881914
}
18891915
} catch (err) {
18901916
ctx.log.debug(`auto-recall-skill (no-memory path): failed: ${err}`);
@@ -1893,11 +1919,11 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
18931919

18941920
if (query.length > 50) {
18951921
const noRecallHint =
1896-
"## Memory system — ACTION REQUIRED\n\n" +
1922+
"## Memory system\n\n" +
18971923
"Auto-recall found no results for a long query. " +
1898-
"You MUST call `memory_search` now with a shortened query (2-5 key words) before answering. " +
1899-
"Do NOT skip this step. Do NOT answer without searching first.";
1900-
return { prependContext: noRecallHint };
1924+
"Call `memory_search` with a shortened query (2-5 key words) before answering.";
1925+
pendingRecallContext = noRecallHint;
1926+
return {};
19011927
}
19021928
return;
19031929
}
@@ -1933,7 +1959,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
19331959
}), dur, true);
19341960
if (query.length > 50) {
19351961
const noRecallHint =
1936-
"## Memory system — ACTION REQUIRED\n\n" +
1962+
"## Memory system\n\n" +
19371963
"Auto-recall found no relevant results for a long query. " +
19381964
"You MUST call `memory_search` now with a shortened query (2-5 key words) before answering. " +
19391965
"Do NOT skip this step. Do NOT answer without searching first.";
@@ -1985,11 +2011,9 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
19852011
const tipsText = "\n\nAvailable follow-up tools:\n" + tips.join("\n");
19862012

19872013
const contextParts = [
1988-
"## User's conversation history (from memory system)",
2014+
"## Recalled memories",
19892015
"",
1990-
"IMPORTANT: The following are facts from previous conversations with this user.",
1991-
"You MUST treat these as established knowledge and use them directly when answering.",
1992-
"Do NOT say you don't know or don't have information if the answer is in these memories.",
2016+
"The following facts were retrieved from previous conversations with this user. Treat them as established knowledge.",
19932017
"",
19942018
lines.join("\n\n"),
19952019
];
@@ -2073,18 +2097,18 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
20732097
}), recallDur, true);
20742098
telemetry.trackAutoRecall(filteredHits.length, recallDur);
20752099

2076-
ctx.log.info(`auto-recall: returning prependContext (${context.length} chars), sufficient=${sufficient}, skills=${skillSection ? "yes" : "no"}`);
2100+
ctx.log.info(`auto-recall: stashing recall context for system message injection (${context.length} chars), sufficient=${sufficient}, skills=${skillSection ? "yes" : "no"}`);
20772101

20782102
if (!sufficient) {
20792103
const searchHint =
20802104
"\n\nIf these memories don't fully answer the question, " +
20812105
"call `memory_search` with a shorter or rephrased query to find more.";
2082-
return { prependContext: context + searchHint };
2106+
pendingRecallContext = context + searchHint;
2107+
return {};
20832108
}
20842109

2085-
return {
2086-
prependContext: context,
2087-
};
2110+
pendingRecallContext = context;
2111+
return {};
20882112
} catch (err) {
20892113
const dur = performance.now() - recallT0;
20902114
store.recordToolCall("memory_search", dur, false);

0 commit comments

Comments
 (0)