@dreamor/dsh-memvault
v0.3.0
Published
DeepSeek Harness (dsh) Cordis plugin: auto-injects MemVault memories into the system prompt and auto-extracts memories at turn end.
Readme
@dreamor/dsh-memvault
Cordis plugin bridging MemVault into DeepSeek Harness (dsh).
The dsh/Cordis API surface it relies on (ctx.systemPrompt.section(),
session/event payloads, Plugin.Object resolution) was verified against
the actual deepseek-harness source, not secondhand docs.
What it does
- Auto-injection: keeps
memory://session-injectcached and registers it as a system prompt section (ctx.systemPrompt.section()), refreshed eagerly at load and again on everyturn/end. No agent tool call required. - Auto-extraction: on
turn/endwithreason.kind === 'completed', calls MemVault'snotify_responsewith both that turn's assistant text and the user's own turn text (user/messageevents withsource.kind === 'user') automatically. Passing the user's own words matters — MemVault's extractor signal words are first-person ("我偏好"/"我喜欢"), which match a user's own statement far more reliably than an assistant's restatement of it.
If you only want the raw 16 MemVault tools exposed to the model (no
auto-injection/auto-extraction), you don't need this package — dsh's own
@deepseek-ai/dsh-mcp-client does the same with zero code: each upstream
server gets its own Cordis plugin instance via cordis.patch.yml
(transport: stdio, serverName: memvault, command: <path to
memvault-proxy>).
Install
Prerequisites: a working dsh install (e.g. npx @deepseek-ai/dsh web) and the
memvault-proxy binary reachable from PATH (or point binaryPath at one —
see the override section below).
Build, then install into a dsh profile from the repo's dsh-plugin/ directory:
cd dsh-plugin
npm install # peer deps on 0.0.1-rc.1 packages need npm i --legacy-peer-deps
npm run build # tsc --strict
npx @deepseek-ai/dsh plugin --profile <profile> add "$PWD"dsh plugin add runs the equivalent of pnpm add and appends the package to
that profile's dsh.profile.bundles automatically — no manual edit of the
profile's cordis.patch.yml is needed for a default install. The plugin ships a
cordis.patch.yml that registers one memvault entry with the default config
(mode: spawn, embeddingProvider: native, …).
Verify the composed config, then start:
npx @deepseek-ai/dsh --profile <profile> --dump-config # 组合后应只有一条 id: memvault 且字段正确
npx @deepseek-ai/dsh <profile> --port 0 # 实际启动方式以你的 dsh 用法为准At startup the plugin spawns memvault-proxy on port 3778 and probes
http://127.0.0.1:3778/health until the server is ready. To confirm the
injection actually lands: start a real conversation turn, then decompress
the session log (zstd -d ~/.dsh/sessions/.../session.jsonl.zstd) and check
that request/header.data.header.system contains the injected
[MUST]/[REF] blocks.
Overriding config (local development)
To override a single field (e.g. point binaryPath at a locally built
memvault-proxy), add a bare-id override patch to the profile's
cordis.patch.yml and rewrite the whole config block — an override replaces
the entire config, it does not merge per-field:
- id: memvault
name: '@dreamor/dsh-memvault'
config:
mode: spawn
db: '~/.memvault/data.db'
port: 3778
binaryPath: /absolute/path/to/memvault-proxy
embeddingProvider: native
agentId: dsh
injectOnAssemble: true
extractOnTurnEnd: trueThe cordis.patch.yml this plugin ships wraps its entry in insert: because
the entry doesn't exist in any earlier layer. A bare - id: patch is an
override that requires the id to already exist and errors with
patch: entry "memvault" not found otherwise — an override patch must
rewrite the whole config block, it never merges per-field.
Config
| Field | Default | Meaning |
|---|---|---|
| mode | spawn | spawn manages a memvault-proxy child process; attach connects to url. |
| db | ~/.memvault/data.db | Used when mode: spawn. |
| port | 3778 | Used when mode: spawn. |
| url | '' | Used when mode: attach, e.g. http://127.0.0.1:3778/mcp. |
| binaryPath | '' (resolved from PATH) | Used when mode: spawn. |
| embeddingProvider | native | Used when mode: spawn — forced as MEMVAULT_EMBEDDING_PROVIDER in the child's env, so it can't accidentally inherit dsh's own OPENAI_API_KEY. |
| agentId | dsh | Reported to MemVault on every call. |
| injectOnAssemble | true | Toggle auto-injection. |
| extractOnTurnEnd | true | Toggle auto-extraction. |
Build
npm install
npm run build
npm testStatus
Builds clean (tsc --strict) against the real, npm-published
@deepseek-ai/[email protected], @deepseek-ai/dsh-system-prompt,
@deepseek-ai/dsh-session, and @deepseek-ai/dsh-llm packages (not mocks —
see src/index.smoke.test.ts, which mounts the plugin on a real Context
alongside the real SystemPrompt service and asserts ctx.systemPrompt.assemble()
actually contains the injected section).
Fully verified end to end against a real dsh install (npx
@deepseek-ai/dsh web, v0.1.0-rc.6). Four real bugs were found and fixed in
the course of the verification:
cordis.patch.ymlinsert vs. override patch semantics (a bareidpatch requires the id to already exist).memvault-proxyinheriting a strayOPENAI_API_KEYfrom dsh's own process env, plus a stale (pre-native-embedding) local binary.- The eager injection-cache refresh at
apply()time racingmemvault-proxy's own startup (fetch failed). mcp-client.tspermanently caching a failed connection attempt —connected ??= (...)memoized on the first (raced, failing) call, so every later call kept re-rejecting forever, never retrying even after the proxy came up.
With all four fixed, a real conversation turn's session log
(~/.dsh/sessions/.../session.jsonl.zstd, decompressed with zstd -d) shows
the actual request/header.data.header.system text containing MemVault's
injected [MUST]/[REF] memories, and a direct notify_response call
against the running memvault-proxy moved MemVault's memory count from 8 to
10 (new preference/fact memories actually saved to Inbox).
A follow-up round extended notify_response to also extract from the user's
own turn text — MemVault's extractor
signal words are first-person, so a user's own statement ("我偏好用 tabs")
matches far more reliably than an assistant's restatement of it ("你偏好用
tabs" used to be silently rejected; the signal-word list was also extended
with second/third-person mirrors). Verified against the real running
memvault-proxy: memory count went from 10 to 13 across three scenarios,
with source:user/source:assistant tags on the results.
Note for anyone installing dsh-internal packages directly: several of them
(@deepseek-ai/dsh-session, @deepseek-ai/dsh-system-prompt) have peer deps
on other 0.0.1-rc.1 packages that aren't all published yet
(@deepseek-ai/dsh-type-meta 404s as of this writing) — npm install
--legacy-peer-deps gets you the ones that matter for this package.
