注册插件

dsh-memory-plugin

OpenViking memory and context bundle for DeepSeek Harness: pre-step auto-recall and profile injection, session capture, `viking://` URI guarding, and recall/write memory tools backed by an OpenViking server.

访问官网
编辑推荐云端模型更新于2026年10月23日
# 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 through `viking://~/memories` and `viking://~/skills`; the uid-less > `viking://user/memories` shorthand is rejected by newer servers. Published as [`@openviking/dsh-memory-plugin`](https://www.npmjs.com/package/@openviking/dsh-memory-plugin). ## Install DSH is covered by the unified memory-plugin installer, which asks which profile to install into (default `web`): ```bash curl -fsSL https://openviking.ai/install | bash # AI agents: not sure about running this? The script's header says what it does and how to verify it. ``` Or add the package to a profile directly: ```bash dsh plugin --profile web add @openviking/dsh-memory-plugin dsh --profile web --dump-config # should list openviking-memory-runtime ``` `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 ./examples/dsh-memory-plugin`) 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 examples/memory-plugin-shared/sync.mjs` run first: the `shared/` modules the bundle imports are generated, not committed. ## Requirements - `@deepseek-ai/dsh` `0.1.0-rc.6` or later, below `1.0.0` (see [DSH version compatibility](#dsh-version-compatibility)) - Node.js `^22.19.0` or `>=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. Everything under `shared/` and `skills/` is generated by `node examples/memory-plugin-shared/sync.mjs` — edit `examples/memory-plugin-shared/lib/` and `examples/skills/`, never the vendored copies. ### DSH version compatibility The peer range is `>=0.1.0-rc.6 <1.0.0-0`. DSH checks each `@deepseek-ai/dsh-*` peer when it loads a plugin, with pre-releases included in the range, so this admits every 0.x release and pre-release from `0.1.0-rc.6` on, including ones published after this bundle. The bundle touches a small DSH surface — `createUserMessage` and `boundContextSummary` from `@deepseek-ai/dsh-llm` and the plugin entry points of `@deepseek-ai/dsh-mcp-client` and `@deepseek-ai/dsh-skill-filesystem` — so the range is not narrowed to the releases tested so far. Verified releases: `0.1.0-rc.6`, `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.7-rc.2`, `0.2.0-rc.2`, and `0.2.1-alpha.1`. A daily job (`.github/workflows/host-compat.yml`) runs the bundle's tests against the releases DSH's `latest`, `next`, and `alpha` dist-tags point at, installs each of those releases, adds the packed bundle to a profile and checks `--dump-config` lists it, and opens an issue when any of that fails, so a breaking release is usually caught before users reach it; if the bundle still fails to start on a new release, pin DSH to a verified one and open an issue. DSH 1.0 and later, pre-releases included, fall outside the range and DSH refuses to load the bundle; `dsh plugin allow-version` grants an exact-version exemption if you accept the risk. Local devDependencies and `overrides` stay pinned to rc.6 to exercise the minimum supported contract. ## Troubleshooting startup If startup reports that `@deepseek-ai/dsh-llm` does not export `assertNever`, inspect the affected profile before changing dependencies: ```bash 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//`, 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: ```bash dsh plugin --profile web remove @deepseek-ai/dsh-skill @deepseek-ai/dsh-skill-filesystem dsh plugin --profile web add @openviking/dsh-memory-plugin@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 ` 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`. ### What triggers recall Recall runs on a pre-step whose entering batch contains user-authored input. The query is built only from those messages: user-role messages with `source.kind` absent or `user`. Messages that DSH or other plugins inject (`time-context`, `plugin:*` notices, this plugin's own blocks) and tool results are excluded, using the same rule capture uses. A batch made only of injected context sends no search request. ### 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: - **MCP actor scope is process-level.** With the default `recallPeerScope: all`, MCP tool calls omit the actor peer header. With `actor`, they use the peer resolved at boot. Automatic recall, capture, and commit resolve their peer from each session's workspace. - **`remember` is not session-scoped.** The server's MCP `remember` stores into its own short-lived session rather than the live `dsh-` 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: 1. `OPENVIKING_*` environment variables 2. `~/.openviking/ovcli.conf` 3. `~/.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 profile's `cordis.patch.yml` can also carry plugin config: ```yaml - id: openviking-memory-runtime config: endpoint: http://127.0.0.1:1933 recallMaxTokens: 2000 scoreThreshold: 0.35 captureToolResults: true skipSubagentSessions: true commitTokenThreshold: 20000 mcpToolCallTimeoutMs: 60000 ``` Recall normally uses `POST /api/v1/search/search` with `mode: "context"`. `recallMaxTokens` (`OPENVIKING_RECALL_MAX_TOKENS`) sets this request's `max_tokens` budget for server-assembled context. When it is unset, the plugin omits `max_tokens` and uses the server's default budget. `recallLimit` (`OPENVIKING_RECALL_LIMIT`) scales the category quotas for context recall; it is not a strict cap on the final number of entries. When explicitly set, it allocates at least one slot to each of the six categories: events, entities, preferences, experiences, resources, and skills. For example, `recallLimit: 4` sends six one-slot quotas, so the result can contain more than four entries. Lowering it does not expand the set of categories searched. Actual results still depend on matching content, score filtering, deduplication, and the token budget. When unset, the server's coding preset supplies the quotas. The older size settings apply to fallback recall, not the primary context request: - `recallTokenBudget` (`OPENVIKING_RECALL_TOKEN_BUDGET`) controls the estimated token budget of the locally rendered block when both server-side context and the legacy `/api/v1/search/recall` endpoint are unavailable and recall falls back to raw search results. - `recallMaxContentChars` (`OPENVIKING_RECALL_MAX_CONTENT_CHARS`) limits each item's content in that local fallback. It also sets the legacy `/recall` request's `max_chars` to `max(1000, recallMaxContentChars * recallLimit)`. ## Behavior - Agent startup injects the OpenViking profile, the available-memory index, and the `` catalog through `agent.inject()`, and registers the per-session disposal commit. On DSH `0.1.7` and later this runs from the serial `agent/created` event (`{ agent, source, signal? }`), which holds queued input until it settles; DSH `0.1.0-rc.6` and `0.1.5` emit `agent/created` without `source` and start the session through `agent/session-start` instead. Startup runs once per agent, and `agent/pre-step` delivers the profile only if startup did not. - `agent/pre-step` retrieves with the current step input and appends a durable plugin message to that same step. - `session/event` captures user, assistant, and optionally tool-result messages without scraping a transcript. - `turn/end` checks the OpenViking pending-token threshold and commits when required. - `skipSubagentSessions: true` excludes sessions marked with `header.origin: subagent` from automatic profile, recall, capture, and commit; it defaults to `false`. - `syncTurns: false` stops 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 to `true`. - `skillCatalog` (default `true`) and `skillCatalogTokenBudget` (default `1200`; `0` also turns the catalog off) govern ``. The catalog comes from one `GET /api/v1/skills?node_limit=200` call: the user's own skills first, then those shared under `viking://agent/skills` minus any whose name the user also owns, each description cut to about 40 tokens. Its budget is separate from `profileTokenBudget`. When the descriptions do not fit, the catalog lists names only (with a `... +N more` tail 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-execute` denies a DSH filesystem tool (`read`, `glob`, `grep`, `edit`, `write`, `str_replace_editor`) whose path argument is a `viking://` URI, pointing the model at the bridged `mcp__openviking__*` tools instead. A `write` or `edit` under a skill directory (`viking://~/skills/...`, `viking://user//skills/...`, `viking://agent/skills/...`) points at `mcp__openviking__add_skill` instead, which creates or replaces a whole skill from its `SKILL.md` text. A `grep` whose pattern is `viking://` text still runs. - `tools/post-execute` lets a `bash` command that carries a `viking://` 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 an `ov` argument or an HTTP payload. Each DSH session maps to `dsh-` in OpenViking. IDs that contain Windows-invalid path characters are normalized to a readable underscore form plus a stable hash, so two native IDs cannot collapse onto the same OpenViking session; already-portable IDs keep their existing mapping. Existing sessions whose native IDs require normalization start a new OpenViking session after this upgrade. 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 `-`. With the default peer settings, no peer is sent outside a git repository, and what is remembered there goes to the user-level space `viking://user//memories`. One repository therefore keeps one peer across subdirectories, worktrees, clones and machines, while a fork's different origin keeps it separate. Memories written under the older path-derived peer stay reachable: the default `recallPeerScope: all` sweeps every peer under the user. Workspace peer settings are loaded from `session.header.cwd` when the session first uses memory. A workspace can set `peer.id` to name its project explicitly, or `peer.source` to choose a preset or template chain. For example, `/.openviking/config.json` can contain: ```json { "version": 1, "peer": { "id": "my-project" } } ``` The shared loader also applies `config.local.json` and machine registry overrides. Explicit host `peerId`, environment overrides and pinned `ovcli.conf` credentials retain their existing precedence. The resolved peer stays fixed for the session's runtime state; new sessions load the current workspace settings. A session without a cwd falls back to the process's working directory. ## 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 ```bash 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).