Skip to content
11 changes: 11 additions & 0 deletions examples/openclaw-plugin/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ Get the current full plugin configuration:
openclaw config get plugins.entries.openviking.config
```

Recommended default for the context-engine path:

```bash
openclaw config get plugins.entries.openviking.config.recallPath
openclaw config get plugins.entries.openviking.hooks.allowPromptInjection
```

The supported default is `recallPath = assemble` with
`hooks.allowPromptInjection = false`, so memory recall stays inside
`assemble()` instead of running in `before_prompt_build`.

### Local Mode

Use this mode when the OpenClaw plugin should start and manage a local OpenViking process.
Expand Down
18 changes: 9 additions & 9 deletions examples/openclaw-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,18 @@ The main rules are:

This matters because the plugin is built to support multi-agent and multi-session OpenClaw usage without mixing memories across sessions.

## Prompt-Front Recall Flow
## Recall Flow

![Automatic recall flow before prompt build](./images/openclaw-plugin-recall-flow.png)

Today the main recall path still lives in `before_prompt_build`:
The default recall path now lives in `assemble()`:

1. Extract the latest user text from `messages` or `prompt`.
1. Extract the latest user text from the active messages passed into `assemble()`.
2. Resolve the agent routing for the current `sessionId/sessionKey`.
3. Run a quick availability precheck so prompt building does not stall when OpenViking is unavailable.
3. Read session context from OpenViking under the configured token budget.
4. Query both `viking://user/memories` and `viking://agent/memories` in parallel.
5. Deduplicate, threshold-filter, rerank, and trim the results under a token budget.
6. Prepend the selected memories as a `<relevant-memories>` block.
6. Append the selected memories into `systemPromptAddition` as a `<relevant-memories>` block.

The reranking logic is not pure vector-score sorting. The current implementation also considers:

Expand All @@ -79,7 +79,7 @@ When the latest user input looks like pasted multi-speaker transcript content:

- metadata blocks, command text, and pure question text are filtered out
- the cleaned text is checked against speaker-turn and length thresholds
- if it matches, the plugin prepends a lightweight `<ingest-reply-assist>` instruction
- if it matches, the plugin adds a lightweight `<ingest-reply-assist>` instruction to `systemPromptAddition`

The goal is not to change memory logic. It is to reduce the chance that the model responds with `NO_REPLY` when the user pastes chat history, meeting notes, or conversation transcripts for ingestion.

Expand All @@ -100,7 +100,7 @@ Session handling is the main axis of this design. In the current implementation
- tool output becomes separate `toolResult`
- the final message list goes through a tool-use/result pairing repair pass

That means OpenClaw sees “compressed history summary + archive index + active messages”, not an ever-growing raw transcript.
That means OpenClaw sees “compressed history summary + archive index + active messages”, not an ever-growing raw transcript. When recall is enabled, `assemble()` also becomes the main memory-injection surface for fresh-session questions.

### What `afterTurn()` does

Expand Down Expand Up @@ -205,8 +205,8 @@ The main difference between `local` and `remote` is who is responsible for bring
The repo also contains a more future-looking design draft at `docs/design/openclaw-context-engine-refactor.md`. It is important not to conflate the two:

- 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()`
- the older draft discusses a broader context-engine-owned lifecycle control plan
- in the current version, automatic recall defaults to `assemble()` and `before_prompt_build` is a compatibility path only
- 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

Expand Down
18 changes: 18 additions & 0 deletions examples/openclaw-plugin/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type MemoryOpenVikingConfig = {
captureMode?: "semantic" | "keyword";
captureMaxLength?: number;
autoRecall?: boolean;
recallPath?: "assemble" | "hook";
recallLimit?: number;
recallScoreThreshold?: number;
recallMaxContentChars?: number;
Expand Down Expand Up @@ -46,6 +47,7 @@ const DEFAULT_TIMEOUT_MS = 15000;
const DEFAULT_CAPTURE_MODE = "semantic";
const DEFAULT_CAPTURE_MAX_LENGTH = 24000;
const DEFAULT_RECALL_LIMIT = 6;
const DEFAULT_RECALL_PATH = "assemble";
const DEFAULT_RECALL_SCORE_THRESHOLD = 0.15;
const DEFAULT_RECALL_MAX_CONTENT_CHARS = 500;
const DEFAULT_RECALL_PREFER_ABSTRACT = true;
Expand Down Expand Up @@ -154,6 +156,7 @@ export const memoryOpenVikingConfigSchema = {
"captureMode",
"captureMaxLength",
"autoRecall",
"recallPath",
"recallLimit",
"recallScoreThreshold",
"recallMaxContentChars",
Expand Down Expand Up @@ -196,6 +199,14 @@ export const memoryOpenVikingConfigSchema = {
) {
throw new Error(`openviking captureMode must be "semantic" or "keyword"`);
}
const recallPath = cfg.recallPath;
if (
typeof recallPath !== "undefined" &&
recallPath !== "assemble" &&
recallPath !== "hook"
) {
throw new Error(`openviking recallPath must be "assemble" or "hook"`);
}

return {
mode,
Expand All @@ -213,6 +224,7 @@ export const memoryOpenVikingConfigSchema = {
Math.min(200_000, Math.floor(toNumber(cfg.captureMaxLength, DEFAULT_CAPTURE_MAX_LENGTH))),
),
autoRecall: cfg.autoRecall !== false,
recallPath: recallPath ?? DEFAULT_RECALL_PATH,
recallLimit: Math.max(1, Math.floor(toNumber(cfg.recallLimit, DEFAULT_RECALL_LIMIT))),
recallScoreThreshold: Math.min(
1,
Expand Down Expand Up @@ -334,6 +346,12 @@ export const memoryOpenVikingConfigSchema = {
label: "Auto-Recall",
help: "Inject relevant OpenViking memories into agent context",
},
recallPath: {
label: "Recall Path",
placeholder: DEFAULT_RECALL_PATH,
advanced: true,
help: '"assemble" keeps memory injection inside the context-engine path; "hook" preserves legacy before_prompt_build recall.',
},
recallLimit: {
label: "Recall Limit",
placeholder: String(DEFAULT_RECALL_LIMIT),
Expand Down
Loading