Chuyển đến nội dung chính

dsh-openviking-memory

Đã xác minh

@yadsh/dsh-openviking-memory · v0.5.1 · Apache-2.0 · Giao diện web

OpenViking memory integration for DeepSeek Harness with configurable automatic context injection; derived from the official OpenViking DSH plugin.

Cài đặt

dsh plugin add @yadsh/dsh-openviking-memory

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Phát hành lên npm mà không có repository công khai. Hãy kiểm tra nội dung package trước khi cài.

Thẻ

Readme

@yadsh/dsh-openviking-memory

OpenViking memory for DeepSeek Harness: durable memory tools, skills, conversation capture and commit — with automatic profile/recall injection you can switch off entirely.

This package is a community-maintained derivative of OpenViking's official @openviking/dsh-memory-plugin. It is not maintained or endorsed by the OpenViking project.

Original upstream: https://github.com/volcengine/OpenViking/tree/main/examples/dsh-memory-plugin. Provenance is recorded in UPSTREAM.md.

What it is

A DSH bundle that connects one agent to an OpenViking server and keeps five capabilities independent of each other:

Capability What it does
Tools Mounts DSH's MCP bridge against the OpenViking stdio proxy, publishing mcp__openviking__*
Skills Serves the openviking-memory skill so the model knows how to search, read and write memory
Capture / commit Mirrors conversation turns into an OpenViking session and commits it once it grows past a token threshold
viking:// guard Denies any filesystem or shell tool call that is handed an OpenViking URI, with a hint naming the right tool
Automatic context presentation Injects the stored profile at session start and/or before each step, and runs semantic recall per step

The first four are always on. The fifth is what this fork adds control over.

Why this fork exists

Upstream always injects: every session gets the stored profile, and every step gets a recall round-trip. That is the right default, and it stays the default here. It is the wrong behaviour when you want the model to decide when memory is worth the latency and the tokens:

Now I need memory → call the OpenViking search/read tools.

autoInject: false gives exactly that. It is not "memory disabled": tools, skills, capture, commit and the URI guard all keep working, so the agent still learns from the conversation and can still recall on demand. Only the automatic context presentation branch is switched off — and with it, every profile and recall HTTP request (not "the request is made and the result dropped").

Differences from upstream

  • autoInject plus three granular knobs — injectStartupProfile, injectStepProfile, autoRecall. See the behaviour matrix below.
  • Zero-work when disabled. With autoInject: false the plugin never calls into the profile or recall machinery, so no profile or recall request is issued at all.
  • Typed, validated configuration. A Schemastery schema declares ranges and enums, and DSH refuses to load the plugin when a value is out of range instead of silently clamping it. Out-of-range defaults still resolve to the upstream behaviour.
  • A settings card in the web UI. The plugin ships a browser bundle, so its configuration is editable from the Plugins page — on this plugin's own row — without touching a patch file. See Settings card.
  • Memory per QA account. On a deployment with QA Surface mounted, each account gets its own OpenViking space, and its QA settings dialog shows what that space holds about it — read-only, because the switches that decide whether the assistant uses the memory belong to the deployment. See Per-account memory.
  • Repository-conventional package layout. The upstream .mjs sources are ported to TypeScript under src/, with a Cordis service, a @yadsh structured log file under <$DSH_HOME>/logs/dsh-openviking-memory/, and the monorepo's build/release tooling.
  • No bundler group wrapper. Upstream's bundle patch nested the runtime in a @deepseek-ai/cordis-plugin-group row with isolate: { openvikingMemory: true }. This fork uses the canonical single-row patch and provides the service from a Cordis Service subclass instead. See docs/upstream-sync.md for what that means when syncing upstream changes.
  • Dropped upstream code that no DSH caller used (cross-harness session bypass helpers, rollout-log ingestion, the doctor surface). The complete list is in SPEC.md.

Do not run both plugins at once

Remove or disable the official @openviking/dsh-memory-plugin before enabling this package. Running both in one profile means duplicate capture, duplicate recall, duplicate MCP registrations, conflicting services and duplicate skill providers.

