examples/openclaw-plugin/README.md
Use OpenViking as the long-term memory backend for OpenClaw. In OpenClaw, this plugin is registered as the openviking context engine.
This document is not an installation guide. It is an implementation-focused design note for integrators and engineers. It describes how the plugin works today based on the code under examples/openclaw-plugin, not a future refactor target.
examples/openclaw-plugin is not a narrow "memory lookup" plugin. It is an integration layer that spans the OpenClaw lifecycle.In the current implementation, the plugin plays four roles at once:
context-engine: implements assemble, afterTurn, and compactbefore_prompt_build, session_start, session_end, agent_end, and before_resetThe diagram above reflects the current implementation boundary:
OpenVikingClient, which centralizes X-OpenViking-* headers and routing logs.viking://user/*, viking://agent/*, and viking://session/*.That split lets OpenClaw stay focused on reasoning and orchestration while OpenViking becomes the source of truth for long-lived context.
The plugin does not send one fixed agent ID to OpenViking. It tries to keep OpenClaw session identity and OpenViking routing aligned.
The main rules are:
sessionId directly when it is already a UUIDsessionKey when deriving a stable ovSessionIdX-OpenViking-Agent per session, not per processplugins.entries.openviking.config.agent_prefix is non-empty, prefix the session agent as <agent_prefix>_<sessionAgent>mainX-OpenViking-Agent on OpenViking requests, including startup health checksX-OpenViking-Account / X-OpenViking-User when accountId / userId are explicitly configuredThis matters because the plugin is built to support multi-agent and multi-session OpenClaw usage without mixing memories across sessions.
The recommended remote-mode configuration only needs:
baseUrlapiKeyagent_prefixIn this setup:
apiKey should usually be a user keyaccountId / userId are advanced options only when the deployment needs explicit identity headers, such as root-key or trusted-server flowsisolateUserScopeByAgent / isolateAgentScopeByUser must match the server-side account namespace policy when using the PR #1356 canonical namespace modelagentScopeMode is a deprecated compatibility alias for older hash-based routing and should only be used against older serversFor OpenViking servers that include PR #1356, the plugin no longer treats agent or user scope as a locally computed hash. Instead it expands shorthand aliases into canonical URIs using the configured namespace policy:
viking://user/memories
viking://user/<user_id>/memories when isolateUserScopeByAgent=falseviking://user/<user_id>/agent/<agent_id>/memories when isolateUserScopeByAgent=trueviking://agent/memories
viking://agent/<agent_id>/memories when isolateAgentScopeByUser=falseviking://agent/<agent_id>/user/<user_id>/memories when isolateAgentScopeByUser=trueThe plugin cannot auto-discover this policy today because /api/v1/system/status does not expose it. Configure the two booleans explicitly so they stay aligned with the server-side account policy.
Today the main recall path still lives in before_prompt_build:
messages or prompt.sessionId/sessionKey.viking://user/memories and viking://agent/memories in parallel.<relevant-memories> block.The reranking logic is not pure vector-score sorting. The current implementation also considers:
level == 2Session handling is the main axis of this design. In the current implementation it covers history assembly, incremental append, asynchronous commit, and blocking compaction readback.
assemble() doesassemble() is not just replaying old chat history. It reads session context back from OpenViking under a token budget, then rebuilds OpenClaw-facing messages:
latest_archive_overview becomes [Session History Summary]pre_archive_abstracts becomes [Archive Index]toolCall (input compatible: toolUse/input is normalized to toolCall/arguments)toolResultThat means OpenClaw sees "compressed history summary + archive index + active messages", not an ever-growing raw transcript.
afterTurn() doesafterTurn() has a narrower job: append only the new turn into the OpenViking session.
user / assistant capture texttoolCall / toolResult content in the serialized turn text<relevant-memories> blocks and metadata noise before captureAfter that, the plugin checks pending_tokens. Once the session crosses commitTokenThreshold, it triggers commit(wait=false):
logFindRequests is enabled, the logs include the task id and follow-up extraction detailcompact() doescompact() is the stricter synchronous boundary:
commit(wait=true) and blocks for completionlatest_archive_overviewov_archive_expand to reopen a specific archiveSo afterTurn() is closer to "incremental append plus threshold-triggered async commit", while compact() is the explicit "wait for archive and compaction to finish" boundary.
Beyond automatic behavior, the plugin exposes six tools directly:
memory_recall: explicit long-term memory searchmemory_store: write text into an OpenViking session and trigger commitmemory_forget: delete by URI, or search first and remove a single strong matchov_archive_expand: expand a concrete archive back into raw messagesov_import: import a resource or skill; defaults to resource and uses kind: "skill" for skillsov_search: search OpenViking resources and skills, especially after importing themThey serve different roles:
memory_recall gives the model an explicit follow-up search pathmemory_store is for immediately persisting clearly important informationov_archive_expand is the "go back to archive detail" escape hatch when summaries are not enoughov_import lets the agent complete explicit import requests without asking the user to remember slash commandsov_search closes the loop after import by letting the user or agent confirm and consume resources and skillsov_archive_expand is especially important because assemble() normally returns archive summaries and indexes, not the full raw transcript.
Resource and skill imports are intentionally separate because they land in different OpenViking namespaces and use different server APIs:
/api/v1/resources and land under viking://resources/.../api/v1/skills and land under viking://agent/skills/...The plugin also registers explicit slash commands for manual imports:
/ov-import ./README.md --to viking://resources/openviking-readme --wait
/ov-import ./skills/install-openviking-memory --kind skill --wait
/ov-search "OpenViking install" --uri viking://resources/openviking-readme
/ov-search "memory install skill" --uri viking://agent/skills
Resource import supports remote URLs, Git URLs, local files, local directories, and uploaded zip files. OpenViking's built-in parsers cover common documents and media such as Markdown, text, PDF, HTML, Word, PowerPoint, Excel, EPUB, images, audio, and video. Directory imports also accept common code, documentation, and config file extensions such as .py, .js, .ts, .go, .rs, .java, .cpp, .json, .yaml, .toml, .csv, .rst, .proto, .tf, and .vue.
For HTTP safety, the plugin never sends a direct local filesystem path to the OpenViking server. Local files and directories are first uploaded through /api/v1/resources/temp_upload; directories are zipped locally with a pure JavaScript zip implementation before upload.
The plugin operates exclusively in remote mode as a pure HTTP client:
baseUrl and optional apiKey come from plugin configThe OpenViking service must be deployed and running independently before the plugin can connect to it.
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:
before_prompt_build, not fully in assemble()afterTurn() already appends to the OpenViking session, but commit remains threshold-triggered and asynchronous on that pathcompact() already uses commit(wait=true), but it is still focused on synchronous commit plus readback rather than owning every orchestration concernThat distinction matters, otherwise the future design draft is easy to misread as already shipped behavior.
If you need to debug this plugin, start with these entry points.
ov-install --current-version
openclaw config get plugins.entries.openviking.config
openclaw config get plugins.slots.contextEngine
OpenClaw plugin logs:
openclaw logs --follow
OpenViking service logs:
cat ~/.openviking/data/log/openviking.log
python -m openviking.console.bootstrap --host 0.0.0.0 --port 8020 --openviking-url http://127.0.0.1:1933
ov tuiov tui
| Symptom | More likely cause | First check |
|---|---|---|
plugins.slots.contextEngine is not openviking | The plugin slot was never set, or another plugin replaced it | openclaw config get plugins.slots.contextEngine |
| Cannot connect to OpenViking service | baseUrl is wrong or the service is down | Check baseUrl in config and test connectivity manually |
| recall behaves inconsistently across sessions | Routing identity is not what you expected | Enable logFindRequests, then inspect openclaw logs --follow |
| long chats stop extracting memory | pending_tokens never crosses the threshold, or Phase 2 fails server-side | Check plugin config and ~/.openviking/data/log/openviking.log |
| summaries are too coarse for detailed questions | You need archive-level detail, not just summary | Use an ID from [Archive Index] with ov_archive_expand |
For installation, upgrade, and uninstall operations, use INSTALL.md.