From 8a8cc8dc9fca7ec71db82780d741d3368fa38fa3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 11:35:51 +0100 Subject: [PATCH] fix(memory): refresh tool config at execution --- CHANGELOG.md | 1 + extensions/memory-core/index.ts | 7 ++- .../src/memory-tool-manager-mock.ts | 8 +++- extensions/memory-core/src/tools.shared.ts | 22 +++++---- extensions/memory-core/src/tools.test.ts | 47 +++++++++++++++++++ extensions/memory-core/src/tools.ts | 2 + 6 files changed, 74 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39b36a559fb..a9e04004dae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Memory-core: re-resolve the active runtime config whenever `memory_search` or `memory_get` executes, so provider changes made by `config.patch` stop leaving stale embedding backends behind in existing tool instances. Fixes #61098. Thanks @BradGroux and @Linux2010. - Channels/setup: treat bundled channel plugins as already bundled during `channels add` and onboarding, enabling them without writing redundant `plugins.load.paths` entries or path install records. Fixes #72740. Thanks @iCodePoet. - WhatsApp: honor gateway `HTTPS_PROXY` / `HTTP_PROXY` env vars for QR-login WebSocket connections, while respecting `NO_PROXY`, so proxied networks no longer fall back to direct `mmg.whatsapp.net` connections that time out with 408. Fixes #72547; supersedes #72692. Thanks @mebusw and @SymbolStar. - Bonjour: default mDNS advertisements to the system hostname when it is DNS-safe, avoiding `openclaw.local` probing conflicts and Gateway restart loops on hosts such as `Lobster` or `ubuntu`. Fixes #72355 and #72689; supersedes #72694. Thanks @mscheuerlein-bot, @gcusms, @moyuwuhen601, @pavan987, @zml-0912, @hhq365, and @SymbolStar. diff --git a/extensions/memory-core/index.ts b/extensions/memory-core/index.ts index 451986e4612..add1b516846 100644 --- a/extensions/memory-core/index.ts +++ b/extensions/memory-core/index.ts @@ -1,3 +1,4 @@ +import { getRuntimeConfigSnapshot } from "openclaw/plugin-sdk/config-runtime"; import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { registerMemoryCli } from "./src/cli.js"; import { registerDreamingCommand } from "./src/dreaming-command.js"; @@ -42,7 +43,8 @@ export default definePluginEntry({ api.registerTool( (ctx) => createMemorySearchTool({ - config: ctx.config, + config: ctx.runtimeConfig ?? ctx.config, + getConfig: () => getRuntimeConfigSnapshot() ?? ctx.runtimeConfig ?? ctx.config, agentSessionKey: ctx.sessionKey, sandboxed: ctx.sandboxed, }), @@ -52,7 +54,8 @@ export default definePluginEntry({ api.registerTool( (ctx) => createMemoryGetTool({ - config: ctx.config, + config: ctx.runtimeConfig ?? ctx.config, + getConfig: () => getRuntimeConfigSnapshot() ?? ctx.runtimeConfig ?? ctx.config, agentSessionKey: ctx.sessionKey, }), { names: ["memory_get"] }, diff --git a/extensions/memory-core/src/memory-tool-manager-mock.ts b/extensions/memory-core/src/memory-tool-manager-mock.ts index c37f6cf4cca..95d56fb7d69 100644 --- a/extensions/memory-core/src/memory-tool-manager-mock.ts +++ b/extensions/memory-core/src/memory-tool-manager-mock.ts @@ -52,7 +52,9 @@ const stubManager = { close: vi.fn(), }; -const getMemorySearchManagerMock = vi.fn(async () => ({ manager: stubManager })); +const getMemorySearchManagerMock = vi.fn(async (_params: { cfg?: unknown }) => ({ + manager: stubManager, +})); const readAgentMemoryFileMock = vi.fn( async (params: MemoryReadParams) => await readFileImpl(params), ); @@ -116,6 +118,10 @@ export function getMemorySearchManagerMockCalls(): number { return getMemorySearchManagerMock.mock.calls.length; } +export function getMemorySearchManagerMockConfigs(): unknown[] { + return getMemorySearchManagerMock.mock.calls.map(([params]) => params.cfg); +} + export function getReadAgentMemoryFileMockCalls(): number { return readAgentMemoryFileMock.mock.calls.length; } diff --git a/extensions/memory-core/src/tools.shared.ts b/extensions/memory-core/src/tools.shared.ts index 65c5f4a7f5d..04c2120e816 100644 --- a/extensions/memory-core/src/tools.shared.ts +++ b/extensions/memory-core/src/tools.shared.ts @@ -13,6 +13,11 @@ type MemoryToolRuntime = typeof import("./tools.runtime.js"); type MemorySearchManagerResult = Awaited< ReturnType<(typeof import("./memory/index.js"))["getMemorySearchManager"]> >; +type MemoryToolOptions = { + config?: OpenClawConfig; + getConfig?: () => OpenClawConfig | undefined; + agentSessionKey?: string; +}; let memoryToolRuntimePromise: Promise | null = null; @@ -44,11 +49,8 @@ export const MemoryGetSchema = Type.Object({ ), }); -export function resolveMemoryToolContext(options: { - config?: OpenClawConfig; - agentSessionKey?: string; -}) { - const cfg = options.config; +export function resolveMemoryToolContext(options: MemoryToolOptions) { + const cfg = options.getConfig?.() ?? options.config; if (!cfg) { return null; } @@ -98,10 +100,7 @@ export async function getMemoryManagerContextWithPurpose(params: { } export function createMemoryTool(params: { - options: { - config?: OpenClawConfig; - agentSessionKey?: string; - }; + options: MemoryToolOptions; label: string; name: string; description: string; @@ -117,7 +116,10 @@ export function createMemoryTool(params: { name: params.name, description: params.description, parameters: params.parameters, - execute: params.execute(ctx), + execute: async (toolCallId, toolParams) => { + const latestCtx = resolveMemoryToolContext(params.options) ?? ctx; + return await params.execute(latestCtx)(toolCallId, toolParams); + }, }; } diff --git a/extensions/memory-core/src/tools.test.ts b/extensions/memory-core/src/tools.test.ts index 97689736771..489205eada2 100644 --- a/extensions/memory-core/src/tools.test.ts +++ b/extensions/memory-core/src/tools.test.ts @@ -1,10 +1,13 @@ import { beforeEach, describe, expect, it } from "vitest"; import { + getMemorySearchManagerMockConfigs, resetMemoryToolMockState, setMemoryBackend, setMemorySearchImpl, } from "./memory-tool-manager-mock.js"; +import { createMemorySearchTool } from "./tools.js"; import { + asOpenClawConfig, createMemorySearchToolOrThrow, expectUnavailableMemorySearchDetails, } from "./tools.test-helpers.js"; @@ -103,4 +106,48 @@ describe("memory_search unavailable payloads", () => { expect.any(Number), ); }); + + it("re-resolves config when executing a previously created tool", async () => { + const startupConfig = asOpenClawConfig({ + agents: { + defaults: { + memorySearch: { + provider: "ollama", + model: "nomic-embed-text", + }, + }, + list: [{ id: "main", default: true }], + }, + memory: { + backend: "builtin", + }, + }); + const patchedConfig = asOpenClawConfig({ + agents: { + defaults: { + memorySearch: { + provider: "openai", + model: "text-embedding-3-small", + }, + }, + list: [{ id: "main", default: true }], + }, + memory: { + backend: "builtin", + }, + }); + let liveConfig = startupConfig; + const tool = createMemorySearchTool({ + config: startupConfig, + getConfig: () => liveConfig, + }); + if (!tool) { + throw new Error("tool missing"); + } + + liveConfig = patchedConfig; + await tool.execute("patched-config", { query: "provider switch" }); + + expect(getMemorySearchManagerMockConfigs()).toEqual([patchedConfig]); + }); }); diff --git a/extensions/memory-core/src/tools.ts b/extensions/memory-core/src/tools.ts index 3c542066249..54ea39efe3c 100644 --- a/extensions/memory-core/src/tools.ts +++ b/extensions/memory-core/src/tools.ts @@ -182,6 +182,7 @@ async function executeMemoryReadResult(params: { export function createMemorySearchTool(options: { config?: OpenClawConfig; + getConfig?: () => OpenClawConfig | undefined; agentSessionKey?: string; sandboxed?: boolean; }) { @@ -344,6 +345,7 @@ export function createMemorySearchTool(options: { export function createMemoryGetTool(options: { config?: OpenClawConfig; + getConfig?: () => OpenClawConfig | undefined; agentSessionKey?: string; }) { return createMemoryTool({