Install

dsh plugin --profile <profile> add @yadsh/dsh-openviking-memory

--profile is required. From a checkout instead:

pnpm nx run @yadsh/dsh-openviking-memory:build
dsh plugin --profile <profile> add ./plugins/dsh-openviking-memory

Quick start

Point the plugin at your OpenViking server and keep the upstream defaults:

- insert:
    - id: dsh-openviking-memory
      name: "@yadsh/dsh-openviking-memory"
      config:
        endpoint: http://openviking:1933

Credentials are resolved in upstream's order: explicit config → OPENVIKING_URL / OPENVIKING_API_KEY / OPENVIKING_ACCOUNT / OPENVIKING_USER / OPENVIKING_PEER_ID → ~/.openviking/ovcli.conf → ~/.openviking/ov.conf.

Manual-only mode

Let the model ask for memory instead of injecting it every step:

- insert:
    - id: dsh-openviking-memory
      name: "@yadsh/dsh-openviking-memory"
      config:
        endpoint: http://openviking:1933

        # No automatic profile or recall injection.
        autoInject: false

        # Keep learning from the conversation.
        syncTurns: true
        captureAssistantTurns: true
        captureToolResults: false
Manual-only (autoInject: false)
Automatic startup profile off
Automatic per-step profile off
Automatic semantic recall off
Conversation capture on
Memory commit on
OpenViking MCP tools on
Skills on
viking:// guard on
Model-initiated recall on

Upstream-compatible mode — the default, and what you get by not writing any of these keys:

config:
  autoInject: true
  injectStartupProfile: true
  injectStepProfile: true
  autoRecall: true

Per-account memory on a QA deployment

One plugin serves every chat, so on a multi-user deployment the OpenViking space has to be split per account — otherwise recall hands one user another user's memories, and capture files one user's conversation where the next user's recall finds it.

With QA Surface (@yadsh/dsh-qa-surface) mounted, the plugin asks it who owns the session and sends that account as X-OpenViking-User. The behaviour is on by default and is the operator's: Multi-user memory → qaUserScoping on the card below, applied to sessions that are already open.

  • A chat root resolves to the account that attested it; a delegated child inherits the chat that created it.
  • A session nobody has claimed yet is left entirely alone — no profile, no recall, no capture — until the account's browser half claims it. A conversation never reads from, or writes into, a space it does not belong to. The log says qa_memory_unattributed once when a session is asked about too early and qa_memory_attributed, with the delay, when the claim lands: the first line on its own means only "not yet", and the ones that never get the second are the chats no account will own.
  • An admin viewing somebody else's chat resolves to nobody (QA Surface fails closed there), so that view neither reads nor writes memory.
  • Without a QA Surface, or with qaUserScoping: false, the plugin keeps the single deployment-wide identity it always had.

Each account's page shows what that space holds. The signed-in user's QA settings dialog gets a Память page (backed by the openvikingMemory Remote namespace): the profile the store keeps about the account, the sections it files memories under with their entries, and the conversations it has learned from.

The page is read-only. Everything on it was written by conversations themselves, and the switches that decide whether the assistant uses the memory at all are the deployment's (the card above) — a person's settings dialog is the wrong place to switch the product's memory off.

It also says when the space is not what it looks like. The plugin sends the account as X-OpenViking-User; a store running in API-key mode strips that header and answers as its own user. The page compares what it asked for with the identity the store reports (userMemoryOverview → accountApplies), and names the shared space instead of presenting other accounts' memories as this one's own.

A read that failed is reported the same way: the page shows its own sentence for the kind of failure — the store does not answer, it refuses this deployment, it could not open the account's space — and never the transport's line. What crosses the Remote boundary is a code (userMemoryOverview → failure), while the endpoint, the status and the store's own message go to the plugin log as qa_memory_overview_failed, where an operator can grep them. A call refused before it reached the plugin is answered from what the transport left in the browser (HTTP 403 reads as "this session is not let through", an aborted request as "the deployment is not answering"), and its detail goes to console.debug.

