Skip to content

dsh-cross-workspace-memory

Verified

dsh-cross-workspace-memory · v0.1.2 · MIT

Cross-workspace long-term memory for DeepSeek Harness: one machine-local store shared by every workspace, a compact index injected at session start, and a searchable tool for the details.

Install

dsh plugin add dsh-cross-workspace-memory

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Creators

Readme

dsh-cross-workspace-memory · 跨工作区长期记忆

CI npm npm downloads license node deps

English · 简体中文

换一个工作区,不用再从零介绍一遍自己。

dsh-cross-workspace-memory 给 DeepSeek Harness 加一层本机共享的长期记忆:偏好、事实、项目档案(路径 / 命令 / 约定)、决策理由(选 A 不选 B 及当时的约束)、上次做到哪。所有工作区共用一份存储,条目带项目标签,检索时优先本工作区。


一、它为什么长这样(先说一件反直觉的事)

这个插件的起点是「换个工作区就丢记忆,缓存命不中,白花钱」。实测之后结论要分开看,而分开看决定了插件的形状:

现象 实测 含义
system prompt 跨工作区是否相同 相同。里面没有工作区路径;cwd 是靠 runtime-context 以历史消息形式注入的,位置在用户首条消息之后 缓存前缀本来就是跨工作区可复用的,这部分没有损失
换工作区真正丢的是什么 对话里攒下的知识与状态:这次踩了什么坑、为什么这么定、改到哪了 这才是要补的东西
上下文块什么时候进历史 宿主发现上下文文本变了,就把 runtime-context 再追加一条(旧的不删)。实测 16 回合的会话:time-context 追加 14 次、runtime-context 追加 3 次 追加在尾部,前面整段的缓存照旧命中;但每追加一次,那段文本就要多付一次

由此得到三条设计约束,写死在 lib/store.js 与 lib/index.js 的注释里:

  1. 注入必须放在 prompt 尾部(systemPrompt.context,order 9000,排在 runtime-context / skill-catalog / time-context 之后),绝不碰 system prompt 头部。
  2. 注入文本必须确定性:同一份存储状态渲染出的字节永远相同,只允许绝对短日期(10-19),禁止「3 天前」「刚刚」这类会自己漂移的措辞。
  3. 注入文本在一次会话里必须基本不动:所以自动记的进度条目里,本会话自己刚写的那几条不进索引(它们就在对话里,对模型零信息量,却会让文本每轮变一次),进度条目在索引里也最多占 3 行。

违反任意一条,插件就会变成它本来要消灭的那种浪费。


二、三种喂回模型的方式

1. 会话开头:注入一份精简索引

一行一条,带类别、项目、日期:

<memory 跨工作区记忆 · 3 条 · 本机所有工作区共享>
跨工作区共享的长期笔记;细节用 memory 工具检索(索引只是摘要,别凭它猜细节)。
- [项目] plugin · dsh-cross-workspace-memory 插件:存储在本机 ~/.dsh/memory/memory.json(10-19)
- [决策] plugin · 选择 JSON 文件而非 storageDomain:storageDomain 需要 zod 做值校验(10-19)
- [进度] plugin · 装好插件并在 desktop profile 里验通(10-19)
提示:memory search / save / update / forget / list / diagnostics
  • 预算默认 1600 字符(约 600 token),超出按类别权重截断 —— preference > decision > project > fact > todo > activity。
  • 空库时整块不注入,一个字都不花。
  • 会话中途写入的新记忆不会挤进当前会话的索引:本会话自己写的进度条目被排除,其余条目也是在会话开始时就渲染好的——所以要读细节还有工具。

2. memory 工具:按需检索全文、读写

你想干什么 参数
回忆「上次做到哪 / 当初为什么这么定」 {"query":"打包 为什么 用 electron"}
记一条 {"text":"构建用 pnpm build;打包走 electron-builder --dir 先看结构","kind":"project"}
改一条 {"id":"m3f8a1c07","text":"..."}
删一条 {"action":"forget","id":"m3f8a1c07"}
看看都有什么 {"action":"list","kind":"decision"}
自检注入是否生效 {"action":"diagnostics"}

