import type { Command } from "commander"; import { formatDocsLink, formatHelpExamples, theme, } from "openclaw/plugin-sdk/memory-core-host-runtime-cli"; import type { MemoryCommandOptions, MemoryPromoteCommandOptions, MemoryPromoteExplainOptions, MemoryRemBackfillOptions, MemoryRemHarnessOptions, MemorySearchCommandOptions, } from "./cli.types.js"; import { DEFAULT_PROMOTION_MIN_RECALL_COUNT, DEFAULT_PROMOTION_MIN_SCORE, DEFAULT_PROMOTION_MIN_UNIQUE_QUERIES, } from "./short-term-promotion.js"; type MemoryCliRuntime = typeof import("./cli.runtime.js"); let memoryCliRuntimePromise: Promise | null = null; async function loadMemoryCliRuntime(): Promise { memoryCliRuntimePromise ??= import("./cli.runtime.js"); return await memoryCliRuntimePromise; } export async function runMemoryStatus(opts: MemoryCommandOptions) { const runtime = await loadMemoryCliRuntime(); await runtime.runMemoryStatus(opts); } async function runMemoryIndex(opts: MemoryCommandOptions) { const runtime = await loadMemoryCliRuntime(); await runtime.runMemoryIndex(opts); } async function runMemorySearch(queryArg: string | undefined, opts: MemorySearchCommandOptions) { const runtime = await loadMemoryCliRuntime(); await runtime.runMemorySearch(queryArg, opts); } async function runMemoryPromote(opts: MemoryPromoteCommandOptions) { const runtime = await loadMemoryCliRuntime(); await runtime.runMemoryPromote(opts); } async function runMemoryPromoteExplain( selectorArg: string | undefined, opts: MemoryPromoteExplainOptions, ) { const runtime = await loadMemoryCliRuntime(); await runtime.runMemoryPromoteExplain(selectorArg, opts); } async function runMemoryRemHarness(opts: MemoryRemHarnessOptions) { const runtime = await loadMemoryCliRuntime(); await runtime.runMemoryRemHarness(opts); } async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) { const runtime = await loadMemoryCliRuntime(); await runtime.runMemoryRemBackfill(opts); } export function registerMemoryCli(program: Command) { const memory = program .command("memory") .description("Search, inspect, and reindex memory files") .addHelpText( "after", () => `\n${theme.heading("Examples:")}\n${formatHelpExamples([ ["openclaw memory status", "Show index and provider status."], [ "openclaw memory status --fix", "Repair stale recall locks and normalize promotion metadata.", ], ["openclaw memory status --deep", "Probe embedding provider readiness."], ["openclaw memory index --force", "Force a full reindex."], ['openclaw memory search "meeting notes"', "Quick search using positional query."], [ 'openclaw memory search --query "deployment" --max-results 20', "Limit results for focused troubleshooting.", ], [ `openclaw memory promote --limit 10 --min-score ${DEFAULT_PROMOTION_MIN_SCORE}`, "Review weighted short-term candidates for long-term memory.", ], [ "openclaw memory promote --apply", "Append top-ranked short-term candidates into MEMORY.md.", ], [ 'openclaw memory promote-explain "router vlan"', "Explain why a specific candidate would or would not promote.", ], [ "openclaw memory rem-harness --json", "Preview REM reflections, candidate truths, and deep promotion output.", ], [ "openclaw memory rem-backfill --path ./memory", "Write grounded historical REM entries into DREAMS.md for UI review.", ], [ "openclaw memory rem-backfill --path ./memory --stage-short-term", "Also seed durable grounded candidates into the live short-term promotion store.", ], ["openclaw memory status --json", "Output machine-readable JSON (good for scripts)."], ])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/memory", "docs.openclaw.ai/cli/memory")}\n`, ); memory .command("status") .description("Show memory search index status") .option("--agent ", "Agent id (default: default agent)") .option("--json", "Print JSON") .option("--deep", "Probe embedding provider availability") .option("--index", "Reindex if dirty (implies --deep)") .option("--fix", "Repair stale recall locks and normalize promotion metadata") .option("--verbose", "Verbose logging", false) .action(async (opts: MemoryCommandOptions & { force?: boolean }) => { await runMemoryStatus(opts); }); memory .command("index") .description("Reindex memory files") .option("--agent ", "Agent id (default: default agent)") .option("--force", "Force full reindex", false) .option("--verbose", "Verbose logging", false) .action(async (opts: MemoryCommandOptions) => { await runMemoryIndex(opts); }); memory .command("search") .description("Search memory files") .argument("[query]", "Search query") .option("--query ", "Search query (alternative to positional argument)") .option("--agent ", "Agent id (default: default agent)") .option("--max-results ", "Max results", (value: string) => Number(value)) .option("--min-score ", "Minimum score", (value: string) => Number(value)) .option("--json", "Print JSON") .action(async (queryArg: string | undefined, opts: MemorySearchCommandOptions) => { await runMemorySearch(queryArg, opts); }); memory .command("promote") .description("Rank short-term recalls and optionally append top entries to MEMORY.md") .option("--agent ", "Agent id (default: default agent)") .option("--limit ", "Max candidates", (value: string) => Number(value)) .option( "--min-score ", `Minimum weighted score (default: ${DEFAULT_PROMOTION_MIN_SCORE})`, (value: string) => Number(value), ) .option( "--min-recall-count ", `Minimum recall count (default: ${DEFAULT_PROMOTION_MIN_RECALL_COUNT})`, (value: string) => Number(value), ) .option( "--min-unique-queries ", `Minimum distinct query count (default: ${DEFAULT_PROMOTION_MIN_UNIQUE_QUERIES})`, (value: string) => Number(value), ) .option("--apply", "Append selected candidates to MEMORY.md", false) .option("--include-promoted", "Include already promoted candidates", false) .option("--json", "Print JSON") .action(async (opts: MemoryPromoteCommandOptions) => { await runMemoryPromote(opts); }); memory .command("promote-explain") .description("Explain a specific promotion candidate and its score breakdown") .argument("", "Candidate key, path fragment, or snippet fragment") .option("--agent ", "Agent id (default: default agent)") .option("--include-promoted", "Include already promoted candidates", false) .option("--json", "Print JSON") .action(async (selectorArg: string | undefined, opts: MemoryPromoteExplainOptions) => { await runMemoryPromoteExplain(selectorArg, opts); }); memory .command("rem-harness") .description("Preview REM reflections, candidate truths, and deep promotions without writing") .option("--agent ", "Agent id (default: default agent)") .option("--path ", "Seed the harness from historical daily memory file(s)") .option("--grounded", "Also render a grounded day-level REM preview") .option("--include-promoted", "Include already promoted deep candidates", false) .option("--json", "Print JSON") .action(async (opts: MemoryRemHarnessOptions) => { await runMemoryRemHarness(opts); }); memory .command("rem-backfill") .description("Write grounded historical REM summaries into DREAMS.md for UI review") .option("--agent ", "Agent id (default: default agent)") .option("--path ", "Historical daily memory file(s) or directory") .option("--rollback", "Remove previously written grounded REM backfill entries", false) .option( "--stage-short-term", "Also seed grounded durable candidates into the short-term promotion store", false, ) .option( "--rollback-short-term", "Remove previously seeded grounded short-term candidates", false, ) .option("--json", "Print JSON") .action(async (opts: MemoryRemBackfillOptions) => { await runMemoryRemBackfill(opts); }); }