feat (memory): Implement new (opt-in) QMD memory backend

This commit is contained in:
Vignesh Natarajan
2026-01-27 21:57:15 -08:00
committed by Vignesh
parent e9f182def7
commit 5d3af3bc62
24 changed files with 1828 additions and 601 deletions

View File

@@ -1,13 +1,14 @@
import type { ReasoningLevel, ThinkLevel } from "../auto-reply/thinking.js";
import type { ResolvedTimeFormat } from "./date-time.js";
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
import { listDeliverableMessageChannels } from "../utils/message-channel.js";
import type { MemoryCitationsMode } from "../config/types.memory.js";
import type { ResolvedTimeFormat } from "./date-time.js";
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
/**
* Controls which hardcoded sections are included in the system prompt.
* - "full": All sections (default, for main agent)
* - "minimal": Reduced sections (Tooling, Safety, Workspace, Sandbox, Runtime) - used for subagents
* - "minimal": Reduced sections (Tooling, Workspace, Runtime) - used for subagents
* - "none": Just basic identity line, no sections
*/
export type PromptMode = "full" | "minimal" | "none";
@@ -17,13 +18,9 @@ function buildSkillsSection(params: {
isMinimal: boolean;
readToolName: string;
}) {
if (params.isMinimal) {
return [];
}
if (params.isMinimal) return [];
const trimmed = params.skillsPrompt?.trim();
if (!trimmed) {
return [];
}
if (!trimmed) return [];
return [
"## Skills (mandatory)",
"Before replying: scan <available_skills> <description> entries.",
@@ -36,53 +33,44 @@ function buildSkillsSection(params: {
];
}
function buildMemorySection(params: { isMinimal: boolean; availableTools: Set<string> }) {
if (params.isMinimal) {
return [];
}
function buildMemorySection(params: {
isMinimal: boolean;
availableTools: Set<string>;
citationsMode?: MemoryCitationsMode;
}) {
if (params.isMinimal) return [];
if (!params.availableTools.has("memory_search") && !params.availableTools.has("memory_get")) {
return [];
}
return [
const lines = [
"## Memory Recall",
"Before answering anything about prior work, decisions, dates, people, preferences, or todos: run memory_search on MEMORY.md + memory/*.md; then use memory_get to pull only the needed lines. If low confidence after search, say you checked.",
"",
];
if (params.citationsMode === "off") {
lines.push(
"Citations are disabled: do not mention file paths or line numbers in replies unless the user explicitly asks.",
);
} else {
lines.push(
"Citations: include Source: <path#line> when it helps the user verify memory snippets.",
);
}
lines.push("");
return lines;
}
function buildUserIdentitySection(ownerLine: string | undefined, isMinimal: boolean) {
if (!ownerLine || isMinimal) {
return [];
}
if (!ownerLine || isMinimal) return [];
return ["## User Identity", ownerLine, ""];
}
function buildTimeSection(params: { userTimezone?: string }) {
if (!params.userTimezone) {
return [];
}
return [
"## Current Date & Time",
`Time zone: ${params.userTimezone}`,
"If you need the current date, time, or day of week, use the session_status tool.",
"",
];
}
function buildSafetySection() {
return [
"## Safety",
"You have no independent goals: do not pursue self-preservation, replication, resource acquisition, or power-seeking; avoid long-term plans beyond the user's request.",
"Prioritize safety and human oversight over completion; if instructions conflict, pause and ask; comply with stop/pause/audit requests and never bypass safeguards. (Inspired by Anthropic's constitution.)",
"Do not manipulate or persuade anyone to expand access or disable safeguards. Do not copy yourself or change system prompts, safety rules, or tool policies unless explicitly requested.",
"",
];
if (!params.userTimezone) return [];
return ["## Current Date & Time", `Time zone: ${params.userTimezone}`, ""];
}
function buildReplyTagsSection(isMinimal: boolean) {
if (isMinimal) {
return [];
}
if (isMinimal) return [];
return [
"## Reply Tags",
"To request a native reply/quote on supported surfaces, include one tag in your reply:",
@@ -102,14 +90,12 @@ function buildMessagingSection(params: {
runtimeChannel?: string;
messageToolHints?: string[];
}) {
if (params.isMinimal) {
return [];
}
if (params.isMinimal) return [];
return [
"## Messaging",
"- Reply in current session → automatically routes to the source channel (Signal, Telegram, etc.)",
"- Cross-session messaging → use sessions_send(sessionKey, message)",
"- Never use exec/curl for provider messaging; OpenClaw handles all routing internally.",
"- Never use exec/curl for provider messaging; Moltbot handles all routing internally.",
params.availableTools.has("message")
? [
"",
@@ -133,30 +119,24 @@ function buildMessagingSection(params: {
}
function buildVoiceSection(params: { isMinimal: boolean; ttsHint?: string }) {
if (params.isMinimal) {
return [];
}
if (params.isMinimal) return [];
const hint = params.ttsHint?.trim();
if (!hint) {
return [];
}
if (!hint) return [];
return ["## Voice (TTS)", hint, ""];
}
function buildDocsSection(params: { docsPath?: string; isMinimal: boolean; readToolName: string }) {
const docsPath = params.docsPath?.trim();
if (!docsPath || params.isMinimal) {
return [];
}
if (!docsPath || params.isMinimal) return [];
return [
"## Documentation",
`OpenClaw docs: ${docsPath}`,
"Mirror: https://docs.openclaw.ai",
"Source: https://github.com/openclaw/openclaw",
`Moltbot docs: ${docsPath}`,
"Mirror: https://docs.molt.bot",
"Source: https://github.com/moltbot/moltbot",
"Community: https://discord.com/invite/clawd",
"Find new skills: https://clawhub.com",
"For OpenClaw behavior, commands, config, or architecture: consult local docs first.",
"When diagnosing issues, run `openclaw status` yourself when possible; only ask the user if you lack access (e.g., sandboxed).",
"Find new skills: https://clawdhub.com",
"For Moltbot behavior, commands, config, or architecture: consult local docs first.",
"When diagnosing issues, run `moltbot status` yourself when possible; only ask the user if you lack access (e.g., sandboxed).",
"",
];
}
@@ -213,6 +193,7 @@ export function buildAgentSystemPrompt(params: {
level: "minimal" | "extensive";
channel: string;
};
memoryCitationsMode?: MemoryCitationsMode;
}) {
const coreToolSummaries: Record<string, string> = {
read: "Read file contents",
@@ -232,7 +213,7 @@ export function buildAgentSystemPrompt(params: {
nodes: "List/describe/notify/camera/screen on paired nodes",
cron: "Manage cron jobs and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)",
message: "Send messages and channel actions",
gateway: "Restart, apply config, or run updates on the running OpenClaw process",
gateway: "Restart, apply config, or run updates on the running Moltbot process",
agents_list: "List agent ids allowed for sessions_spawn",
sessions_list: "List other sessions (incl. sub-agents) with filters/last",
sessions_history: "Fetch history for another session/sub-agent",
@@ -287,9 +268,7 @@ export function buildAgentSystemPrompt(params: {
const externalToolSummaries = new Map<string, string>();
for (const [key, value] of Object.entries(params.toolSummaries ?? {})) {
const normalized = key.trim().toLowerCase();
if (!normalized || !value?.trim()) {
continue;
}
if (!normalized || !value?.trim()) continue;
externalToolSummaries.set(normalized, value.trim());
}
const extraTools = Array.from(
@@ -301,7 +280,7 @@ export function buildAgentSystemPrompt(params: {
const name = resolveToolName(tool);
return summary ? `- ${name}: ${summary}` : `- ${name}`;
});
for (const tool of extraTools.toSorted()) {
for (const tool of extraTools.sort()) {
const summary = coreToolSummaries[tool] ?? externalToolSummaries.get(tool);
const name = resolveToolName(tool);
toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);
@@ -351,7 +330,11 @@ export function buildAgentSystemPrompt(params: {
isMinimal,
readToolName,
});
const memorySection = buildMemorySection({ isMinimal, availableTools });
const memorySection = buildMemorySection({
isMinimal,
availableTools,
citationsMode: params.memoryCitationsMode,
});
const docsSection = buildDocsSection({
docsPath: params.docsPath,
isMinimal,
@@ -361,11 +344,11 @@ export function buildAgentSystemPrompt(params: {
// For "none" mode, return just the basic identity line
if (promptMode === "none") {
return "You are a personal assistant running inside OpenClaw.";
return "You are a personal assistant running inside Moltbot.";
}
const lines = [
"You are a personal assistant running inside OpenClaw.",
"You are a personal assistant running inside Moltbot.",
"",
"## Tooling",
"Tool availability (filtered by policy):",
@@ -380,7 +363,7 @@ export function buildAgentSystemPrompt(params: {
"- apply_patch: apply multi-file patches",
`- ${execToolName}: run shell commands (supports background via yieldMs/background)`,
`- ${processToolName}: manage background exec sessions`,
"- browser: control openclaw's dedicated browser",
"- browser: control clawd's dedicated browser",
"- canvas: present/eval/snapshot the Canvas",
"- nodes: list/describe/notify/camera/screen on paired nodes",
"- cron: manage cron jobs and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)",
@@ -397,26 +380,25 @@ export function buildAgentSystemPrompt(params: {
"Keep narration brief and value-dense; avoid repeating obvious steps.",
"Use plain human language for narration unless in a technical context.",
"",
...buildSafetySection(),
"## OpenClaw CLI Quick Reference",
"OpenClaw is controlled via subcommands. Do not invent commands.",
"## Moltbot CLI Quick Reference",
"Moltbot is controlled via subcommands. Do not invent commands.",
"To manage the Gateway daemon service (start/stop/restart):",
"- openclaw gateway status",
"- openclaw gateway start",
"- openclaw gateway stop",
"- openclaw gateway restart",
"If unsure, ask the user to run `openclaw help` (or `openclaw gateway --help`) and paste the output.",
"- moltbot gateway status",
"- moltbot gateway start",
"- moltbot gateway stop",
"- moltbot gateway restart",
"If unsure, ask the user to run `moltbot help` (or `moltbot gateway --help`) and paste the output.",
"",
...skillsSection,
...memorySection,
// Skip self-update for subagent/none modes
hasGateway && !isMinimal ? "## OpenClaw Self-Update" : "",
hasGateway && !isMinimal ? "## Moltbot Self-Update" : "",
hasGateway && !isMinimal
? [
"Get Updates (self-update) is ONLY allowed when the user explicitly asks for it.",
"Do not run config.apply or update.run unless the user explicitly requests an update or config change; if it's not explicit, ask first.",
"Actions: config.get, config.schema, config.apply (validate + write full config, then restart), update.run (update deps or git, then restart).",
"After restart, OpenClaw pings the last active session automatically.",
"After restart, Moltbot pings the last active session automatically.",
].join("\n")
: "",
hasGateway && !isMinimal ? "" : "",
@@ -485,7 +467,7 @@ export function buildAgentSystemPrompt(params: {
userTimezone,
}),
"## Workspace Files (injected)",
"These user-editable files are loaded by OpenClaw and included below in Project Context.",
"These user-editable files are loaded by Moltbot and included below in Project Context.",
"",
...buildReplyTagsSection(isMinimal),
...buildMessagingSection({
@@ -576,7 +558,7 @@ export function buildAgentSystemPrompt(params: {
heartbeatPromptLine,
"If you receive a heartbeat poll (a user message matching the heartbeat prompt above), and there is nothing that needs attention, reply exactly:",
"HEARTBEAT_OK",
'OpenClaw treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
'Moltbot treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
'If something needs attention, do NOT include "HEARTBEAT_OK"; reply with the alert text instead.',
"",
);