OpenViking × DeepSeek Harness 记忆插件接入指南:为 dsh 赋予跨项目长期记忆
OpenViking × DeepSeek Harness 记忆插件接入指南:为 dsh 赋予跨项目长期记忆
OpenViking 为 AI Agent 提供统一的自进化上下文数据库,而 DeepSeek Harness(dsh)作为 DeepSeek 官方的 Agent 开发工具,可以通过 @openviking/dsh-memory-plugin 插件获得跨项目、跨会话的长期记忆能力。本文以 docs/zh/agent-integrations/17-dsh.md 为骨架,结合 examples/dsh-memory-plugin 下的源码实现,完整讲解该插件的安装、验证、配置、运行机制与常见问题排查,让你能直接在自己的 DSH 环境中复现「每次对话自动召回相关记忆、自动捕获新内容,且模型直接拿到 OpenViking 工具与 openviking-memory 技能」的完整链路。
一、插件能做什么
dsh 插件以 Cordis 插件的形式跑在 DSH 进程内(而非外挂 hook),因此能紧密贴着会话生命周期工作,安装后无需额外配置即可获得四项核心能力:
- 自动召回:每个模型步骤(pre-step)前用当前输入做语义检索,把结果作为持久消息追加到同一步骤;
- 自动捕获:直接从 DSH 的事件流捕获 user、assistant 以及(可选的)工具结果消息,待同步 token 超过阈值即 commit,并保留最近十条消息在本地上下文中;
- 工具面直通:模型看到的是 OpenViking 的完整 MCP 工具集,以
mcp__openviking__*前缀发布,无需手工维护工具子集; - 技能注入:附带共享的
openviking-memory技能,让模型知道何时该检索、读取和写入。
插件源码位于 examples/dsh-memory-plugin,以 npm 包 @openviking/dsh-memory-plugin 发布,运行时没有任何 npm 依赖——它的消息结构来自 @deepseek-ai/dsh-llm 的 createUserMessage,工具面来自 @deepseek-ai/dsh-mcp-client,技能提供来自 @deepseek-ai/dsh-skill-filesystem,这些 peerDependencies 由 DSH 自身安装,无需向 profile 额外添加任何东西(见 examples/dsh-memory-plugin/README.md)。
二、安装:两种方式任选
方式一:统一安装器(推荐)
DSH 与其他记忆插件(Claude Code、Codex、OpenCode、pi 等)共用同一个安装器。它会依次询问语言(English/中文)、要安装的 harness、下载源和 OpenViking 凭据;每一步都是幂等的,重复运行完全安全。
bash <(curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/memory-plugin-shared/install.sh)
GitHub 访问困难的地区,可以从火山引擎 TOS 镜像运行同一个安装器(或在下载源选项里选「TOS 镜像」):
bash <(curl -fsSL https://ovrelease.tos-cn-beijing.volces.com/memory-plugin-shared/install.sh)
选择 DSH 后,安装器会询问装到哪个 profile,默认 web;也可以用 --dsh-profile <name> 提前指定。
安装完成后用一段时间,开一个新会话问问之前提过的事情——它会记得。
方式二:手动安装
-
配置连接:写
~/.openviking/ovcli.conf(含url、api_key,可选account/user),或设置OPENVIKING_URL和OPENVIKING_API_KEY环境变量。如果使用纯本地模式(http://127.0.0.1:1933,无鉴权),这一步可以跳过——插件默认就指向本地。 -
把插件装进 profile:
dsh plugin --profile web add @openviking/dsh-memory-plugindsh plugin会转发给 profile 目录下的 pnpm,所以任何 profile 名都可以;web是dsh首次运行时自动创建的那个。注意:从源码 checkout 目录直接链接(dsh plugin --profile web add ./examples/dsh-memory-plugin)仅在 checkout 自带node_modules时可用,因为 Node 会从源码树的 realpath 解析 bundle 的 dsh peers,而不是从 profile 解析。 -
确认 profile 已生效:
dsh --profile web --dump-config输出里应该能看到
openviking-memory插件组。
还没有
ovcli.conf?见部署指南 → CLI。卸载:
dsh plugin --profile web rm @openviking/dsh-memory-plugin。
三、验证:启动、看注入、查召回
启动 dsh --profile web 打开一个会话,按以下三点确认插件生效:
- 上下文注入:会话开头应该能看到一条 OpenViking 上下文注入(形如
<openviking-context source="profile">的画像块); - 工具可见:模型应该具备
mcp__openviking__*工具; - 召回生效:问一句更早会话里聊过的事,确认召回结果被注入。
如果什么都没有,设置 OV_DEBUG_LOG=/tmp/ov-dsh.log 后查看该文件。
从源码看,注入发生在 agent/session-start 与 agent/pre-step 两个事件上:index.mjs 中 injectStartupProfile 在会话启动时注入画像块,agent/pre-step 监听器以 { prepend: true } 注册,确保在其他贡献者之后追加画像与召回结果,且 profile 与 recall 通过 Promise.all 并发执行以减少瀑布等待时间。
四、工作方式:事件驱动、会话映射与工具面
4.1 生命周期与事件挂钩
插件的 apply() 在 index.mjs 中注册了完整的事件监听:
| DSH 事件 | 插件行为 |
|---|---|
agent/session-start | 注入 OpenViking 画像块与可用记忆索引 |
agent/pre-step | 用当前步骤输入做语义检索,把结果作为持久消息追加到同一步骤 |
session/event | 捕获 user、assistant 及(可选的)工具结果消息,并检查 token 阈值决定是否 commit |
session/flush | 冲刷该会话的待写队列 |
tools/pre-execute | 拦截把 viking:// URI 当本地路径的 filesystem/shell 调用 |
注入选择 pre-step 用户消息而非 system prompt,是刻意设计:DSH 的 preset 若声明了 complete: true(如自带的 minimal preset),会在组装阶段把 persona 恢复为唯一 prompt 区块,静默丢弃其他贡献——基于 system prompt 的记忆插件在这种 preset 下会无提示地丢失上下文。而 pre-step 注入让每次注入都成为可重放、对压缩可见的会话事件(见 examples/dsh-memory-plugin/README.md 的设计说明)。
4.2 会话映射与 peer
每个 DSH 会话映射为 OpenViking 中的 dsh-<session-id>,子 agent 各自拥有独立会话。对应关系由 shared/session-model.mjs 的 deriveHarnessSessionId("dsh-", session.id) 生成,子 agent 会话可通过 skipSubagentSessions: true 排除(匹配 header.origin: subagent)。
4.3 MCP 工具面
模型看到的工具面就是 OpenViking 的 MCP 工具集,经由与其他记忆集成相同的 stdio 代理 servers/mcp-proxy.mjs 接入,以 mcp__openviking__ 前缀发布,包括 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 以及服务器声明的其余工具。工具列表会在服务器通告变更时重新同步,因此服务器升级新增工具无需发新版 bundle。
为什么不直接让 DSH 连接服务器的 /mcp 端点?因为服务器在 stateless_http=True 时仍会以空闲 200 SSE 流响应 GET /mcp,一旦 MCP SDK 客户端打开这条独立流就不再解析 POST 响应,tools/list 永远不会返回;代理自己掌握传输层,不受影响(见 README 设计说明)。
由于代理每个 profile 只起一个进程,有两个直接后果:
- actor peer 是进程级的:召回、捕获、commit 仍按每个会话从该会话的工作区仓库解析 peer,但工具调用携带的是启动时解析的 peer。若一个进程要服务多个工作区且需要精确归属工具调用,请显式设置
OPENVIKING_PEER_ID; remember不是会话级的:服务端 MCP 的remember写入的是服务端一个短生命周期的会话而非当前的dsh-<session-id>流(对话本身仍由自动捕获记录)。
MCP 桥接(mountOpenVikingMcp)与技能提供者(mountOpenVikingSkills)在 apply() 中最后挂载且不阻塞等待——因为桥接会阻塞在首次 tools/list 上,这样即使服务器连接受理但迟迟不响应,也不会拖住上面所有生命周期注册(见 index.mjs 注释)。
4.4 URI 拦截
误把 viking:// URI 当本地路径的文件或 shell 调用会被 tools/pre-execute 拦截(guardVikingUri),并提示改用对应的 OpenViking 工具。viking:// 是虚拟数据库路径而非文件系统路径,这一保护在 uri-guard.mjs 中实现。
4.5 写入失败重放
写入失败会进入共享的 OpenViking 待写队列(shared/pending-queue.mjs),在下次会话开始时重放(replayPending)。syncTurns: false 时重放同样被禁用——队列中的写入会一直保留,直到某个仍在写入的会话把它们排空。对应逻辑见 runtime.mjs 的 initializeState 与 enqueuePending。
五、配置详解
5.1 凭证解析顺序
凭证解析顺序为 OPENVIKING_* 环境变量 → ~/.openviking/ovcli.conf → ~/.openviking/ov.conf,与 Claude Code、Codex、OpenCode、pi 共用同一条链路(见 shared/credentials.mjs);这些文件变更后会自动重载。ov.conf 的 server 段(url/host/port/root_api_key)与 codex 段也会被读取。patch 中写的凭证优先于环境变量;行为开关则优先读环境变量。
| 环境变量 | 默认值 | 说明 |
|---|---|---|
OPENVIKING_URL / OPENVIKING_BASE_URL | http://127.0.0.1:1933 | 服务端点 |
OPENVIKING_API_KEY / OPENVIKING_BEARER_TOKEN | — | API Key(以 Authorization: Bearer 发送) |
OPENVIKING_ACCOUNT / OPENVIKING_USER | — | 可信模式下的 account 与 user |
OPENVIKING_PEER_ID | — | 显式指定 actor peer |
OPENVIKING_WORKSPACE_PEER | true | 按每个会话的工作区推导 peer;设为 0 则不发送 peer |
OPENVIKING_RECALL_PEER_SCOPE | all | 设为 actor 可将召回限制在当前工作区 |
OV_DEBUG_LOG | — | 把调试日志写到该路径 |
MCP 代理的凭证通过子进程环境传递:DSH 会从继承的 env 中清洗凭证形态的变量名,且子进程看不到 Cordis patch,所以 mcp.mjs 的 buildMcpConfig 要把运行时已解析的 endpoint/apiKey/account/user/peerId 显式写入代理的子进程 env(DSH Desktop 下还需设置 ELECTRON_RUN_AS_NODE=1,让 Electron 以 Node 方式运行代理脚本)。
5.2 行为参数:Cordis patch
行为参数写在 profile 的 Cordis patch 条目里(模板见 cordis.patch.yml):
- insert:
- id: openviking-memory
name: '@deepseek-ai/cordis-plugin-group'
group: true
isolate:
openvikingMemory: true
config:
- id: openviking-memory-runtime
name: '@openviking/dsh-memory-plugin'
config:
endpoint: http://127.0.0.1:1933
recallTokenBudget: 2000
scoreThreshold: 0.35
captureToolResults: false
skipSubagentSessions: true
commitTokenThreshold: 20000
mcpToolCallTimeoutMs: 60000
所有配置项的默认值与边界约束都在 config.mjs 的 DEFAULT_CONFIG 与 clamp* 函数中定义,关键参数如下:
| 参数 | 默认值 | 约束范围 | 说明 |
|---|---|---|---|
endpoint | http://127.0.0.1:1933 | — | 服务端点,结尾多余的 / 会被去除 |
recallTokenBudget | 2000 | 200–50000 | 单次召回的 token 预算 |
scoreThreshold | 0.35 | 0–1 | 召回相似度阈值 |
recallLimit | 10 | 1–50 | 召回结果条数上限 |
recallMaxContentChars | 500 | 100–5000 | 单条召回内容最大字符数 |
recallPreferAbstract | true | — | 优先返回摘要而非全文 |
minQueryLength | 3 | 1–64 | 最短查询长度(低于此值不触发召回) |
profileTokenBudget | 10000 | 500–50000 | 画像注入的 token 预算 |
commitTokenThreshold | 20000 | 1000–1000000 | 触发 commit 的 pending token 阈值 |
commitKeepRecentCount | 10 | 0–1000 | commit 时保留在本地上下文的最近消息数 |
captureMode | semantic | semantic/keyword | 捕获模式 |
captureMaxLength | 24000 | 200–100000 | 单条捕获内容最大长度 |
captureToolResults | false | — | 是否捕获工具结果消息 |
captureAssistantTurns | true | — | 是否捕获 assistant 轮次 |
skipSubagentSessions | false | — | 是否跳过 origin: subagent 的子 agent 会话 |
requestTimeoutMs | 10000 | 1000–120000 | 普通请求超时 |
mcpToolCallTimeoutMs | 60000 | 1000–600000 | MCP 工具调用超时 |
syncTurns | true | — | 见下文「只读模式」 |
5.3 只读模式:syncTurns: false
同一个 config 块里的 syncTurns: false 让该集成变成只读:画像注入和记忆召回照常,但什么都不再写回——不捕获对话、不 commit,也不重放此前会话排入队列的写入,那些写入会一直留在队列里,直到某个仍在写入的会话把它们排空。源码层面,runtime.mjs 的 capture()、maybeCommit()、dispose() 均在开头检查 state.config.syncTurns,测试 runtime.test.mjs 的 syncTurns false sends nothing 用例验证了「无捕获、无 commit、无 dispose flush、无重放」的完整行为。
5.4 工作区 peer:peerSource
同一个 config 块里的 peerSource 决定工作区 peer 的派生方式(实现见 shared/workspace-peer.mjs):
"git"(默认):取仓库归一化后的originURL(git@github.com:volcengine/OpenViking.git得到github.com-volcengine-openviking),其次是仓库根路径,因此同一个仓库的每个 clone、worktree 和子目录共用同一个 peer;不在仓库中则完全不发送 peer,在那里记下的内容进入用户级空间viking://user/<you>/memories;"cwd":恢复此前的行为——把工作目录路径中的非字母数字字符全部替换成-;"none":完全不发送 peer。
peerSource 也接受模板字符串(如 "team-{dir}")或模板数组;拼写错误的 preset 名(不含 { 的裸字符串)会被警告并回退到默认链,避免产生一个无人预期的命名空间。
注意:DSH 不会读取工作区的 .openviking/config.json,所以写在里面的 peer.id 不生效;要让仓库之外的目录拥有独立记忆,请为它设置 OPENVIKING_PEER_ID(详见让一个目录拥有独立记忆)。旧版基于路径的 peer 下写入的记忆仍可被召回到:默认的 recallPeerScope: all 会扫遍用户下的所有 peer,且 resolveEffectivePeerId 返回的 legacyPeerId 会让召回同时命中旧命名空间。
5.5 技能注入
插件通过自己的独立 ctx.skills 提供者(includeDefaultRoots: false)只挂载 skills/openviking-memory/SKILL.md,不会遮蔽或复制 DSH 自带的 filesystem 技能目录。技能内容指导模型:会话生命周期(启动注入、任务中检索、有持久信息时写入、结束时自动捕获 commit)、检索工具选择(search 的 context 模式优先、find 快速三选、grep/glob 精确匹配)、写入纪律(remember 只用于用户明确要求保留或明确的持久事实;forget 是永久删除,仅当用户明确要求时使用;viking:// URI 是虚拟路径,绝不能传给 filesystem 工具)。
六、版本与运行前提
从 examples/dsh-memory-plugin/README.md 的 Requirements 可以确认:
@deepseek-ai/dsh需要0.1.0-rc.6或更新的0.1.x版本(peer 范围止于0.2.0之前);- Node.js 需要
^22.19.0或>=24; - 需要一个可达的 OpenViking 服务器,且服务器须支持
viking://~home-alias(召回面向调用者自身的viking://~/memories与viking://~/skills空间)。
自测方式:在插件目录运行 npm ci 安装精确锁定的 dsh devDependencies、npm run check 做语法与版本一致性检查、npm test 运行全部单元测试;live-recall.test.mjs 是可选的真服务器端到端闸门,用 OPENVIKING_E2E=1 加正常凭证链启用——它通过会话 commit 存入哨兵记忆,等待抽取后断言召回能取回该哨兵,这是任何 stub 都无法验证的性质。
七、常见问题排查
| 现象 | 排查方向 |
|---|---|
| 没有注入,也没有 OpenViking 工具 | dsh --profile web --dump-config 里应能看到 openviking-memory;重新运行安装器或 dsh plugin --profile web add … |
| 装到了错误的 profile | 安装器默认 web;用 --dsh-profile <name> 重新运行 |
安装时报 ERESOLVE | @deepseek-ai/dsh-* 各包预发布 tag 不同步;请精确安装 @deepseek-ai/dsh@0.1.0-rc.6 |
| 安装时报包「不在 npm registry 中」 | pnpm 默认拒绝发布不满 24 小时的版本(minimumReleaseAge)。等一等,或把该精确版本加进 profile 的 pnpm-workspace.yaml 的 minimumReleaseAgeExclude |
| 召不回任何内容 | curl http://localhost:1933/health;检查端点配置,以及 prompt 是否长于最小查询长度(3 个字符) |
| OpenViking 返回 401 / 403 | 检查 OPENVIKING_API_KEY;可信模式部署还要检查 OPENVIKING_ACCOUNT 与 OPENVIKING_USER |
| 串入了其他项目的记忆 | 设置 OPENVIKING_RECALL_PEER_SCOPE=actor |
| 崩溃后没有 commit | commit 由 token 阈值和 teardown 触发;排队的写入会在下次会话开始时重放 |
八、延伸阅读
- 集成能力参考:各 harness 集成能力的横向对照
- 客户端配置参考:peer 派生、
peer.id与工作区记忆隔离的完整说明 - 部署指南:OpenViking 服务器与 CLI 凭据的部署方式
- 插件源码:examples/dsh-memory-plugin,包含全部
.test.mjs行为测试 - 共享安装器与共享库:examples/memory-plugin-shared,
shared/与skills/下的文件由node examples/memory-plugin-shared/sync.mjs生成
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)