kind 支持英文与中文别名:preference/偏好、decision/决策、project/项目、fact/事实、todo/待办、activity/进度。

检索是中文整句友好的:英文按词切,中文按二字组,所以「打包为什么慢」能命中「打包用了 electron-builder」这类条目,不需要精确措辞。

3. 自动捕获:每轮结束记一条进度

每个 turn 结束写一条 activity:「用户这句话的第一句」+「用了哪些工具、各几次」+「碰了哪些文件」。于是新开一个工作区,第一条索引就写着上个工作区做到哪。

  • 每个项目只保留最近 3 条(keepActivities),不会把索引撑爆。
  • 子代理(subagent)会话默认不记,避免噪声。
  • 落盘有 1.5 秒合并窗口(同一毫秒内多次写入只落一次)。

自动捕获记的是**「做了啥」,不是「学到啥」**。真正的结论("这个 API 的返回值不是 lossless JSON","选了 A 因为 B 在沙箱里起不来")请用 memory save 显式写 —— 这类判断只有模型自己在场时知道。


三、安装

plugin_manager install_bundle  target = dsh-cross-workspace-memory

从 npm 拉包。装完新开一个会话,索引就会出现在开头。

为什么包名叫 dsh-cross-workspace-memory

dsh-memory 这个名字在 npm 上已被占用(bbnopromo 于 2026-08-13 发布了同名包,用途类似:SQLite FTS5 + 三个工具 + recall 段落)。为了不撞名、能发布,本包改用现在的名字;但 bundle 的 id 保持 dsh-memory 不变,所以从旧名升级是原地替换,配置和记忆文件都不用动。

打包与分发(维护者)

node test/store.test.mjs      # 17 pass / 0 fail
npm pack                      # → dsh-cross-workspace-memory-<version>.tgz(10 个文件,约 42 kB)
# 校验:解包后在 package/ 里再跑一次测试,确认 tarball 自包含
tar -xzf dsh-cross-workspace-memory-0.1.2.tgz -C /tmp/x && (cd /tmp/x/package && node test/store.test.mjs)

npm publish --dry-run         # 先看清单
npm publish                   # 本包 unscoped,默认 public;registry 为官方源
npm view dsh-cross-workspace-memory version dist.tarball   # 发布后核对

新包名第一次发布会先看到一个占位版本。 registry 会先建 0.0.0-stage(packument 里 stub: true,README 是 "Temporary package placeholder for staged publishing"),真实版本进入审核,通过后自动转正并把 latest 指向它。本例从 npm publish 到 npm view … version 能查到 0.1.0 隔了约 3 分钟,期间只看得到 0.0.0-stage —— 别当失败处理,更别急着改版本号重发。若长时间停在占位版,用 npm stage list 看有没有待批的 staged release(npm stage view / approve / reject <stage-id>)。

发版顺序建议:改 package.json 的 version → 更新本节数字与 CHANGELOG(如有)→ 测试 → npm publish → git tag vX.Y.Z && git push --tags。readme 会随包一起发布到 npm 页面,所以先改 README 再 publish,否则 npm 上看到的是旧文案(npm 不允许覆盖已发布版本,改文案得再发一个补丁版本)。

2FA 会让无 TTY 的发布直接失败。 账号开了两步验证(auth-and-writes)时,npm publish 必须过一次交互式验证:在真终端里它打印认证链接或提示输入 6 位码;在 CI / 管道里则直接报 EOTP,而且链接会被打码成 ***(防泄漏),连复制出来代点都做不到。两条可行路:npm publish --otp=<6位码>,或者建一个勾了 Bypass 2FA 的 granular token(注意:classic token 自 2025-11 起已不能创建、旧 token 已全部吊销;带 bypass 的 GAT 目前仍能直接发布,npm 计划 2027-01 收回这个能力,之后应改用 trusted publishing / staged publishing)。

也能发布到 GitHub Packages(只作镜像,安装方同样需要 token):

# 包名需改为 @<github账号>/dsh-cross-workspace-memory
npm publish --registry=https://npm.pkg.github.com