Per-account overrides of the deployment's plan still exist and are read from openviking-memory-qa-users.json under $DSH_HOME (qaUserSettingsPath overrides the path). An override can only narrow the plan, never widen it — and nothing in the browser writes that file any more: it is an operator's lever, applied the next time a session asks for its plan.

# A deployment that prefers the old shared space, or a local install that
# mounts a QA surface for other reasons:
config:
  qaUserScoping: false

Where the settings live. The configuration card opens from this plugin's row on the Host's Plugins page, and it edits the settings document of the profile that mounts this plugin. A browser reaching the deployment over the network is served that document in memory mode — the Host's own contract calls it process-local and never writable — and while its form reports that state the card says so in one sentence instead of drawing controls, so the operator configures on the machine that serves the installation. That is how the tab this card left behaved too: the provider that decides it is shared, and §6.17 of SPEC.md carries the citations, plus the one thing a live stand still owes — whether the deployed page serves that row to a non-loopback connection at all. So the card is the operator's surface in access as well as in effect: the QA page above is the face a network browser is meant to use, and it only reports what the memory holds.

There is no namespace for the card to register. Since 0.1.7 a field is an editable form field exactly when its schema node is volatile, and the profile entry id is the namespace; ctx.configForms.get('dsh-openviking-memory') is how the card reaches it. A configuration that declares no volatile knob has no form to edit, and its card renders nothing.

