dsh-openviking
Verified@logictan/dsh-openviking Β· v0.4.6 Β· Apache-2.0
OpenViking memory and context bundle for DeepSeek Harness
Install
dsh plugin add @logictan/dsh-openviking Confirm the layer applied with dsh --profile default --dump-config β see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Readme
OpenViking Memory for DeepSeek Harness
An installable DeepSeek Harness bundle that adds OpenViking auto-recall, session capture, viking:// URI protection, and the OpenViking MCP tool surface.
Requires an OpenViking server with
viking://~home-alias support. Recall targets the caller's own context space throughviking://~/memoriesandviking://~/skills; the uid-lessviking://user/memoriesshorthand is rejected by newer servers.
Published as @logictan/dsh-openviking.
Install
This is the DSH fork of the upstream OpenViking memory plugin. It ships inside the aggregate bundle, which is the supported way to install it:
dsh plugin --profile web add @logictan/dsh-plugins-all
dsh --profile web --dump-config # should list the openviking-memory row
Adding this package alone also works, but then the rest of the bundle's plugins are absent:
dsh plugin --profile web add @logictan/dsh-openviking
dsh plugin forwards to pnpm inside the profile directory, so the bundle has to
be a real package. Linking a source checkout (dsh plugin --profile web add packages/dsh-openviking) only works when that checkout has its own
node_modules, because Node resolves the bundle's dsh peers from the source
tree's realpath rather than from the profile.
It also needs node packages/dsh-openviking-shared/sync.mjs run first: the
shared/ modules the bundle imports are generated, not committed.
Requirements
@deepseek-ai/dsh0.1.0-rc.6,0.1.5-rc.1, or0.1.5-rc.2; stable0.1.xreleases are also admitted by the peer range- Node.js
^22.19.0or>=24 - A reachable OpenViking server
The bundle has no runtime npm dependencies. Its message structures come from
createUserMessage (@deepseek-ai/dsh-llm), its tool surface from
@deepseek-ai/dsh-mcp-client, and its skill provider from
@deepseek-ai/dsh-skill-filesystem β peerDependencies that DSH itself already
installs. Keep those packages in the host installation rather than adding
individual DSH core packages to the profile. DSH initializes profiles with
nodeLinker: hoisted and autoInstallPeers: false, then makes the host packages
available through its module fallback at boot. A missing-peer warning during
dsh plugin add alone does not prove startup is broken.
The peer range is >=0.1.0-rc.6 <0.2.0 || ^0.1.5-rc.1. The second clause
explicitly admits the 0.1.5 release candidates: semver does not include them
in the first clause merely because they compare above 0.1.0-rc.6. Other
pre-release series need separate verification. Local devDependencies and
overrides stay pinned to rc.6 to exercise the minimum supported contract.
Everything under shared/ is generated by
node packages/dsh-openviking-shared/sync.mjs β edit
packages/dsh-openviking-shared/lib/, never the vendored copies.
Troubleshooting startup
If startup reports that @deepseek-ai/dsh-llm does not export assertNever,
inspect the affected profile before changing dependencies:
dsh --version
dsh plugin --profile web why @deepseek-ai/dsh-skill
dsh plugin --profile web why @deepseek-ai/dsh-skill-filesystem
dsh plugin --profile web why @deepseek-ai/dsh-llm
Replace web with the affected profile name. Check its package.json,
pnpm-lock.yaml, and pnpm-workspace.yaml under
${DSH_HOME:-$HOME/.dsh}/profiles/<profile>/, including dependency overrides.
[email protected] imports assertNever from the old location. A profile-local
copy can shadow the newer host package and break both DSH's own skill loader
and this plugin. As of 2026-09-14, the skill and skill-filesystem npm latest
tags still point to 0.0.1 release candidates; installing either without a
version is not an upgrade strategy.
If these old packages are direct profile dependencies added only to satisfy this plugin, remove those constraints and let DSH supply its matching packages:
dsh plugin --profile web remove @deepseek-ai/dsh-skill @deepseek-ai/dsh-skill-filesystem
dsh plugin --profile web add @logictan/dsh-openviking@latest
dsh --profile web
If another plugin requires them, update that plugin's constraints instead of
removing them blindly. Keep autoInstallPeers: false and nodeLinker: hoisted
in the profile's pnpm settings. Do not patch the import in node_modules: a
mixed DSH package family can have other incompatible interfaces. Updating this
memory plugin alone does not remove conflicting profile-local packages.
Use dsh --profile <profile> to start a named profile. dsh web is an alias for
the stock web profile, not a subcommand to append after --profile.
Design notes
Why injection uses pre-step user messages, not the system prompt
Recall and profile context enter through the agent/pre-step waterfall as
durable, source-attributed user messages
(source: { kind: 'plugin:openviking-memory', β¦ }). The producer-owned kind is
required by DSH session format v4; the plugin still recognizes legacy
kind: 'plugin' messages while older sessions are replayed.
They are deliberately not added to the system prompt: a DSH preset whose
persona declares complete: true (the stock minimal preset does) restores
that persona as the sole prompt section after assembly, silently discarding
every other contribution β a system-prompt-based memory plugin loses its
context under such presets with no error. Pre-step injection also makes each
injection a session event that replays, is visible to compaction, and never
reaches request/header.
How the tool surface is mounted
mcp.mjs mounts @deepseek-ai/dsh-mcp-client on servers/mcp-proxy.mjs, the
same stdio proxy every other OpenViking memory integration starts, so the model
gets the server's full tool set instead of a hand-maintained subset and the
transport behaves identically across harnesses. Pointing the bridge straight at
the server's /mcp endpoint does not work: with stateless_http=True the
server still answers GET /mcp with an idle 200 SSE stream, and once the MCP
SDK client opens that standalone stream it stops resolving POST responses, so
tools/list never returns. The proxy owns the transport itself and is
unaffected.
The bundle's resolved connection β url, MCP url, key, account, user and auth
mode β travels to the proxy through the child environment, because DSH scrubs
credential-shaped names out of the inherited env and a subprocess cannot see the
Cordis patch. It goes with OPENVIKING_CREDENTIAL_SOURCE=env, so the proxy reads
no config file and reaches the server exactly as the runtime does, including
with no key when the runtime has none. A changed ovcli.conf therefore takes
effect for the tools when DSH restarts the bundle, as it does for the runtime.
Two consequences follow from the proxy being one process per profile:
- The actor peer is process-level. Recall, capture, and commit still resolve a peer per session from that session's workspace repository, but tool calls carry the peer resolved at boot. Set
OPENVIKING_PEER_IDwhen one process serves several repositories and you need tool calls attributed exactly. rememberis not session-scoped. The server's MCPrememberstores into its own short-lived session rather than the livedsh-<session-id>stream β the same behavior the Claude Code, Codex, and Cursor integrations have. Automatic capture still records the conversation itself.
The bridge and the skill provider are mounted last in apply(), after every
lifecycle registration, so a proxy that cannot start holds up nothing above it.
Why the skill gets its own provider
skills.mjs registers a second ctx.skills provider with
includeDefaultRoots: false and bundledSkillDir pointing at skills/. DSH
reads this host-owned bundle directly rather than asking a workspace filesystem
to resolve a path outside its root. Its existing filesystem provider still
owns the project and user skill roots; the bundle does not duplicate that
catalog or override higher-priority project skills.
Configuration
OpenViking credentials use the same resolution order as the other memory plugins:
OPENVIKING_*environment variables~/.openviking/ovcli.conf~/.openviking/ov.conf
Common environment variables:
| Variable | Purpose |
|---|---|
OPENVIKING_URL / OPENVIKING_BASE_URL |
OpenViking server endpoint |
OPENVIKING_API_KEY / OPENVIKING_BEARER_TOKEN |
Bearer credential |
OPENVIKING_ACCOUNT |
Trusted-mode account |
OPENVIKING_USER |
Trusted-mode user |
OPENVIKING_PEER_ID |
Explicit actor peer |
OPENVIKING_WORKSPACE_PEER |
Derive a peer from each DSH session workspace's git identity by default; 0 sends no peer |
OPENVIKING_RECALL_PEER_SCOPE |
all for cross-workspace recall or actor for isolation |
The patch can also carry plugin config:
- insert:
- id: openviking-memory
name: '@logictan/dsh-openviking'
config:
endpoint: http://127.0.0.1:1933
recallTokenBudget: 2000
scoreThreshold: 0.35
captureToolResults: false
skipSubagentSessions: true
commitTokenThreshold: 20000
mcpToolCallTimeoutMs: 60000
Behavior
agent/session-startinjects the OpenViking profile, the available-memory index, and the<available-skills>catalog throughagent.inject().agent/pre-stepretrieves with the current step input and appends a durable plugin message to that same step.session/eventcaptures user, assistant, and optionally tool-result messages without scraping a transcript.turn/endchecks the OpenViking pending-token threshold and commits when required.skipSubagentSessions: trueexcludes sessions marked withheader.origin: subagentfrom automatic profile, recall, capture, and commit; it defaults tofalse.syncTurns: falsestops every new write: no captured messages, no threshold or shutdown commit. Writes queued while the toggle was on are still replayed by the background drainer once the server recovers β they were captured with the toggle on. Profile injection and recall are unaffected; it defaults totrue.skillCatalog(defaulttrue) andskillCatalogTokenBudget(default1200;0also turns the catalog off) govern<available-skills>. The catalog comes from oneGET /api/v1/skills?node_limit=200call: the user's own skills first, then those shared underviking://agent/skillsminus any whose name the user also owns, each description cut to about 40 tokens. Its budget is separate fromprofileTokenBudget. When the descriptions do not fit, the catalog lists names only (with a... +N moretail if even the names do not all fit); when not even one name fits, it shrinks to a one-line count; with no skills, or a server without the endpoint, it is omitted.- Failed writes enter the shared OpenViking pending queue. A background drainer (default every 60s,
OPENVIKING_PENDING_DRAIN_INTERVAL_MS) probes the server health and replays the queue in-process, so a transient write failure recovers without restarting dsh; it does not consume the session-start retry budget. Session-start replays keep consuming retries as before. tools/pre-executedenies a DSH filesystem tool (read,glob,grep,edit,write,str_replace_editor) whose path argument is aviking://URI, pointing the model at the bridgedmcp__openviking__*tools instead. Awriteoreditunder a skill directory (viking://~/skills/...,viking://user/<id>/skills/...,viking://agent/skills/...) points atmcp__openviking__add_skillinstead, which creates or replaces a whole skill from itsSKILL.mdtext. Agrepwhose pattern isviking://text still runs.tools/post-executelets abashcommand that carries aviking://URI run unchanged and attaches a notice for the model: use the bridged tools if it meant OpenViking content, or ignore the notice when the URI is intentional data such as anovargument or an HTTP payload.
Each DSH session maps to dsh-<session-id> in OpenViking. Workspace-derived actor peers are resolved per session and sent on every session-specific request: the peer is the git identity of the session's workspace β the normalized origin URL ([email protected]:volcengine/OpenViking.git becomes github.com-volcengine-openviking), else the repository root path, that fallback keeping the older rule where every non-letter-or-digit character becomes -. Outside a git repository no peer is sent at all, and what is remembered there goes to the user-level space viking://user/<you>/memories. One repository therefore keeps one peer across subdirectories, worktrees, clones and machines, while a fork's different origin keeps it separate. DSH does not read workspace .openviking/config.json files, so a peer.id written there has no effect; pin a peer with OPENVIKING_PEER_ID instead. Memories written under the older path-derived peer stay reachable: the default recallPeerScope: all sweeps every peer under the user.
Tools
The model sees the OpenViking MCP tools under the bridge's server-qualified
names β mcp__openviking__search, mcp__openviking__read,
mcp__openviking__list, mcp__openviking__tree, mcp__openviking__grep,
mcp__openviking__glob, mcp__openviking__remember,
mcp__openviking__write, mcp__openviking__edit,
mcp__openviking__forget, mcp__openviking__add_resource,
mcp__openviking__add_skill, and the rest of whatever the connected server
advertises. The list re-syncs when the server announces a change, so a server
upgrade adds tools without a bundle release.
mcp__openviking__forget performs permanent deletion. The calling model should
use it only when the user explicitly requests deletion.
The bundle also serves the shared openviking-memory and openviking-skills
skills from skills/ through its own isolated ctx.skills provider, so the
model gets the same guidance the other integrations ship: when to search, read,
and write, and how to find, use, create, share, and migrate OpenViking skills.
Testing
npm ci # installs the exact-pinned dsh devDependencies the tests exercise
npm run check # syntax check every shipped module + package/PLUGIN_VERSION agreement
npm test # node --test *.test.mjs β runs in the repo's PR workflow
The 0.3.2 compatibility check installed the packed bundle through
dsh plugin add in isolated profiles and exercised DSH 0.1.0-rc.6,
0.1.5-rc.1, and 0.1.5-rc.2 with matching core packages. It covered Web startup
and browser loading, skill discovery/read, MCP tool calls, profile/recall
injection, user/assistant capture, resume deduplication, threshold commits, and
disposal commits. The check used a local HTTP/MCP fixture and a deterministic
model adapter on macOS with Node.js 26.5.0; it verifies harness integration, not
real-server memory extraction. The rc.2 CLI also exited cleanly on SIGTERM.
The startup failure from issue #4944 was reproduced by adding the reported
old skill packages to an rc.2 profile; removing those direct dependencies and
reinstalling the bundle restored the same integration checks. A clean rc.2
profile with the published 0.3.0 bundle did not reproduce that import error.
live-recall.test.mjs is an opt-in end-to-end gate against a real OpenViking
server: it stores a sentinel memory through a session commit, waits for
extraction, and asserts recall returns that sentinel β the property no stub
can certify. Enable it with OPENVIKING_E2E=1 plus the normal credential
chain; it skips otherwise (including in CI until a server secret exists).