From c11e7a742049fce189a01c720a64f0347cf8ae79 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 5 Apr 2026 21:40:56 +0100 Subject: [PATCH] feat(memory-wiki): add prompt supplement integration --- .../.generated/plugin-sdk-api-baseline.sha256 | 4 +- docs/plugins/sdk-overview.md | 17 ++++---- extensions/lobster/src/lobster-tool.test.ts | 1 + extensions/memory-wiki/index.test.ts | 21 ++++++++-- extensions/memory-wiki/index.ts | 2 + extensions/memory-wiki/src/prompt-section.ts | 42 +++++++++++++++++++ src/plugins/api-builder.ts | 5 +++ src/plugins/loader.runtime-registry.test.ts | 3 ++ src/plugins/loader.test.ts | 4 ++ src/plugins/loader.ts | 7 ++++ src/plugins/memory-state.test.ts | 21 +++++++++- src/plugins/memory-state.ts | 34 ++++++++++++++- src/plugins/registry.ts | 4 ++ src/plugins/types.ts | 4 ++ test/helpers/plugins/plugin-api.ts | 1 + 15 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 extensions/memory-wiki/src/prompt-section.ts diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index fe013d1bb9e..0472d83b78b 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -97509287d728c8f5d1736f7ea07521451ada4b9d7ef56555dbe860a89e1b6e08 plugin-sdk-api-baseline.json -a22b3d427953cc8394b28c87ef7a992d2eb4f2c9f6a76fa58b33079e2306661b plugin-sdk-api-baseline.jsonl +73fa1674a0b450c3ef489ea6d16983c81285ffe0c896072bfae2b4b24d0cf15e plugin-sdk-api-baseline.json +4e83b129be7c697489a1b33f891c31852be90d077483599bf6f4c6f7cfc2eb79 plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index bf97f3d43b3..52916eb5b19 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -308,14 +308,15 @@ methods: ### Infrastructure -| Method | What it registers | -| ---------------------------------------------- | --------------------- | -| `api.registerHook(events, handler, opts?)` | Event hook | -| `api.registerHttpRoute(params)` | Gateway HTTP endpoint | -| `api.registerGatewayMethod(name, handler)` | Gateway RPC method | -| `api.registerCli(registrar, opts?)` | CLI subcommand | -| `api.registerService(service)` | Background service | -| `api.registerInteractiveHandler(registration)` | Interactive handler | +| Method | What it registers | +| ---------------------------------------------- | --------------------------------------- | +| `api.registerHook(events, handler, opts?)` | Event hook | +| `api.registerHttpRoute(params)` | Gateway HTTP endpoint | +| `api.registerGatewayMethod(name, handler)` | Gateway RPC method | +| `api.registerCli(registrar, opts?)` | CLI subcommand | +| `api.registerService(service)` | Background service | +| `api.registerInteractiveHandler(registration)` | Interactive handler | +| `api.registerMemoryPromptSupplement(builder)` | Additive memory-adjacent prompt section | Reserved core admin namespaces (`config.*`, `exec.approvals.*`, `wizard.*`, `update.*`) always stay `operator.admin`, even if a plugin tries to assign a diff --git a/extensions/lobster/src/lobster-tool.test.ts b/extensions/lobster/src/lobster-tool.test.ts index 16453c25bcb..32eb8daaed6 100644 --- a/extensions/lobster/src/lobster-tool.test.ts +++ b/extensions/lobster/src/lobster-tool.test.ts @@ -62,6 +62,7 @@ function fakeApi(overrides: Partial = {}): OpenClawPluginApi registerCommand() {}, registerContextEngine() {}, registerMemoryPromptSection() {}, + registerMemoryPromptSupplement() {}, registerMemoryFlushPlan() {}, registerMemoryRuntime() {}, registerMemoryEmbeddingProvider() {}, diff --git a/extensions/memory-wiki/index.test.ts b/extensions/memory-wiki/index.test.ts index 6194c92485a..4e6b7426750 100644 --- a/extensions/memory-wiki/index.test.ts +++ b/extensions/memory-wiki/index.test.ts @@ -6,6 +6,7 @@ import plugin from "./index.js"; function createApi() { const registerCli = vi.fn(); const registerGatewayMethod = vi.fn(); + const registerMemoryPromptSupplement = vi.fn(); const registerTool = vi.fn(); const api = createTestPluginApi({ id: "memory-wiki", @@ -15,17 +16,31 @@ function createApi() { runtime: {} as OpenClawPluginApi["runtime"], registerCli, registerGatewayMethod, + registerMemoryPromptSupplement, registerTool, }) as OpenClawPluginApi; - return { api, registerCli, registerGatewayMethod, registerTool }; + return { + api, + registerCli, + registerGatewayMethod, + registerMemoryPromptSupplement, + registerTool, + }; } describe("memory-wiki plugin", () => { - it("registers gateway methods, tools, and wiki cli surface", async () => { - const { api, registerCli, registerGatewayMethod, registerTool } = createApi(); + it("registers prompt supplement, gateway methods, tools, and wiki cli surface", async () => { + const { + api, + registerCli, + registerGatewayMethod, + registerMemoryPromptSupplement, + registerTool, + } = createApi(); await plugin.register(api); + expect(registerMemoryPromptSupplement).toHaveBeenCalledTimes(1); expect(registerGatewayMethod.mock.calls.map((call) => call[0])).toEqual([ "wiki.status", "wiki.init", diff --git a/extensions/memory-wiki/index.ts b/extensions/memory-wiki/index.ts index d8cacb18f22..3a5c204641a 100644 --- a/extensions/memory-wiki/index.ts +++ b/extensions/memory-wiki/index.ts @@ -2,6 +2,7 @@ import { definePluginEntry } from "./api.js"; import { registerWikiCli } from "./src/cli.js"; import { memoryWikiConfigSchema, resolveMemoryWikiConfig } from "./src/config.js"; import { registerMemoryWikiGatewayMethods } from "./src/gateway.js"; +import { buildWikiPromptSection } from "./src/prompt-section.js"; import { createWikiApplyTool, createWikiGetTool, @@ -18,6 +19,7 @@ export default definePluginEntry({ register(api) { const config = resolveMemoryWikiConfig(api.pluginConfig); + api.registerMemoryPromptSupplement(buildWikiPromptSection); registerMemoryWikiGatewayMethods({ api, config, appConfig: api.config }); api.registerTool(createWikiStatusTool(config, api.config), { name: "wiki_status" }); api.registerTool(createWikiLintTool(config, api.config), { name: "wiki_lint" }); diff --git a/extensions/memory-wiki/src/prompt-section.ts b/extensions/memory-wiki/src/prompt-section.ts new file mode 100644 index 00000000000..fba03cae5bc --- /dev/null +++ b/extensions/memory-wiki/src/prompt-section.ts @@ -0,0 +1,42 @@ +import type { MemoryPromptSectionBuilder } from "openclaw/plugin-sdk/memory-host-core"; + +export const buildWikiPromptSection: MemoryPromptSectionBuilder = ({ availableTools }) => { + const hasWikiSearch = availableTools.has("wiki_search"); + const hasWikiGet = availableTools.has("wiki_get"); + const hasWikiApply = availableTools.has("wiki_apply"); + const hasWikiLint = availableTools.has("wiki_lint"); + + if (!hasWikiSearch && !hasWikiGet && !hasWikiApply && !hasWikiLint) { + return []; + } + + const lines = [ + "## Compiled Wiki", + "Use the wiki when the answer depends on accumulated project knowledge, prior syntheses, entity pages, or source-backed notes that should survive beyond one conversation.", + ]; + + if (hasWikiSearch && hasWikiGet) { + lines.push( + "Workflow: `wiki_search` first, then `wiki_get` for the exact page or imported memory file you need. Shared search may return `corpus=memory` results when active-memory bridging is enabled.", + ); + } else if (hasWikiSearch) { + lines.push( + "Use `wiki_search` before answering from stored knowledge. Shared search may return `corpus=memory` results when active-memory bridging is enabled.", + ); + } else if (hasWikiGet) { + lines.push( + "Use `wiki_get` to inspect specific wiki pages or imported memory files by path/id.", + ); + } + + if (hasWikiApply) { + lines.push( + "Use `wiki_apply` for narrow synthesis filing and metadata repair instead of rewriting managed markdown blocks by hand.", + ); + } + if (hasWikiLint) { + lines.push("After meaningful wiki updates, run `wiki_lint` before trusting the vault."); + } + lines.push(""); + return lines; +}; diff --git a/src/plugins/api-builder.ts b/src/plugins/api-builder.ts index d69f47571b1..c157accdc5d 100644 --- a/src/plugins/api-builder.ts +++ b/src/plugins/api-builder.ts @@ -41,6 +41,7 @@ export type BuildPluginApiParams = { | "registerCommand" | "registerContextEngine" | "registerMemoryPromptSection" + | "registerMemoryPromptSupplement" | "registerMemoryFlushPlan" | "registerMemoryRuntime" | "registerMemoryEmbeddingProvider" @@ -78,6 +79,8 @@ const noopOnConversationBindingResolved: OpenClawPluginApi["onConversationBindin const noopRegisterCommand: OpenClawPluginApi["registerCommand"] = () => {}; const noopRegisterContextEngine: OpenClawPluginApi["registerContextEngine"] = () => {}; const noopRegisterMemoryPromptSection: OpenClawPluginApi["registerMemoryPromptSection"] = () => {}; +const noopRegisterMemoryPromptSupplement: OpenClawPluginApi["registerMemoryPromptSupplement"] = + () => {}; const noopRegisterMemoryFlushPlan: OpenClawPluginApi["registerMemoryFlushPlan"] = () => {}; const noopRegisterMemoryRuntime: OpenClawPluginApi["registerMemoryRuntime"] = () => {}; const noopRegisterMemoryEmbeddingProvider: OpenClawPluginApi["registerMemoryEmbeddingProvider"] = @@ -129,6 +132,8 @@ export function buildPluginApi(params: BuildPluginApiParams): OpenClawPluginApi registerContextEngine: handlers.registerContextEngine ?? noopRegisterContextEngine, registerMemoryPromptSection: handlers.registerMemoryPromptSection ?? noopRegisterMemoryPromptSection, + registerMemoryPromptSupplement: + handlers.registerMemoryPromptSupplement ?? noopRegisterMemoryPromptSupplement, registerMemoryFlushPlan: handlers.registerMemoryFlushPlan ?? noopRegisterMemoryFlushPlan, registerMemoryRuntime: handlers.registerMemoryRuntime ?? noopRegisterMemoryRuntime, registerMemoryEmbeddingProvider: diff --git a/src/plugins/loader.runtime-registry.test.ts b/src/plugins/loader.runtime-registry.test.ts index 8b5e665cadd..2b8930e512f 100644 --- a/src/plugins/loader.runtime-registry.test.ts +++ b/src/plugins/loader.runtime-registry.test.ts @@ -9,6 +9,7 @@ import { buildMemoryPromptSection, getMemoryRuntime, registerMemoryFlushPlanResolver, + registerMemoryPromptSupplement, registerMemoryPromptSection, registerMemoryRuntime, resolveMemoryFlushPlan, @@ -156,6 +157,7 @@ describe("clearPluginLoaderCache", () => { create: async () => ({ provider: null }), }); registerMemoryPromptSection(() => ["stale memory section"]); + registerMemoryPromptSupplement("memory-wiki", () => ["stale wiki supplement"]); registerMemoryFlushPlanResolver(() => ({ softThresholdTokens: 1, forceFlushTranscriptBytes: 2, @@ -174,6 +176,7 @@ describe("clearPluginLoaderCache", () => { }); expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([ "stale memory section", + "stale wiki supplement", ]); expect(resolveMemoryFlushPlan({})?.relativePath).toBe("memory/stale.md"); expect(getMemoryRuntime()).toBeDefined(); diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index baf0a31cd83..ed43581814b 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -29,6 +29,7 @@ import { buildMemoryPromptSection, getMemoryRuntime, registerMemoryFlushPlanResolver, + registerMemoryPromptSupplement, registerMemoryPromptSection, registerMemoryRuntime, resolveMemoryFlushPlan, @@ -1382,6 +1383,7 @@ module.exports = { id: "throws-after-import", register() {} };`, create: async () => ({ provider: null }), }); registerMemoryPromptSection(() => ["active memory section"]); + registerMemoryPromptSupplement("memory-wiki", () => ["active wiki supplement"]); registerMemoryFlushPlanResolver(() => ({ softThresholdTokens: 1, forceFlushTranscriptBytes: 2, @@ -1448,6 +1450,7 @@ module.exports = { id: "throws-after-import", register() {} };`, expect(scoped.plugins.find((entry) => entry.id === "snapshot-memory")?.status).toBe("loaded"); expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([ "active memory section", + "active wiki supplement", ]); expect(resolveMemoryFlushPlan({})?.relativePath).toBe("memory/active.md"); expect(getMemoryRuntime()).toBe(activeRuntime); @@ -1468,6 +1471,7 @@ module.exports = { id: "throws-after-import", register() {} };`, create: async () => ({ provider: null }), }); api.registerMemoryPromptSection(() => ["stale failure section"]); + api.registerMemoryPromptSupplement(() => ["stale failure supplement"]); api.registerMemoryFlushPlan(() => ({ softThresholdTokens: 10, forceFlushTranscriptBytes: 20, diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index f74472a7677..3e6826a85c2 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -39,6 +39,7 @@ import { getMemoryFlushPlanResolver, getMemoryPromptSectionBuilder, getMemoryRuntime, + listMemoryPromptSupplements, restoreMemoryPluginState, } from "./memory-state.js"; import { isPathInside, safeStatSync } from "./path-safety.js"; @@ -132,6 +133,7 @@ type CachedPluginState = { memoryEmbeddingProviders: ReturnType; memoryFlushPlanResolver: ReturnType; memoryPromptBuilder: ReturnType; + memoryPromptSupplements: ReturnType; memoryRuntime: ReturnType; }; @@ -993,6 +995,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi restoreRegisteredMemoryEmbeddingProviders(cached.memoryEmbeddingProviders); restoreMemoryPluginState({ promptBuilder: cached.memoryPromptBuilder, + promptSupplements: cached.memoryPromptSupplements, flushPlanResolver: cached.memoryFlushPlanResolver, runtime: cached.memoryRuntime, }); @@ -1557,6 +1560,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi const previousMemoryEmbeddingProviders = listRegisteredMemoryEmbeddingProviders(); const previousMemoryFlushPlanResolver = getMemoryFlushPlanResolver(); const previousMemoryPromptBuilder = getMemoryPromptSectionBuilder(); + const previousMemoryPromptSupplements = listMemoryPromptSupplements(); const previousMemoryRuntime = getMemoryRuntime(); try { @@ -1574,6 +1578,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi restoreRegisteredMemoryEmbeddingProviders(previousMemoryEmbeddingProviders); restoreMemoryPluginState({ promptBuilder: previousMemoryPromptBuilder, + promptSupplements: previousMemoryPromptSupplements, flushPlanResolver: previousMemoryFlushPlanResolver, runtime: previousMemoryRuntime, }); @@ -1584,6 +1589,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi restoreRegisteredMemoryEmbeddingProviders(previousMemoryEmbeddingProviders); restoreMemoryPluginState({ promptBuilder: previousMemoryPromptBuilder, + promptSupplements: previousMemoryPromptSupplements, flushPlanResolver: previousMemoryFlushPlanResolver, runtime: previousMemoryRuntime, }); @@ -1639,6 +1645,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi memoryEmbeddingProviders: listRegisteredMemoryEmbeddingProviders(), memoryFlushPlanResolver: getMemoryFlushPlanResolver(), memoryPromptBuilder: getMemoryPromptSectionBuilder(), + memoryPromptSupplements: listMemoryPromptSupplements(), memoryRuntime: getMemoryRuntime(), }); } diff --git a/src/plugins/memory-state.test.ts b/src/plugins/memory-state.test.ts index 54dc632818f..70e5a7af140 100644 --- a/src/plugins/memory-state.test.ts +++ b/src/plugins/memory-state.test.ts @@ -6,7 +6,9 @@ import { getMemoryFlushPlanResolver, getMemoryPromptSectionBuilder, getMemoryRuntime, + listMemoryPromptSupplements, registerMemoryFlushPlanResolver, + registerMemoryPromptSupplement, registerMemoryPromptSection, registerMemoryRuntime, resolveMemoryFlushPlan, @@ -44,6 +46,7 @@ function expectClearedMemoryState() { function createMemoryStateSnapshot() { return { promptBuilder: getMemoryPromptSectionBuilder(), + promptSupplements: listMemoryPromptSupplements(), flushPlanResolver: getMemoryFlushPlanResolver(), runtime: getMemoryRuntime(), }; @@ -103,6 +106,18 @@ describe("memory plugin state", () => { ).toEqual(["citations: off"]); }); + it("appends prompt supplements in plugin-id order", () => { + registerMemoryPromptSection(() => ["primary"]); + registerMemoryPromptSupplement("memory-wiki", () => ["wiki"]); + registerMemoryPromptSupplement("alpha-helper", () => ["alpha"]); + + expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([ + "primary", + "alpha", + "wiki", + ]); + }); + it("uses the registered flush plan resolver", () => { registerMemoryFlushPlanResolver(() => ({ softThresholdTokens: 1, @@ -137,13 +152,17 @@ describe("memory plugin state", () => { relativePath: "memory/first.md", runtime, }); + registerMemoryPromptSupplement("memory-wiki", () => ["wiki supplement"]); const snapshot = createMemoryStateSnapshot(); _resetMemoryPluginState(); expectClearedMemoryState(); restoreMemoryPluginState(snapshot); - expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual(["first"]); + expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([ + "first", + "wiki supplement", + ]); expect(resolveMemoryFlushPlan({})?.relativePath).toBe("memory/first.md"); expect(getMemoryRuntime()).toBe(runtime); }); diff --git a/src/plugins/memory-state.ts b/src/plugins/memory-state.ts index ff30f11120a..f041a97e099 100644 --- a/src/plugins/memory-state.ts +++ b/src/plugins/memory-state.ts @@ -7,6 +7,11 @@ export type MemoryPromptSectionBuilder = (params: { citationsMode?: MemoryCitationsMode; }) => string[]; +export type MemoryPromptSupplementRegistration = { + pluginId: string; + builder: MemoryPromptSectionBuilder; +}; + export type MemoryFlushPlan = { softThresholdTokens: number; forceFlushTranscriptBytes: number; @@ -54,27 +59,50 @@ export type MemoryPluginRuntime = { type MemoryPluginState = { promptBuilder?: MemoryPromptSectionBuilder; + promptSupplements: MemoryPromptSupplementRegistration[]; flushPlanResolver?: MemoryFlushPlanResolver; runtime?: MemoryPluginRuntime; }; -const memoryPluginState: MemoryPluginState = {}; +const memoryPluginState: MemoryPluginState = { + promptSupplements: [], +}; export function registerMemoryPromptSection(builder: MemoryPromptSectionBuilder): void { memoryPluginState.promptBuilder = builder; } +export function registerMemoryPromptSupplement( + pluginId: string, + builder: MemoryPromptSectionBuilder, +): void { + const next = memoryPluginState.promptSupplements.filter( + (registration) => registration.pluginId !== pluginId, + ); + next.push({ pluginId, builder }); + memoryPluginState.promptSupplements = next; +} + export function buildMemoryPromptSection(params: { availableTools: Set; citationsMode?: MemoryCitationsMode; }): string[] { - return memoryPluginState.promptBuilder?.(params) ?? []; + const primary = memoryPluginState.promptBuilder?.(params) ?? []; + const supplements = memoryPluginState.promptSupplements + // Keep supplement order stable even if plugin registration order changes. + .toSorted((left, right) => left.pluginId.localeCompare(right.pluginId)) + .flatMap((registration) => registration.builder(params)); + return [...primary, ...supplements]; } export function getMemoryPromptSectionBuilder(): MemoryPromptSectionBuilder | undefined { return memoryPluginState.promptBuilder; } +export function listMemoryPromptSupplements(): MemoryPromptSupplementRegistration[] { + return [...memoryPluginState.promptSupplements]; +} + export function registerMemoryFlushPlanResolver(resolver: MemoryFlushPlanResolver): void { memoryPluginState.flushPlanResolver = resolver; } @@ -104,12 +132,14 @@ export function hasMemoryRuntime(): boolean { export function restoreMemoryPluginState(state: MemoryPluginState): void { memoryPluginState.promptBuilder = state.promptBuilder; + memoryPluginState.promptSupplements = [...state.promptSupplements]; memoryPluginState.flushPlanResolver = state.flushPlanResolver; memoryPluginState.runtime = state.runtime; } export function clearMemoryPluginState(): void { memoryPluginState.promptBuilder = undefined; + memoryPluginState.promptSupplements = []; memoryPluginState.flushPlanResolver = undefined; memoryPluginState.runtime = undefined; } diff --git a/src/plugins/registry.ts b/src/plugins/registry.ts index ac988ca3462..202750ece9d 100644 --- a/src/plugins/registry.ts +++ b/src/plugins/registry.ts @@ -25,6 +25,7 @@ import { } from "./memory-embedding-providers.js"; import { registerMemoryFlushPlanResolver, + registerMemoryPromptSupplement, registerMemoryPromptSection, registerMemoryRuntime, } from "./memory-state.js"; @@ -1116,6 +1117,9 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) { } registerMemoryPromptSection(builder); }, + registerMemoryPromptSupplement: (builder) => { + registerMemoryPromptSupplement(record.id, builder); + }, registerMemoryFlushPlan: (resolver) => { if (!hasKind(record.kind, "memory")) { pushDiagnostic({ diff --git a/src/plugins/types.ts b/src/plugins/types.ts index 41ed169833d..eb35d9a15df 100644 --- a/src/plugins/types.ts +++ b/src/plugins/types.ts @@ -2082,6 +2082,10 @@ export type OpenClawPluginApi = { registerMemoryPromptSection: ( builder: import("./memory-state.js").MemoryPromptSectionBuilder, ) => void; + /** Register an additive memory-adjacent prompt section (non-exclusive). */ + registerMemoryPromptSupplement: ( + builder: import("./memory-state.js").MemoryPromptSectionBuilder, + ) => void; /** Register the pre-compaction flush plan resolver for this memory plugin (exclusive slot). */ registerMemoryFlushPlan: (resolver: import("./memory-state.js").MemoryFlushPlanResolver) => void; /** Register the active memory runtime adapter for this memory plugin (exclusive slot). */ diff --git a/test/helpers/plugins/plugin-api.ts b/test/helpers/plugins/plugin-api.ts index 19f38a16396..0b00af1e5ef 100644 --- a/test/helpers/plugins/plugin-api.ts +++ b/test/helpers/plugins/plugin-api.ts @@ -33,6 +33,7 @@ export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi registerCommand() {}, registerContextEngine() {}, registerMemoryPromptSection() {}, + registerMemoryPromptSupplement() {}, registerMemoryFlushPlan() {}, registerMemoryRuntime() {}, registerMemoryEmbeddingProvider() {},