From 398d6c532f4f0b17b3f325cc897d8599101b3b01 Mon Sep 17 00:00:00 2001 From: haosenwang1018 Date: Thu, 9 Apr 2026 01:58:33 +0000 Subject: [PATCH 1/5] fix: skip hook recall when context-engine active --- examples/openclaw-plugin/index.ts | 6 +++++ .../ut/plugin-bypass-session-patterns.test.ts | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/examples/openclaw-plugin/index.ts b/examples/openclaw-plugin/index.ts index 849501402..b325ae3c7 100644 --- a/examples/openclaw-plugin/index.ts +++ b/examples/openclaw-plugin/index.ts @@ -903,6 +903,12 @@ const contextEnginePlugin = { ); return; } + if (contextEngineRef) { + verboseRoutingInfo( + `openviking: skipping before_prompt_build auto-recall because context-engine is active (sessionKey=${ctx?.sessionKey ?? "none"}, sessionId=${ctx?.sessionId ?? "none"})`, + ); + return; + } const agentId = resolveAgentId(ctx?.sessionId, ctx?.sessionKey); let client: OpenVikingClient; try { diff --git a/examples/openclaw-plugin/tests/ut/plugin-bypass-session-patterns.test.ts b/examples/openclaw-plugin/tests/ut/plugin-bypass-session-patterns.test.ts index 88e2e61d3..48226992d 100644 --- a/examples/openclaw-plugin/tests/ut/plugin-bypass-session-patterns.test.ts +++ b/examples/openclaw-plugin/tests/ut/plugin-bypass-session-patterns.test.ts @@ -65,6 +65,33 @@ describe("plugin bypass session patterns", () => { ); }); + it("skips before_prompt_build auto-recall once context-engine is active", async () => { + const { handlers, logger, registerContextEngine } = setupPlugin(); + + const factory = registerContextEngine.mock.calls[0]?.[1] as (() => unknown) | undefined; + expect(factory).toBeTruthy(); + factory!(); + + const hook = handlers.get("before_prompt_build"); + expect(hook).toBeTruthy(); + + const result = await hook!( + { + messages: [{ role: "user", content: "remember the launch checklist" }], + prompt: "remember the launch checklist", + }, + { + sessionId: "runtime-session", + sessionKey: "agent:main:test:1", + }, + ); + + expect(result).toBeUndefined(); + expect(logger.warn).not.toHaveBeenCalledWith( + expect.stringContaining("failed to get client"), + ); + }); + it("bypasses before_reset without calling commitOVSession", async () => { const { handlers, registerContextEngine } = setupPlugin({ bypassSessionPatterns: ["agent:*:cron:**"], From 431d29fbed97710dbf81f78e5f6ea9e1309e0b32 Mon Sep 17 00:00:00 2001 From: haosenwang1018 Date: Thu, 9 Apr 2026 02:15:22 +0000 Subject: [PATCH 2/5] docs: clarify context-engine recall path --- examples/openclaw-plugin/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/openclaw-plugin/README.md b/examples/openclaw-plugin/README.md index 46007e0cb..bc97ea306 100644 --- a/examples/openclaw-plugin/README.md +++ b/examples/openclaw-plugin/README.md @@ -55,7 +55,7 @@ This matters because the plugin is built to support multi-agent and multi-sessio ![Automatic recall flow before prompt build](./images/openclaw-plugin-recall-flow.png) -Today the main recall path still lives in `before_prompt_build`: +Today there are two recall-related paths: legacy hook injection in `before_prompt_build`, and context reconstruction in `assemble()`. They should not both inject prompt context for the same run: 1. Extract the latest user text from `messages` or `prompt`. 2. Resolve the agent routing for the current `sessionId/sessionKey`. @@ -182,7 +182,7 @@ The repo also contains a more future-looking design draft at `docs/design/opencl - this README describes current implemented behavior - the older draft discusses a stronger future move into context-engine-owned lifecycle control -- in the current version, the main automatic recall path still lives in `before_prompt_build`, not fully in `assemble()` +- in the current version, hook-based auto-recall still exists in `before_prompt_build`, but when the context-engine path is active it should not run alongside `assemble()` - in the current version, `afterTurn()` already appends to the OpenViking session, but commit remains threshold-triggered and asynchronous on that path - in the current version, `compact()` already uses `commit(wait=true)`, but it is still focused on synchronous commit plus readback rather than owning every orchestration concern From e22961372f2cf978752297091c744627d6658b71 Mon Sep 17 00:00:00 2001 From: haosenwang1018 Date: Thu, 9 Apr 2026 02:45:28 +0000 Subject: [PATCH 3/5] test: cover context-engine-active recall bypass --- .../ut/plugin-normal-flow-real-server.test.ts | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/examples/openclaw-plugin/tests/ut/plugin-normal-flow-real-server.test.ts b/examples/openclaw-plugin/tests/ut/plugin-normal-flow-real-server.test.ts index 47f2b169d..8edfc682c 100644 --- a/examples/openclaw-plugin/tests/ut/plugin-normal-flow-real-server.test.ts +++ b/examples/openclaw-plugin/tests/ut/plugin-normal-flow-real-server.test.ts @@ -176,6 +176,82 @@ describe("plugin normal flow with healthy backend", () => { await once(server, "close"); }); + it("skips hook recall after the context-engine is instantiated but still assembles context", async () => { + const handlers = new Map unknown>(); + let service: + | { + start: () => Promise; + stop?: () => Promise | void; + } + | null = null; + let contextEngineFactory: (() => unknown) | null = null; + + plugin.register({ + logger: { + debug: () => {}, + error: () => {}, + info: () => {}, + warn: () => {}, + }, + on: (name, handler) => { + handlers.set(name, handler); + }, + pluginConfig: { + autoCapture: true, + autoRecall: true, + baseUrl, + commitTokenThreshold: 20000, + ingestReplyAssist: false, + mode: "remote", + }, + registerContextEngine: (_id, factory) => { + contextEngineFactory = factory as () => unknown; + }, + registerService: (entry) => { + service = entry; + }, + registerTool: () => {}, + }); + + expect(service).toBeTruthy(); + expect(contextEngineFactory).toBeTruthy(); + + await service!.start(); + + const contextEngine = contextEngineFactory!() as { + assemble: (params: { + sessionId: string; + messages: Array<{ role: string; content: string }>; + }) => Promise<{ messages: Array<{ role: string; content: unknown }> }>; + }; + + const beforePromptBuild = handlers.get("before_prompt_build"); + expect(beforePromptBuild).toBeTruthy(); + const hookResult = await beforePromptBuild!( + { messages: [{ role: "user", content: "what backend language should we use?" }] }, + { agentId: "main", sessionId: "session-normal", sessionKey: "agent:main:normal" }, + ); + + expect(hookResult).toBeUndefined(); + + const assembled = await contextEngine.assemble({ + sessionId: "session-normal", + messages: [{ role: "user", content: "fallback" }], + }); + + expect(assembled.messages[0]).toEqual({ + role: "user", + content: "[Session History Summary] +Earlier work focused on backend stack choices.", + }); + expect(assembled.messages[1]).toEqual({ + role: "assistant", + content: [{ type: "text", text: "Stored answer from OpenViking." }], + }); + + await service?.stop?.(); + }); + it("keeps normal prompt-build and context-engine flow working", async () => { const handlers = new Map unknown>(); let service: From ea2167fd79230681f858bdbbafd9098c7139ac08 Mon Sep 17 00:00:00 2001 From: haosenwang1018 Date: Thu, 9 Apr 2026 03:15:27 +0000 Subject: [PATCH 4/5] test: cover before_reset without context engine --- .../ut/plugin-bypass-session-patterns.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/examples/openclaw-plugin/tests/ut/plugin-bypass-session-patterns.test.ts b/examples/openclaw-plugin/tests/ut/plugin-bypass-session-patterns.test.ts index 48226992d..6168e501e 100644 --- a/examples/openclaw-plugin/tests/ut/plugin-bypass-session-patterns.test.ts +++ b/examples/openclaw-plugin/tests/ut/plugin-bypass-session-patterns.test.ts @@ -92,6 +92,29 @@ describe("plugin bypass session patterns", () => { ); }); + it("does not try to commit on before_reset when the context-engine was never instantiated", async () => { + const { handlers, logger, registerContextEngine } = setupPlugin(); + + expect(registerContextEngine).toHaveBeenCalledTimes(1); + + const hook = handlers.get("before_reset"); + expect(hook).toBeTruthy(); + + await expect( + hook!( + {}, + { + sessionId: "runtime-session", + sessionKey: "agent:main:test:1", + }, + ), + ).resolves.toBeUndefined(); + + expect(logger.warn).not.toHaveBeenCalledWith( + expect.stringContaining("failed to commit OV session on reset"), + ); + }); + it("bypasses before_reset without calling commitOVSession", async () => { const { handlers, registerContextEngine } = setupPlugin({ bypassSessionPatterns: ["agent:*:cron:**"], From d4e67664b7091e48fd3cee7321ef951f4428f193 Mon Sep 17 00:00:00 2001 From: haosenwang1018 Date: Thu, 9 Apr 2026 03:45:25 +0000 Subject: [PATCH 5/5] test: lock single context-engine registration --- examples/openclaw-plugin/tests/ut/tools.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/examples/openclaw-plugin/tests/ut/tools.test.ts b/examples/openclaw-plugin/tests/ut/tools.test.ts index a1fd7d48e..6540c6b85 100644 --- a/examples/openclaw-plugin/tests/ut/tools.test.ts +++ b/examples/openclaw-plugin/tests/ut/tools.test.ts @@ -208,6 +208,16 @@ describe("Plugin registration", () => { ); }); + it("registers the context engine exactly once", () => { + const { api } = setupPlugin(); + contextEnginePlugin.register(api as any); + expect(api.registerContextEngine).toHaveBeenCalledTimes(1); + expect(api.registerContextEngine).toHaveBeenCalledWith( + "openviking", + expect.any(Function), + ); + }); + it("registers context engine when api.registerContextEngine is available", () => { const { api } = setupPlugin(); contextEnginePlugin.register(api as any);