四、配置项

写在 cordis.patch.yml 的 config 里,全部可省略:

键 默认 说明
dir ~/.dsh/memory 存储目录(DSH_HOME 有值时跟它走)
capture true 是否自动记录每轮进度
maxIndexChars 1600 会话开头注入的字符上限
keepActivities 3 每个项目保留几条进度
maxItems 400 总条目上限,超了先删最老的进度条目,再删低权重类别

五、存储格式

单文件 ~/.dsh/memory/memory.json:

{
  "version": 1,
  "items": [
    {
      "id": "m3f8a1c07",
      "kind": "decision",
      "text": "选择 JSON 文件而非 storageDomain:它需要 zod 做值校验",
      "tags": ["dsh", "storage"],
      "project": "D:/dshproject/plugin",
      "projectName": "plugin",
      "created": 1761000000000,
      "updated": 1761000000000,
      "source": "manual"
    }
  ]
}
  • 写入是 tmp + rename 的原子替换,不会写坏。
  • 文件坏了(JSON 解析失败)不会让插件崩:loadError 记下来,工具照常回话。
  • 多实例(同时开两个 dsh)各自持有内存副本,下次写入前先重读文件,内容变了就按 id 合并,updated 大的胜出;删除不传播 —— 单机单人的场景下,这比加锁便宜得多。这里刻意不用 mtime 判断变化:时间戳分辨率不足时连续两次写会拿到相同 mtime(Windows 上就踩到过),按内容比才可靠。

六、已知边界

  1. 索引不是实时的:本会话里 memory save 写进去的东西,要等宿主重新装配上下文才可能出现在索引里(通常要新开一轮/新会话);当前会话直接用 memory search 读,别等索引。同理,本会话自己的进度条目被排除在索引外 —— 这是刻意的(见第一节约束 3)。
  2. 只记本机会话:不跨机器同步。想同步就把 memory.json 放进自己的同步盘,并把 dir 指过去。
  3. 中文检索按二字组:够用但不是分词,太短的查询(一个字)会命中偏多。
  4. 索引是摘要:故意只放一行一条。凭索引猜细节是这类插件最常见的误用 —— 工具描述里已经写明「索引只是摘要,要细节就 search」。
  5. 改完代码要重载:plugin_manager set_plugin target="include:dsh-memory" enabled=false 再 true(只改 lib/*.js 的话,热重载不会自己发生;ESM 模块有缓存)。

七、开发

node test/store.test.mjs      # 17 个用例,约 100ms

别用 node --test:本机沙箱下 node:test 默认走 pipe 子进程,会 spawn EPERM。

  • lib/store.js —— 纯逻辑,零宿主依赖(可脱离 DSH 单测)。
  • lib/index.js —— Host 侧:注入 + 工具 + 自动捕获。@deepseek-ai/dsh-tools 只能由 DSH Host 进程解析,纯 node 进程 import 不了,所以测试只覆盖 store。
  • tools/session-stats.mjs —— 解压 ~/.dsh/sessions/ 下的多帧 zstd 日志,统计各会话的回合数与上下文追加次数(本文第一节那些实测数据就是它跑出来的)。

八、延伸阅读


九、许可

MIT © 2026 Urtaome0211

选它的理由很实际:这是个零依赖、几十 KB 的小工具,MIT 是 DSH 插件生态里最常见、也最不给人添麻烦的选择——随便商用、随便改、闭源再分发都行,只要保留版权声明;真有人想把它并进 GPL 项目,也仍然兼容。如果要拿去做什么,不用先问我。

十、贡献

欢迎提 issue 和 PR。两条硬约束请先看一眼,它们不是风格偏好,是设计的根:

  1. 不许把注入挪到系统提示词或用户消息前——前缀一变,prompt 缓存全废,插件存在的意义就没了。
  2. 注入文本必须逐字节确定(绝对日期、稳定排序、排除本会话自己写的内容)。这条靠单测兜着:test/store.test.mjs 里有「两次渲染完全相同」和「不含相对时间措辞」两个用例,改 renderIndex 时它们会先叫。