Switching scoping on moves the memory — and nothing moves it back. The space is chosen by the X-OpenViking-User header, so memories written before the switch were filed under the deployment-wide user and appear in no account's space afterwards. There is no merge operation in this plugin, and the QA page is read-only on purpose, so re-filing is an operator's act against the memory API. The path, in order:

  1. Read which identity the store answers with — the account page shows it (userMemoryOverview → serverIdentity). Off, that is the shared space every chat uses; on, it is the account's own.
  2. Confirm the store honours the header before moving anything. A store in api_key mode strips it and answers as the key's own user (accountApplies: false on every page), and then every write lands in that one space whatever the plugin sends — re-filing would relocate nothing until the store runs in trusted or dev mode.
  3. List what the shared space holds (the deployment's own page reads it, and the bridged mcp__openviking__* tools see it as their caller), decide what is worth keeping, and write those notes into the owning account's space.
  4. Leave the rest where it is. Turning qaUserScoping off again returns every chat to the deployment space, so the older memory becomes reachable as it was without two accounts' histories being stitched together.

Turn scoping on at the start of a deployment and there is nothing to migrate.

The header is an assertion, not a guarantee. Whether a space is really per account is the memory store's decision: its trusted and dev auth modes honour X-OpenViking-User, while api_key mode strips the header and answers as the key's own user. That is not this plugin's to fix — but it is the plugin's to report, which is why every account page says which space it is actually showing.

What is still deployment-wide. The bridged mcp__openviking__* tools are one MCP server for the whole process, and DSH's MCP client carries a single identity for it — so a model-initiated search, read or remember is issued as the deployment identity, not as the account that asked for it. A fact the model stored through a tool is therefore shared by every account on the deployment, and no account's Память page will claim it as that account's own. Automatic context (profile and recall) and everything the plugin writes for a session are per account; a tool call the model makes on its own is not. Closing that gap needs either a per-session MCP identity in DSH's MCP client or native tool implementations in this plugin.

Behaviour matrix

Configuration Startup profile Per-step profile Automatic recall
defaults yes yes yes
autoInject: false no no no
injectStartupProfile: false no yes yes
injectStepProfile: false yes no yes
autoRecall: false yes yes no
all three granular false no no no

Each granular knob is gated by autoInject, so it can narrow the master switch but never widen it. When a capability is off, the plugin issues no request on its behalf.

Configuration

Every key is optional; the default column is what resolveConfig uses when the key is absent, and it matches upstream.

Settings card

The package ships a browser bundle, so the plugin gets a card on the Plugins page, opened from its own row in the DSH web UI. It edits the plugin's dsh-openviking-memory settings namespace directly — no patch file required:

  • Sections follow the reference tables below: automatic context presentation, connection, peer identity, recall, capture and commit, the multi-user memory switch, plus an advanced group with skipSubagentSessions, the timeouts and the deprecated captureMode.
  • Writes are immediate. Toggles and selects apply on change; text and number fields commit on blur or Enter. Emptying a field clears the override, so the value falls back to the profile's composition layer — and for the connection fields that means the OPENVIKING_* environment variables and credential files stay in charge. A committed change is re-resolved and handed to the running runtime, so an edited switch reaches sessions that are already open; the bridged mcp__openviking__* tools follow on the next reload, because they are a child process whose transport is fixed when it starts.
  • Overrides are visible. A field the profile's user layer carries is marked, and a reset action clears every override in one step.
  • The card draws no chrome of its own. The Plugins page draws the row's card — its surface, heading, description line and expand control — and this bundle mounts the body inside it. The master switch therefore reads from its own autoInject toggle rather than from a header badge. Runtime diagnostics live in the plugin log under <$DSH_HOME>/logs/dsh-openviking-memory/; the card shows the configuration and nothing else, because it has no Remote face.
  • The card edits the Host's document from loopback. The settings document a card writes is the profile the Host serves, and a page that reaches the deployment over the network is served it in memory mode — process-local, and never writable, in the Host contract's own words — which this card reports as one sentence naming the state, not as a disabled form and not as an empty section. So the operator sets these switches on the machine that serves the installation, including a stand through its loopback port; the surface a network browser is meant to use is the read-only account page described in §Per-account memory. The split is deliberate: the switches below change one shared deployment (endpoint, credentials, which memory is injected), so they belong to the operator, not to whoever opens a chat.

Injection

Option Type Default Description
autoInject boolean true Master switch for automatic context presentation
injectStartupProfile boolean true Inject the stored profile once at session start
injectStepProfile boolean true Inject the profile before a step while undelivered
autoRecall boolean true Run automatic semantic recall before each step

Connection

Option Type Default Description
endpoint string (from credentials, else http://127.0.0.1:1933) OpenViking base URL
apiKey string "" Bearer token
account string "" X-OpenViking-Account header
user string "" X-OpenViking-User header
peerId string "" Explicit actor peer id; skips workspace derivation
workspacePeer boolean true Derive a peer id from the workspace
peerSource string "git" chain Peer preset (git, cwd, none) or a template such as team-{dir}

Recall and profile

Option Type Default Description
recallPeerScope "all" | "actor" "all" Search every peer of the user, or only the caller's
recallQueryExpansion "auto" | "off" "auto" Server-side query expansion (sent only when you name it)
recallTokenBudget integer 200–50000 2000 Token budget for one recall block
recallMaxContentChars integer 100–5000 500 Per-entry character cap
recallPreferAbstract boolean true Use the stored abstract instead of reading the body
recallLimit integer 1–50 10 Maximum entries per step (naming it switches the server to the client's quota table)
scoreThreshold number 0–1 0.35 Minimum relevance score
minQueryLength integer 1–64 3 Shortest prompt that triggers a recall
profileTokenBudget integer 500–50000 10000 Token budget for the profile block
recallRewrite "off" | "auto" | "client" | "server" "off" Who builds the digest
recallDedupTurns integer 0–1000 5 Turns the server de-duplicates against (0 disables)
recallContextTimeoutMs integer 0–600000 0 Hard deadline for one context request (0 derives it)
recallMaxTokens integer 64–1000000 1600 Token ceiling for a server-assembled block
recallCompressMaxBullets integer 1–50 6 Bullet cap when a digest is produced

Capture and commit

Option Type Default Description
syncTurns boolean true Capture conversation turns into the OpenViking session
captureToolResults boolean false Capture tool results as well
captureMode "semantic" | "keyword" "semantic" Accepted for compatibility; not consumed by this plugin
captureMaxLength integer 200–100000 24000 Character cap per captured turn
captureToolMaxChars integer 200–1000000 1000000 Character cap per captured tool payload
captureAssistantTurns boolean true Capture assistant turns as well as user turns
captureFilters string[] [] Sed-style filters: s/pat/rep/, d|pat| (drop), k|pat| (keep only), optionally prefixed user: / assistant:
skipSubagentSessions boolean false Leave delegated subagent sessions entirely alone
commitTokenThreshold integer 1000–1000000 20000 Commit once the session's pending tokens reach this
commitKeepRecentCount integer 0–1000 10 Recent turns a commit keeps unsummarized

Multi-user deployments

Option Type Default Description
qaUserScoping boolean true With a QA Surface mounted, keep one memory space per account; an unattributed session is left alone entirely
qaUserSettingsPath string <$DSH_HOME>/openviking-memory-qa-users.json Where the deployment's per-account plan overrides live (no browser writes it)

Transport

Option Type Default Description
requestTimeoutMs integer 1000–120000 10000 Timeout for one OpenViking request
mcpToolCallTimeoutMs integer 1000–600000 60000 Timeout for one bridged MCP tool call

Out-of-range or unknown enum values are rejected by DSH with a validation error rather than silently coerced. Values supplied through OPENVIKING_* environment variables keep upstream's clamping behaviour, because they bypass the schema.

OpenViking setup

The plugin talks to a running OpenViking server over HTTP and starts the bundled stdio MCP proxy as a child process (process.execPath, so it works inside DSH Desktop where Electron is the executable). Nothing else needs installing: the MCP bridge itself ships with DSH.

Where memories are filed is OpenViking's decision, not this plugin's. A git repository derives its peer from origin, so every clone and worktree of one repository shares one memory; a directory that is neither a repository nor marked gets no peer of its own. Upstream's ~/.openviking/ovcli.conf is the usual place to pin actor_peer_id if you want a fixed identity.

Security and privacy

  • Credentials never enter agent messages or the log file. API keys and bearer tokens are only ever sent as request headers; the plugin log records booleans (hasApiKey) and never the values.
  • Capture is a write path, not a read grant. syncTurns and its companions control what this plugin sends to your OpenViking; they never widen what a tool call may do.
  • viking:// is still guarded. The guard is registered in every mode, including autoInject: false, and denies filesystem or shell calls handed an OpenViking URI.
  • viking_forget and the other destructive tools are unchanged. This fork does not add, weaken or auto-approve any tool contract.
  • autoInject: false is not "memory disabled" — it turns off automatic context presentation only. Treat a profile you no longer want injected as something to remove in OpenViking, not something this knob hides.
  • The offline pending queue writes raw conversation payloads to ~/.openviking/pending with 0700/0600 permissions; point OPENVIKING_PENDING_DIR elsewhere if that location does not suit your threat model.

Upstream & attribution

This package is derived from OpenViking's @openviking/dsh-memory-plugin (Apache License 2.0), imported from a pinned upstream commit:

  • Project: OpenViking
  • Original package: @openviking/dsh-memory-plugin (path examples/dsh-memory-plugin)
  • Original authors: OpenViking / Volcengine contributors
  • Upstream revision: 688f78e923d2269d96c27096fe2dad10156ebdb8 (version 0.3.2)
  • License: Apache License 2.0 — see LICENSE
  • Full provenance and the list of local modifications: UPSTREAM.md
  • Upstream sync process: docs/upstream-sync.md

Files ported from upstream keep an attribution header naming the original project, and no upstream copyright or attribution notice was removed.

Compatibility

  • DeepSeek Harness >=0.1.7-rc.2 <0.2.0
  • Node.js ^22.19.0 || >=24.0.0

See compatibility.json in the installed package for the machine-readable form.

Development

pnpm nx run @yadsh/dsh-openviking-memory:build
pnpm nx run @yadsh/dsh-openviking-memory:lint
pnpm nx run @yadsh/dsh-openviking-memory:typecheck
pnpm nx run @yadsh/dsh-openviking-memory:test
pnpm nx run @yadsh/dsh-openviking-memory:verify

The product contract lives in SPEC.md.

License

Apache License 2.0, inherited from the upstream package this fork is derived from. See LICENSE.