Skip to content

About

把 @restregular/dsh-memory 的记忆库暴露成独立 MCP 服务,让 Trae / WorkBuddy 等任意 MCP 客户端与 DSH 共用同一份长期记忆。

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

dsh-memory-mcp

让 DSH 的长期记忆跨 IDE 共享 —— Trae / WorkBuddy / ZCode / 任何支持 MCP 的 agent 都能读能写同一份记忆。

🤖 给 AI agent 的入口:如果你是 agent 而不是人,先读 AGENTS.md(动手前必须知道的事实与不变量); 需要让别的 agent/工具发现本仓库,用 llms.txt。 本文是写给人的完整说明,可以按需跳读。

@restregular/dsh-memory 是一个只服务于 DeepSeek Harness 的插件: 它的记忆库、召回注入、管理面板都长在宿主里,离开 DSH 就完全用不上。

本项目把同一套记忆能力抽出来做成一个独立的 MCP (Model Context Protocol) 服务:

┌──────────────┐   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│  DSH 插件     │   │     Trae     │   │  WorkBuddy   │   │    ZCode     │
│ (原生面板)    │   │ (MCP 客户端)  │   │ (MCP 客户端)  │   │ (MCP 客户端)  │
└──────┬───────┘   └──────┬───────┘   └──────┬───────┘   └──────┬───────┘
       │                  │                  │                  │
       └──────────────────┴──────────────────┴──────────────────┘
                          ▼
              <store-dir>(两边必须配成同一个)
              global.jsonl + projects/<sha1>.jsonl
                    + .vec 向量边车

一处写入,多处可见。 在 Trae 里记下的偏好,回到 DSH 继续对话时 agent 也能检索到。

与上游的区别

@restregular/dsh-memory 本项目
形态 DSH 插件(cordis loader entry) 独立 MCP 服务(stdio)
可用宿主 仅 DSH Trae / WorkBuddy / ZCode / 任何 MCP 客户端,DSH 不需要开
工具数 4 个(memory_get 定义了但漏注册,见 上游 #2) 5 个(补上 memory_get)
管理面板 ✅ 设置页「记忆」 ❌ 面板仍建议在 DSH 里用
自动召回注入 ✅ 每轮自动注入 ❌ 由调用方 agent 主动调 memory_search
存储 JSONL + .vec 边车 完全相同的格式,共用同一个 storeDir
检索算法 词法 IDF + CJK bigram + 本地向量 逐条对齐(复用上游 scoring.js / scope.js)
多进程安全 ❌ 见 上游 #3 ✅ 独占锁 + 每次事务重新读盘

特点

  • 五个模型工具:memory_save / memory_search / memory_list / memory_get / memory_forget
  • 与 DSH 插件共用同一份数据(storeDir),无需导入导出,两个方向都实时互通
  • 不需要开 DSH —— IDE 自己拉起进程、自己拉起向量后端
  • 检索算法与上游逐条对齐,不是"近似实现"
  • 跨进程安全:独占锁文件 + 读-改-写在锁内重新读盘,不会覆盖别的进程刚写的记录

⚡ 不需要开 DSH

本 MCP 服务完全独立,不依赖 DeepSeek Harness 是否运行。

它是怎么启动的

由 IDE 的 MCP 配置启动 —— 不是某个脚本,也不是随 DSH 插件启动。IDE 读自己的 mcp.json,看到 dsh-memory 条目后 spawn 出这个进程:

{
  "mcpServers": {
    "dsh-memory": {
      "command": "C:\\Program Files\\nodejs\\node.exe",
      "args": ["C:\\path\\to\\dsh-memory-mcp\\src\\index.js", "--store-dir", "C:\\Users\\me\\.dsh-memory-store"]
    }
  }
}

启动后的链路:

你打开 Trae / WorkBuddy / ZCode
  └─ IDE 读 mcp.json(或 ZCode 的 ~/.zcode/cli/config.json)
       → spawn: node src/index.js --store-dir ...
            ├─ 读磁盘上的 JSONL 记忆(不等 DSH)
            └─ 探活 8199 → 没活 → 自己 spawn llama-server(向量后端)

想在任务管理器里确认它:Trae 开着时,能看到一个 node.exe 跑着 D:\dev\dsh-memory-mcp\src\index.js,命令行参数与上面 mcp.json 里写的完全一致。

三者关系

组件 谁启动它 依赖谁
DSH 记忆插件 DSH 自身的 cordis 插件系统 —
本 MCP 服务 IDE 的 MCP 配置 不依赖 DSH
llama-server 向量服务 各自 spawn(插件用 8078,MCP 用 8199) 互不干扰

三者只共用同一个数据目录(<store-dir>,DSH 插件与 MCP 必须配成同一个),记忆因此互通 —— 仅此而已, 没有父子关系、没有共享进程。

生命周期

stdio 型 MCP 不是 Windows 服务,而是 IDE 按需拉起的子进程:

场景 MCP 服务
Trae / WorkBuddy / ZCode 需要调记忆工具时 ✅ 由 IDE 拉起
只有 DSH 开着 ❌ 不存在(DSH 走自己的插件,不经过 MCP)
IDE 关闭 ❌ 随之退出,不留孤儿进程

记忆不会因为进程重启而丢失 —— 下次 IDE 拉起时读的是磁盘上同一份 JSONL。

想开机就常驻可以自己注册计划任务,但通常没必要:IDE 关掉自动回收更干净。

实测

scripts/acceptance-standalone.mjs(在 8078/8199 全空闲、无任何 llama-server 的状态下, DSH_MEMORY_EMBED_EXE / DSH_MEMORY_EMBED_MODEL 已配好):

✓ MCP 服务在无 DSH 情况下冷启动成功
✓ MCP 自己拉起了 llama-server(8199)
✓ DSH 的 8078 始终未被占用(两者互不干扰)
✓ 读到了 DSH 之前写入的记忆(命中 5 条)
✓ memory_save 在无 DSH 时成功
✓ 语义相近但措辞不同也能召回
✓ MCP 退出后向量后端随之关闭(无孤儿进程)

服务端日志:vector: 已启动 llama-server pid=35584 http://127.0.0.1:8199

⚠️ 别用端口状态反推宿主状态。「8199 没监听」只说明向量服务此刻不在跑, 不代表 DSH 没运行 —— 这个推理错误本项目犯过一次,特此记下。


目录


安装

要求 Node.js >= 20.3.0(见 package.json 的 engines)。

这个下限不是随手写的,也不是"Node 越新越好"的意思 —— 它由两个硬约束取较严者决定:

约束 要求 出处
源码里用到的 AbortSignal.any() >= 20.3.0 src/vector.js(AbortSignal.any 在 Node 20.3.0 才出现;20.2 及以前是 undefined)
依赖树里的 @hono/node-server >= 20 MCP SDK 的传递依赖

所以 20.3.0 是真实下限。低于它会在运行到向量检索时抛 AbortSignal.any is not a function, 而且那个报错不会告诉你"是 Node 太旧"—— 这是部署时第一道容易撞上的门槛。

Node 18.20 上实测整套自测也能过,但那是走运:AbortSignal.any 只在「带 signal 调 embed()」这条 运行期路径上才会执行到。靠"恰好没踩到"的兼容不算兼容,所以下限没有定在 18。

两种装法,任选一种:

# ① 从源码(推荐:能跑完整自测)
git clone https://github.com/tearslee/dsh-memory-mcp.git
cd dsh-memory-mcp
npm install

# ② 作为依赖装进别的项目 / 全局(装完就能自证)
npm install dsh-memory-mcp
npx dsh-memory-mcp --version

依赖清单:

包 版本 用途
@modelcontextprotocol/sdk 1.32.2(精确版本) MCP 协议实现(Server / StdioServerTransport / Client)

存储层(src/store.js / src/scoring.js / src/scope.js / src/scope-names.js / src/lock.js)零外部依赖,只用 Node.js 内置模块。 SDK 只在 src/server.js 里用到三行 import;其余 8 个文件对 SDK 一无所知。

npm install 后依赖树会变大(96 个包)是正常的 —— SDK 自带 express / ajv / zod 等传递依赖。

启动

# 最简:store 用默认目录(~/.dsh-memory-store),工作区 = 当前目录
node src/index.js

# 指定工作区(决定 project 作用域)
node src/index.js --project D:\dev\my-app

# 指定存储目录(**与 DSH 插件共用时必须显式给**,见下方警告)
node src/index.js --store-dir D:\dev\.dsh-memory-store

# 两者一起
node src/index.js --project D:\dev\my-app --store-dir D:\dev\.dsh-memory-store

⚠️ 默认 storeDir 是 ~/.dsh-memory-store,它是平台中立的,但不一定是你 DSH 在用的那个。 DEFAULT_STORE_DIR 只保证"任何机器上都能跑起来",不保证"跑对了库"。真正要共用的是 DSH 记忆插件的 storeDir。两者不一致时服务一切正常,只是你看到的是另一份(空的)记忆库 —— 这是本仓库最容易让人误判「没生效」的地方。

用默认值启动时,stderr 会打一条显式提示:

[dsh-memory-mcp] ℹ️  未指定 --store-dir / DSH_MEMORY_STORE_DIR,使用默认目录:C:\Users\me\.dsh-memory-store
[dsh-memory-mcp]    ⇒ 请确认它与 DSH 记忆插件的 storeDir **是同一个目录**,否则两边看到的是两份互不相通的记忆。

看到这条就说明你在用默认库。想确认对不对,最直接的办法是看那个目录里有没有 global.jsonl。

启动参数(都支持 --key value 与 --key=value 两种写法):

参数 说明 缺省
--project <dir> / --project-dir <dir> 当前工作区目录(绝对或相对)。决定 project / project:<路径> 作用域。 DSH_MEMORY_PROJECT → process.cwd()
--store-dir <dir> / --store <dir> 记忆存储目录。 DSH_MEMORY_STORE_DIR → ~/.dsh-memory-store
-h / --help 打印用法后退出(0)。 —
-v / --version 打印版本后退出(0)。 —

进程是 stdio 传输:stdout 只输出 MCP 协议消息,所有日志走 stderr。 SIGINT / SIGTERM 会优雅退出,并且只终止自己拉起的 llama-server(复用的外部服务原样保留)。

命令行与退出码

CLI 是自描述的,不需要读源码就能查用法:

$ node src/index.js --help      # 打到 stdout,exit 0
$ node src/index.js --version
dsh-memory-mcp 0.1.0

--help / --version 有两个刻意的性质:

  • 不加载 MCP SDK —— 所以 node_modules 没装、依赖缺失时,帮助照样能打出来。 这是部署排障的第一步("先确认我参数写对没")。
  • 不碰磁盘 —— 不建 store 目录、不探活向量后端。用一个不存在的 --store-dir 跑 --help, 跑完那个路径仍然不存在。
退出码 含义
0 正常退出(--help / --version;服务在 stdin 关闭后也以 0 退出)
1 启动失败(store 目录不可用、依赖缺失等),会打印可操作的中文提示
2 参数错误(未知参数、参数缺值),不会启动服务、不碰 store

两条容易踩的规则:

  • 未知参数不再被静默忽略。--store_dir、--storedir、--storeD 这类笔误以前会让服务 悄悄回退到默认 store(在别人机器上等于写到一个莫名其妙的目录),现在一律 exit 2 并指出是哪个参数写错了。
  • flag 缺值同样报错。--store-dir 后面什么都没有、或紧跟另一个 --xxx,都算缺值 —— 不会把 --help 当成目录名吃掉。

启动失败时给的是可操作的中文诊断,不是 Node 裸栈:

[dsh-memory-mcp] ❌ 启动失败:记忆存储目录不可用:Q:\nope\store
[dsh-memory-mcp]    ⇒ 原因:盘符 Q: 不存在(这台机器上没有这个盘)
[dsh-memory-mcp]    ⇒ 改 --store-dir(或环境变量 DSH_MEMORY_STORE_DIR)指向一个已存在且可写的目录;默认是 C:\Users\me\.dsh-memory-store
[dsh-memory-mcp]    原始错误:ENOENT ENOENT: no such file or directory, mkdir 'Q:\nope\store\projects'

需要完整调用栈时设 DSH_MEMORY_DEBUG=1。已覆盖的诊断:盘符不存在、父目录不存在、路径已存在且是个文件、 无写入权限、缺依赖(npm install 提示)。

验证你的部署

刚 npm install 完,别急着配 IDE —— 先用下面 5 步确认你自己这台机器是好的。 全部用临时目录,不会碰你真实的记忆库。

下面命令块是 bash / zsh 写法;PowerShell 的差异标在每行后面的注释里。 ④⑤ 里的 --store-dir 指向一个临时目录,跑完可以直接删。

# ① Node 版本够不够(要 >= 20.3.0;低于它会在向量检索时才炸,很难查)
node -v
node -p "require('./package.json').engines.node"

# ② 依赖 + 版本 + storeDir 一次查完(只读,不建目录、不碰记忆库)
node scripts/check-install.mjs
#   想连"我会落到哪个 storeDir"一起确认,就把要用的参数带上:
#   node scripts/check-install.mjs --store-dir <你的 DSH storeDir>

# ③ CLI 能自描述(--version / --help 都不加载 SDK、不建目录,依赖没装也能用)
node src/index.js --version
node src/index.js --help | head -3
#   PowerShell: node src/index.js --help | Select-Object -First 3

# ④ 实际生效的 storeDir 对不对 —— 看 stderr 首三行
#    注意 stdin 必须关掉/喂空,否则 stdio 服务会一直等输入不退出
echo '' | node src/index.js --store-dir /tmp/dsh-probe
#   PowerShell: echo '' | node src/index.js --store-dir "$env:TEMP\dsh-probe"

# ⑤ MCP 握手通不通(喂一个 initialize 给 stdin,stdout 应回一行 JSON-RPC)
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"selfcheck","version":"1.0.0"}}}' \
  | node src/index.js --store-dir /tmp/dsh-probe
#   PowerShell 用上面这条原样即可(echo 是 Write-Output 的别名,单行 JSON 没问题)

每一步该看到什么(以下输出在 Windows(Node 26)与 Linux(Node 20/22/24)上逐条实际跑过; 除路径写法以外一致)。

① 版本检查 —— engines 会原样打出来,自己比一下:

v26.3.0
>=20.3.0

② 部署自检 —— 三行绿色 ✅ 就是好的:

  ✅ Node 版本 26.3.0(要求 >= 20.3.0)
  ✅ 依赖 @modelcontextprotocol/sdk 1.32.2
     file:///C:/path/to/dsh-memory-mcp/node_modules/@modelcontextprotocol/sdk/dist/esm/index.js
  ✅ 入口 src/index.js 存在

然后它会告诉你会落到哪个 storeDir。如果用的是默认值,你会看到一条 ⚠️:

  ⚠️  storeDir = C:\Users\me\.dsh-memory-store
     ⇒ 这是**默认值**。它只保证能跑,不保证就是你 DSH 在用的那个库。

看到这条 ⚠️ 就要警惕了 —— 接着带上真实参数再跑一次,确认它认得出你的库:

node scripts/check-install.mjs --store-dir <你的 DSH storeDir>
# 期望多出一行:✓ 该目录里有 global.jsonl,看起来是一份已有的记忆库

为什么不用 npm ls @modelcontextprotocol/sdk --depth=0:从 npm 装进别的项目时 node_modules 是**平铺(hoisted)**的,本包目录下没有自己的 node_modules, npm ls 会打出 -- (empty) —— 那表示"依赖被提升到上一层",不是"没装", 很容易让人误判成安装失败。check-install.mjs 用 Node 自己的解析算法,两种布局下都准。

③ 自描述 —— 退出码 0,且 --help 第一行带版本号:

dsh-memory-mcp 0.1.0
dsh-memory-mcp 0.1.0 —— 把 DSH 的长期记忆库暴露成独立 MCP 服务(stdio)

④ 这一步最容易查出问题。前几行 stderr 就是实际生效的配置(不是你以为的那个)。 第 4 行起是向量后端的日志,因机器而异(关闭 / 复用已有服务 / 自行拉起 / 启动失败退化), 不影响判断 —— 只看前 3 行。看到 已启动 dsh-memory-mcp v0.1.0(stdio) 即启动成功:

[dsh-memory-mcp] store-dir  = C:\Users\me\AppData\Local\Temp\dsh-probe
[dsh-memory-mcp] project   = D:\dev\my-app(project 作用域 = project:D:\dev\my-app)
[dsh-memory-mcp] 默认保存作用域 = global
[dsh-memory-mcp] 已启动 dsh-memory-mcp v0.1.0(stdio)        ← 这行 = 启动成功
  • store-dir 必须等于你 DSH 插件正在用的那个目录。不相等 = 两边看到两份互不相通的库。
  • 没传 --store-dir 时会额外多打一行 ℹ️ 未指定 --store-dir … 使用默认目录,说明你正在用默认库 (~/.dsh-memory-store),多半不是你 DSH 那个。
  • 对照办法:去那个目录 ls,有没有 global.jsonl。
  • 若看到 vector: llama-server 启动失败(…),向量后端不可用,退化为纯词法检索:不是错误, 服务照常工作,只是没有语义召回。换机器后想恢复向量,按 环境变量 那张表配 DSH_MEMORY_EMBED_EXE + DSH_MEMORY_EMBED_MODEL,或直接设 DSH_MEMORY_VECTOR=off 关掉它。

⑤ 握手 —— stdout 只有一行 JSON-RPC(日志全在 stderr,这是 stdio 传输的硬要求):

{"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"dsh-memory-mcp","version":"0.1.0"}},"jsonrpc":"2.0","id":1}

看到这行 = 进程能拉起来、协议层是通的。想让 IDE 之外更完整地验一遍工具清单,直接跑 npm test(见 自测)。

④⑤ 里 --store-dir 指向的临时目录跑完可以删;它只证明"能启动",不代表配好了。 真正要共用记忆时,必须显式 --store-dir 指向 DSH 插件的 storeDir。

环境变量

变量 默认 说明
DSH_MEMORY_PROJECT process.cwd() 当前工作区目录,等价于 --project。
DSH_MEMORY_STORE_DIR ~/.dsh-memory-store 存储目录,等价于 --store-dir。
DSH_MEMORY_SCOPE_ALL false 1/true/yes/on ⇒ memory_save 省略 scope 时默认写 project 而不是 global。
DSH_MEMORY_VECTOR 开启 off(或 0/false/no/disabled)⇒ 完全关闭向量,退化为纯词法检索。
DSH_MEMORY_EMBED_ENDPOINT — 直接指定已有的 embedding 服务(如 http://127.0.0.1:8078)。设了它就永不 spawn 进程,也永不杀进程。
DSH_MEMORY_EMBED_EXE D:\dev\llamacpp\llama-server.exe ⚠️ llama-server 可执行文件。默认值是作者本机路径,换机器必须设。
DSH_MEMORY_EMBED_MODEL D:\dev\models\Qwen3-Embedding-0.6B-Q8_0.gguf ⚠️ gguf 模型文件。同上。
DSH_MEMORY_EMBED_PORT 8199 端口(避开 DSH 的 8078,两者可共存)。
DSH_MEMORY_EMBED_THREADS 6 llama-server 线程数。
DSH_MEMORY_EMBED_POOLING 按模型推断 池化方式。换模型时务必确认这一项(见下)。
DSH_MEMORY_DEBUG 关闭 1 ⇒ 启动失败时额外打印完整调用栈(默认只打一行原始错误)。

⚠️ 向量相关的三个默认值(EXE / MODEL)是作者本机的绝对 Windows 路径。 别人 clone 后直接跑,向量后端会 spawn 失败 —— 但服务不会崩,只是静默退化为纯词法检索。 换机器时三选一:设 DSH_MEMORY_EMBED_EXE + DSH_MEMORY_EMBED_MODEL;或用 DSH_MEMORY_EMBED_ENDPOINT 指向已有的 embedding 服务;或直接 DSH_MEMORY_VECTOR=off 关掉向量。

五个工具

所有工具都返回 { content: [{ type: 'text', text }], structuredContent: {...} }, 文本是给人(和模型)看的友好渲染,structuredContent 是给程序用的结构化数据。

memory_save

保存一条长期记忆。

参数 类型 必填 默认 说明
content string ✅ — 一句自包含的陈述句,不依赖上下文也能读懂。
scope string global 作用域。省略时写入 global(见下方说明)。
path string — 工作区目录。给出后写入 project:<解析后的绝对路径>。
kind enum note fact / decision / preference / procedure / note / rule。
tags string[] [] 自由标签。
importance number — 0~1,越界报错。
supersedes string — 被本条取代的记忆 id。

默认 scope = global,这是与 DSH 最重要的差异。 DSH 里 tools.defaultScope 是 session,但 Trae / WorkBuddy / DSH 各自的 session id 完全不同, session 作用域无法跨 IDE 共享。真正能共享的只有 global 与 project:<绝对路径>, 所以 MCP 默认写 global,让三个 IDE 看到同一份记忆。

想要「只对当前工作区生效」,显式传 scope: "project" 或 path: "<工作区目录>"。

也可以用 DSH_MEMORY_SCOPE_ALL=true 把默认改成 project。默认不开启 —— 默认写 global 的收益是「Trae 里学到的偏好,WorkBuddy 里立刻能用」, 而代价是 global 作用域会混入各个项目的记录。对多数个人使用者来说, 跨 IDE 共享的价值大于作用域混在一起的代价,所以默认选 global。

输出示例:

已保存记忆 ae2061aa @ global

memory_search

按关键词或语义检索。

参数 类型 必填 默认 说明
query string ✅ — 关键词或一句话描述。
scope string 当前项目 + global all / * = 所有已发现作用域(只读)。
topK number 5 1~20。

输出示例:

找到 2 条记忆(检索范围:D:/dev/my-app, global)

[0.4213] (global/procedure) 本机 DSH 的 desktop profile 由 Electron 应用独占管理
    id=ae2061aa  tags=DSH,desktop profile  importance=0.90  updated=2026-10-19 12:03:11

[0.1875] (project:D:/dev/my-app/decision) 前端统一用 TypeScript 严格模式
    id=be1c02d7  importance=0.70  updated=2026-10-20 09:15:22

提示:需要完整内容时用 memory_get 传入上面的 8 位 id 前缀。

memory_list

列出记忆,按最近更新优先。

参数 类型 必填 默认 说明
scope string 当前项目 + global all = 所有作用域。
kind string — 按种类过滤。
limit number 50 1~200。

memory_get

按 id 取回具体内容(可写 8 位前缀)。已知 id 时用它,不要为此再跑一次 search。

参数 类型 必填 说明
ids string[] ✅ id 或 8 位前缀。自动去重,最多取前 50 个。

memory_forget

删除记忆。id 与 query 恰好二选一。

参数 类型 必填 说明
id string 二选一 精确 id(或 8 位前缀)。跨作用域唯一,scope 参数不适用。
query string 二选一 按内容子串批量删。
scope string query 路径省略时只作用于当前工作区(不会误删 global)。
  • 都不给 → TARGET_REQUIRED
  • 都给 → TARGET_AMBIGUOUS
  • scope: "all" 与 query 同用 → SCOPE_NOT_WRITABLE(防批量误删)

输出示例:

已删除 1 条记忆 ae2061aa @ global

结构化输出

每个工具除了给模型的文本,还返回 structuredContent,字段名与上游 DSH 插件保持一致 (同一个 agent 在两边可以用同一套读取习惯):

工具 structuredContent 关键字段
memory_save { ok, id, deduped, scope, kind }
memory_search { ok, count, scopes, records: [{ id, shortId, score, scope, kind, content, tags, importance, updatedAt }] }
memory_list { ok, count, scopes, records: [...] }
memory_get { ok, count, records: [...], missing: [...] }
memory_forget { ok, deleted, ids: [...] }

出错时返回 isError: true + { ok: false, code, message }。不抛栈,文本里写明怎么改。

参数校验类错误码:

错误码 触发点
CONTENT_EMPTY memory_save 的 content 为空或非字符串
QUERY_EMPTY memory_search 的 query 为空或非字符串
KIND_INVALID kind 不在六种之内(memory_save 与 memory_list 共用)
IMPORTANCE_RANGE / TOPK_RANGE / LIMIT_RANGE / IDS_EMPTY 对应数值越界,或 ids 是空数组
TARGET_REQUIRED / TARGET_AMBIGUOUS memory_forget 的 id 与 query 都不给 / 都给
PATH_INVALID path 是空串
PATH_SCOPE_CONFLICT 同时给 path 和 scope: global/session/all

作用域类错误码:

错误码 触发点
SCOPE_INVALID 作用域名不合法(含 : / \ ..、控制字符、超长、保留名)
SCOPE_NOT_WRITABLE 用 all 写;或 memory_forget 把 all 与 query 同用
SCOPE_NAME_INVALID / SCOPE_NAME_TAKEN 别名本身非法 / 该别名已被另一个路径占用(来自 scope-names.js)

存储与兜底类错误码:

错误码 触发点
LOCK_TIMEOUT / LOCK_RECURSION 取锁 10 秒超时 / 同进程对同一把锁递归加锁
STORE_ERROR 存储层返回了无法识别的结果(适配层兜底)
UNKNOWN_TOOL / TOOL_ERROR 工具名不存在 / 任何未归类的异常

注意 memory_search 的记录数组字段是 records(不是 results),与上游 lib/tools/index.js 的 { records: [...] } 对齐。

记忆在磁盘上长什么样

<store-dir>/
├── global.jsonl                    ← scope 键 "global"
├── global.vec                      ← 向量边车,首行 #qwen3-embedding-0.6b\t1024
├── projects/<sha1(scope)>.jsonl    ← 其他任何 scope 键
├── projects/<sha1(scope)>.vec
├── scopes.json                     ← 作用域别名(只有登记过别名才存在)
├── .locks/                         ← 每 scope 一个锁文件(sha1(scope).lock)
├── llama-server-mcp.log            ← 自己 spawn 的向量后端日志(仅 spawn 过才存在)
└── settings.json                   ← DSH 的注入设置(本 MCP **不读不写**)

排查"记忆去哪了"时,就看这个目录里有没有 global.jsonl、以及 mtime 是不是刚刚。 llama-server-mcp.log 是排查"语义检索为什么不生效"的第一现场。

Trae / WorkBuddy / ZCode 配置

Trae 与 WorkBuddy 的 MCP 配置格式一样(mcpServers 对象)。ZCode 格式不同,单独一节。 下面几段直接贴进对应配置文件即可。

Trae

配置文件:%APPDATA%\Trae CN\User\mcp.json(本机实测路径)。 也可以在 Trae 里 设置 → MCP 服务器 → 添加 手动填。

WorkBuddy

配置文件:%USERPROFILE%\.workbuddy\mcp.json。

ZCode

配置文件:%USERPROFILE%\.zcode\cli\config.json。 ZCode 的键名与形状和上面两个不一样:不是顶层 mcpServers,而是嵌套的 mcp.servers。 少一层 mcp 会被 ZCode 当成「没配 MCP」而静默忽略。

{
  "mcp": {
    "servers": {
      "dsh-memory": {
        "type": "stdio",
        "command": "C:\\Program Files\\nodejs\\node.exe",
        "args": [
          "C:\\path\\to\\dsh-memory-mcp\\src\\index.js",
          "--store-dir",
          "C:\\Users\\me\\.dsh-memory-store"
        ],
        "env": {
          "DSH_MEMORY_EMBED_EXE": "C:\\path\\to\\llama-server.exe",
          "DSH_MEMORY_EMBED_MODEL": "C:\\path\\to\\Qwen3-Embedding-0.6B-Q8_0.gguf",
          "DSH_MEMORY_EMBED_POOLING": "last"
        },
        "timeoutMs": 60000
      }
    }
  }
}

ZCode 专属注意事项(均为本机对 ZCode.exe / resources/glm/zcode.cjs 实测求证):

  • schema 是严格的:多写一个不认识的键,整个 server 会被丢掉而不是被忽略。 可用键只有 type / command / args / cwd / env / enabled / timeoutMs / alwaysLoad / protocolVersion。command 必须是字符串(写成数组 ["npx","-y",...] 会让 ZCode 的设置页崩在 command.trim is not a function)。
  • cwd 不用写:不写时 ZCode 直接拿当前工作区当 MCP 进程的 cwd, 所以 project 作用域天然跟着工作区走 —— 这正好替代了下面「关于 --project」那节 在 Trae/WorkBuddy 里要靠项目级配置解决的问题。
  • 模板变量不展开:${...} 只对插件自带的 MCP 生效,配置文件里要用绝对路径。
  • 改完要重启会话(ZCode 每个工作区起一个 app-server 子进程,配置在它启动时读一次); 状态看 设置 → MCP,会显示 connected / failed 与工具数量。
  • 没有 zcode mcp add 命令:ZCode 的 MCP 只能靠改这个 JSON 文件,或走设置页图形界面。
  • 兜底位置是 %USERPROFILE%\.agents\mcp.json(顶层 mcpServers), 但仅当同一个作用域的 .zcode 里一个 MCP server 都没有时才被读; 既然上面已经往 .zcode 写了,那份就不会生效。

一份可直接改的模板(Trae / WorkBuddy)

下面把本机的绝对路径换成了占位符。三个 ⚠️ 处必须换成你自己的:

{
  "mcpServers": {
    "dsh-memory": {
      "command": "C:\\Program Files\\nodejs\\node.exe",
      "args": [
        "C:\\path\\to\\dsh-memory-mcp\\src\\index.js",
        "--store-dir",
        "C:\\Users\\me\\.dsh-memory-store"
      ],
      "env": {
        "DSH_MEMORY_EMBED_EXE": "C:\\path\\to\\llama-server.exe",
        "DSH_MEMORY_EMBED_MODEL": "C:\\path\\to\\Qwen3-Embedding-0.6B-Q8_0.gguf",
        "DSH_MEMORY_EMBED_POOLING": "last"
      }
    }
  }
}

command 写node 的绝对路径而不是 node —— IDE 拉起 MCP 时继承的 PATH 常常不含 node, 写 node 会报「不是内部或外部命令」。

--store-dir 必须指向 DSH 插件在用的那个目录,否则两边看不到同一份记忆。

不想配向量后端的话,把 env 整段删掉、改成 "DSH_MEMORY_VECTOR": "off",只保留词法检索 —— 记忆照样能读写,只是没有语义召回。

关于 --project:全局配置里故意不写

⚠️ 上面这份配置里没有 --project,这是刻意的。

MCP 配置是全局的(一个配置文件服务所有项目),而 --project 是单一值。 一旦写死成某个具体项目,那么你在 Trae 里打开任何其它项目时, 它的 project 作用域都会错误地落到那个目录 —— 所有项目的记忆混进同一个桶。

不写 --project 时,服务退回 process.cwd()。正确用法是按项目单独配一份:

  • 项目级配置(推荐):在项目根放 .trae/mcp.json(Trae 需在设置里开启「项目级 MCP」), 或 .workbuddy/mcp.json(WorkBuddy),此时加 --project <项目绝对路径>。
  • 或者干脆不用 project 作用域:memory_save 默认写 global, memory_search 默认检索「当前项目 + global」。全局配置下项目识别可能不准, 但 global 那部分始终正确 —— 而跨 IDE 共享的本来就是 global。
  • 想让保存默认落到项目而不是 global,加 "DSH_MEMORY_SCOPE_ALL": "true"。

如果 IDE 不支持 env 字段,把环境变量写进 command 的调用脚本里。 别指望"省略 --store-dir 就行" —— 默认目录是 ~/.dsh-memory-store,与你的 DSH storeDir 多半不是同一个。

接好之后先自测一遍

不用真的让 agent 去调工具,先确认进程能被拉起来:

# CLI 层(不建连接):确认参数/版本/路径都对
node <repo>/src/index.js --version
node <repo>/src/index.js --help

# 真正起一次服务,看 stderr 首几行(stdio 型服务在 IDE 外跑需要喂一个 initialize)
echo {} | node <repo>/src/index.js --store-dir <你的目录>

首三行 stderr 会打印实际生效的 store-dir / project / 默认保存作用域 —— 「记忆写错地方」90% 在这一屏就能看出来。

ZCode 用户可以一条命令验完:scripts/probe-zcode-handshake.mjs 会读你自己的 ~/.zcode/cli/config.json,完全照 ZCode 的 spawn 方式起进程(工作区当 cwd、 {...进程环境, ...配置里的 env} 合并、stderr 走 pipe),然后 initialize → tools/list → 真调一次 memory_search:

node <repo>/scripts/probe-zcode-handshake.mjs [工作区绝对路径]

它打印握手耗时、工具清单和一次真实检索结果。它只读不写记忆库, 所以对真实 store 跑是安全的(不像 acceptance-*.mjs 那样必须先指到临时目录)。

与 DSH 共存的设计

存储目录相同,进程独立,互不干扰。

<store-dir>/
├── global.jsonl                    ← scope 键 "global"
├── global.vec                      ← 向量边车,首行 #qwen3-embedding-0.6b\t1024
├── projects/<sha1(scope)>.jsonl    ← 其他任何 scope 键
├── projects/<sha1(scope)>.vec
├── .locks/<sha1(scope)>.lock       ← 每个 scope 一把独占锁
├── scopes.json                     ← 作用域别名(登记过别名才存在)
├── llama-server-mcp.log            ← 自己 spawn 的向量后端日志(仅 spawn 过才存在)
└── settings.json                   ← DSH 的注入设置(MCP 不读不写)
  • 作用域键 → 文件名:global → global.jsonl;其余 → projects/sha1(scope).jsonl (哈希文件名结构上不可能路径穿越,也没有 Windows 保留名问题)。
  • 记录格式与上游逐字节兼容:
    {"id":"<uuid>","scope":"global","kind":"procedure","content":"...","tags":[],
     "source":{"origin":"explicit"},"createdAt":1792403053326,"updatedAt":1792403053326,
     "lastAccessedAt":1792403053326,"accessCount":0,"importance":0.9}
  • 向量边车:首行 #<model>\t<dims>,之后每行 <id>\t<base64(Float32Array 原始字节)>。 头不匹配(换过模型)时整个边车作废、重新嵌入。

为什么不会互相覆盖

上游 DSH 插件的 MemoryStore 是进程内快照 + 全量重写: loadScope() 每个 scope 只读盘一次,flush() 拿内存快照重写整个文件。 两个进程共用时,后者的快照里没有前者刚写的记录,一次 flush 就确定性抹掉。

本项目的 SharedStore 换掉了这一层:

  • 粒度为单个 scope 文件的独占锁文件(openSync(path, 'wx') / O_EXCL);
  • 每个「读-改-写」事务都在锁内重新从磁盘读取该 scope 的全部记录;
  • 写盘走 tmp + renameSync 原子替换;
  • .vec 向量边车走单行 append(append 语义天然跨进程安全),删除向量时才在锁内全量重写。

⚠️ 注意:DSH 插件本身仍然是「快照 + 全量重写」。当 DSH 进程和 MCP 进程同时写同一个 scope 时, 数据是双向互毁的,不是单方面碾压。scripts/acceptance-coexist.mjs 实测:

加锁写者 A 写入 24 条 → 磁盘剩 12 条
上游式写者   写入 12 条 → 磁盘剩  0 条
合计磁盘 12 条(写入前双方共 36 条)

本项目的锁能保证自己这一侧的事务不丢(串行写 5/5、外部追加不被 flush 抹除均已验证), 但挡不住另一个进程的无锁全量重写 —— 这是上游 MemoryStore 的病,本项目修不了它。

实务纪律(唯一可靠的办法):

  • 读随便读,两边同时读完全没问题(读路径不产生覆盖)。
  • 写同一时刻只让一个进程写。日常用法是 DSH 与 MCP 交替着来, 不要在两边同一瞬间各写一条。
  • MCP 侧的 3 个实例之间(多个 IDE 同时开)不会出问题 —— 它们共用同一把锁。

与上游的版本关系

本项目基于 0.8.0 的源码(本机已升级到 0.8.0,2026-10-20)。

已核对 0.7.2 → 0.8.0 的差异:只改动了 6 个文件(client.js / panel.js / provider/apply.js / provider/index.js / package.json / README.md), store.js / scoring.js / scope.js / scope-names.js / tools/* 逐字节未变 —— 所以本项目复用这些模块的部分不受版本升级影响,升级后实测验收 13/13 全绿。

升级上游后仍建议比对这几个文件(它们才是与本项目耦合的部分)。

上游包无法被直接 import:exports 只声明了 . / ./client / ./package.json(没有 lib/* 子路径), 裸包根入口又会因 @deepseek-ai/cordis peer 依赖而 ERR_MODULE_NOT_FOUND。 所以本项目复制了那几个零依赖纯模块的源码(scoring.js / scope.js 逐字复制, store.js 重写但保持字节级格式兼容),版权声明见 third-party-notices.md。

scope 语义(与 DSH 的差异)

键空间(与上游完全一致):

形式 含义
global 所有项目共享
project:<绝对路径> 一个工作区
session:<id> 一个会话(同一套 projects/<sha1> 文件体系)
自定义名 自由命名

自定义名规则:内部空白(含制表/换行)折叠为单个空格 → 转小写 → 长度 1~64 码点 → 禁 : / \ .. 控制字符、禁纯点/空格组成的名字、禁保留名 global/project/session/all。 任何不合法的作用域名一律报错,绝不静默回退。

MCP 里的默认行为(与 DSH 不同,原因见下):

工具 省略 scope 时 原因
memory_save global 跨 IDE 共享的只有 global 与 project。
memory_search 当前项目 + global session 在 MCP 里没有共享意义。
memory_list 当前项目 + global 同上。
memory_forget(query) 当前项目 比 session 安全,不会误删别人的。

为什么不用 session:DSH 的 session id 来自 DSH 自己的会话头,Trae / WorkBuddy 各有各的 id。 用 session 作用域等于每个 IDE 一份互不相通的记忆,跨 agent 共享就无从谈起。 所以本 MCP 把裸 session 落到当前工作区(与上游「拿不到会话就回退 project」同语义), 但显式 session:<id> 仍然原样保留,可以 pin 某个会话作用域。

路径归一化(必须归一化,否则同一个目录会分裂成两个 scope):

  • 绝对路径 → 折叠 . / ..、去掉末尾分隔符、盘符大写(c:\x → C:\x);
  • 相对路径 → 按 DSH_MEMORY_PROJECT / --project 解析(都没有才按 process.cwd())。

向量开关

默认开启,但完全可选。

  • 检索是混合召回:hybrid = max(lexical, cosine),不是加权求和。 词法通道用 IDF + CJK bigram(每个 scope 内部独立统计 IDF), 有强命中(共享拉丁词或 CJK bigram)用宽松阈值(global 0.12 / 其余 0.1), 只有单字 CJK 命中则要求 max(floor, 0.25)。
  • 向量通道:minScore 0.5,recallN 30,topK 默认 5,score 保留 4 位小数。
  • 排序:score 降序 → scope 层级(session 0 > project 1 > 自定义 2 > global 3)→ id.localeCompare。
  • 过期记录(expiredAt < now)跳过。

后端生命周期:

  1. 先 GET /health 探活 —— 活着就复用,不接管生命周期、不杀进程 (所以可以和 DSH 的 8078 实例共存,本项目默认用 8199);
  2. 没活才 spawn llama-server(windowsHide: true、detached: false), 日志重定向到 <store-dir>/llama-server-mcp.log;
  3. 进程退出时只杀自己 spawn 的那个。

熔断:连续失败后指数退避(1s → 2s → 4s … 上限 30s),不会每次检索都白等一次超时。 超时分级:交互检索 300ms 预算;写入 / 回填 10s 预算(整段内容在 CPU 上嵌入慢得多)。

关掉向量:

DSH_MEMORY_VECTOR=off node src/index.js

退化后:memory_search 纯词法检索,memory_save 不再写 .vec 边车,完全不碰网络。 已存在的 .vec 文件不受影响。

复用 DSH 已算好的向量:本项目默认的模型名与维度是 qwen3-embedding-0.6b / 1024, 与现有 store 里的 .vec 头完全一致,所以 DSH 之前算好的向量可以直接拿来用,不必重新嵌入。 换了模型的话,头对不上,整个边车作废并重新嵌入(绝不拿旧模型的向量算相似度)。

⚠️ 池化方式(pooling)必须跟着模型走

llama-server 的 --pooling 决定向量落在哪个空间里。用错了,DSH 存好的向量就全部作废 (检索结果会变成噪声,而不是报错)。

上游 models.js 的原话是「BGE 系用 cls,Qwen3-Embedding 用 last」,本项目照此实现: 按模型名自动推断 —— bge-* → cls,nomic-* → mean,其余(Qwen3-Embedding 等)→ last。

实测(2026-10-20,拿 D:/dev/.dsh-memory-store/global.vec 里 DSH 存好的向量比):

llama-server 参数 现算向量 vs DSH 已存向量 的余弦
--pooling last 1.0000 —— 同一个空间,可直接复用
--pooling cls 0.3522 —— 完全不同的空间,混用等于噪声

所以换模型时,除了 DSH_MEMORY_EMBED_MODEL,还要确认 DSH_MEMORY_EMBED_POOLING。 换错了不会报错,只会静默地让向量检索失效 —— 这是本项目最容易踩的坑。

顺带一提:用 cls 时,不相关文本之间的余弦高达 0.97(几乎所有句子都"相似"); 用 last 时降到 0.41,这才是正常嵌入空间该有的区分度。

已知限制

  1. 与 DSH 并发写同一 scope 有丢数据风险(见上文「为什么不会互相覆盖」的注意事项)—— 本项目的存储层是并发安全的,但上游 DSH 插件不是。
  2. 触达计数:检索命中后 accessCount 的刷新走存储层的 touchMany(ids) —— 按 scope 分组,每个 scope 只取一次锁、只重写一次文件。 只对最终返回的 topK(≤5 条)做,不对全部候选做。 收益是打分公式里的 accessBoost = 1 + 0.1·ln(1+accessCount); 代价是每次检索会有一次小规模写盘。这是本实现与上游的一处刻意差异 —— 上游的 touch 只是把 scope 标 dirty、靠 500ms 去抖 flush(看起来免费), 但那正是它跨进程会丢数据的那个 flush;这里没有免费的那一层。
  3. 没有 LLM rerank:上游 provider 有一个可选的第三阶段(LLM 重排,默认关闭)。 本项目只有词法 + 向量两路召回。
  4. 没有自动注入:DSH 插件会在 agent 回合开始时自动把相关记忆注入上下文; MCP 侧没有这个机制 —— 其它 IDE 需要 agent 主动调用 memory_search。
  5. session 作用域在 MCP 里基本没用:跨 IDE 的 session id 不通用。 显式 session:<id> 仍然可用,但只有同一 IDE 内部才有意义。
  6. Windows 路径大小写:归一化只统一盘符大小写(c: → C:),路径其余部分的大小写按原样保留 (Windows 本身不区分,Linux 上可能区分)。
  7. 向量是 CPU 密集型:首次嵌入会有几秒延迟(模型加载)。

自测

npm install

# 一条命令跑主力三项(约 7 秒,都不碰真实 store)
npm test

# 逐项跑
node scripts/selftest-cli.mjs          # CLI 契约:--help/--version/未知参数/退出码/启动失败诊断/配置解析
node scripts/selftest-mcp.mjs          # 完整 MCP 协议端到端(真起 stdio server)
node scripts/selftest.mjs              # 存储层并发(真 fork 多进程)
node scripts/selftest-tools.mjs        # 工具层:参数校验 / scope 决策 / 渲染(内存版假 store)
node scripts/selftest-protocol.mjs     # 协议层:握手 / tools/list / tools/call(内存版假 store)
node scripts/selftest-vector.mjs       # 向量后端:配置 / 探活 / 熔断 / 生命周期
node scripts/selftest-vector-e2e.mjs   # 向量端到端:写 .vec 边车 + 语义召回

# 验收测试(会碰 store;真实库那一份需要你先指定 DSH_MEMORY_REAL_STORE,不设就跳过)
node scripts/acceptance-real.mjs       # 端到端打真实 storeDir(只写/只删自己造的记录)
node scripts/acceptance-coexist.mjs    # 与「上游式无锁写者」并存的实测数据(用临时目录)
node scripts/acceptance-standalone.mjs # 不开 DSH 的独立运行验证(纯词法可跑;自拉起后端需配环境变量)
脚本 覆盖 检查项 碰真实 store?
selftest-cli.mjs CLI 契约:--help/--version 的零副作用、未知参数 exit 2、启动失败诊断无裸栈、parseArgs/resolveConfig 纯函数 115(POSIX 114,有一条 Windows 专有断言) 否
selftest-mcp.mjs 真实 stdio 握手、5 个工具的 schema、保存、检索、幂等、列表/按前缀取回、删除后不再召回、错误码、向量开关对比、落盘检查、stderr 日志 40 否
selftest.mjs 单进程语义、4 进程 × 20 条真并发、外部追加不被抹除、跨进程锁(超时/陈旧锁/粒度)、.vec 边车、别名映射 55 否
selftest-tools.mjs 路径归一化、scope 决策、全部校验错误码、检索渲染、幂等、别名、自定义作用域、schema 自检 60(POSIX 59,盘符大小写折叠是 Windows 专有语义) 否
selftest-protocol.mjs 握手、tools/list、tools/call 正常/错误路径、stdout 纯净性 18 否
selftest-vector.mjs 配置解析、探活、复用不 spawn、维度、L2 归一化、熔断退避、与已存向量的空间兼容性 27 只读
selftest-vector-e2e.mjs 写 .vec 边车 → 读回 → 不同措辞的语义召回 6 否
acceptance-real.mjs 对你指定的 storeDir 跑完整协议:写入(path ⇒ project: 作用域,并断言不是 global)→ 磁盘可见 → 跨作用域读回 → 幂等 → 按前缀取回 → 删除 → 尝试确认仍能读到 DSH 既有记忆 18 需 DSH_MEMORY_REAL_STORE(只删自己造的)
acceptance-coexist.mjs 与复刻上游「内存快照+全量重写」的无锁写者并发,量化双向互毁的损失 4 否(临时目录)
acceptance-standalone.mjs 不开 DSH:冷启动 → (可选)自拉起向量后端 → 读写 → 语义召回 → 退出后无孤儿进程 11 需 DSH_MEMORY_REAL_STORE(只删自己造的)

检查项数在 Windows 与 Linux 上有 1~2 项差异,这是刻意的:被跳过的都是平台专有语义 (盘符不存在、盘符大小写折叠),在 POSIX 上断言它们反而是错的。CI 两个平台都跑,数字对不上是正常的。

selftest-mcp.mjs 用官方 SDK 的 Client + StdioClientTransport 起自己, 真实走一遍完整的 MCP 协议握手(initialize → tools/list → tools/call)。

跑测试前必须知道的几件事

  • 默认 npm test 不碰真实 store:它 = selftest-cli.mjs && selftest-mcp.mjs && selftest.mjs。
  • acceptance-real.mjs 与 acceptance-standalone.mjs 会写 store,所以目标库必须由你显式给出:
    # PowerShell
    $env:DSH_MEMORY_REAL_STORE = "D:\dev\.dsh-memory-store"; node scripts/acceptance-real.mjs
    # bash / zsh
    DSH_MEMORY_REAL_STORE=~/.dsh-memory-store node scripts/acceptance-real.mjs
    没设这个变量时它们只打印一行提示然后 exit(0) 跳过(退出码 0 表示"没测",不是"测过了")。 脚本不会猜路径、也不会回退到任何写死的机器路径;入口路径从脚本自身位置推导,换机器不用改。 它们只删自己造的记录,但半路 Ctrl-C 仍可能在库里留下几条带随机后缀标记的记录。 想先确认脚本逻辑通、又不碰真实库:把该变量指向 os.tmpdir() 下的临时目录即可。
  • acceptance-standalone.mjs 的向量后端也只从环境变量读:DSH_MEMORY_EMBED_EXE + DSH_MEMORY_EMBED_MODEL(自拉起)或 DSH_MEMORY_EMBED_ENDPOINT(复用已有服务)。 都没配就打印提示,并把「自拉起后端 / 语义召回」标成跳过 —— 不报 FAIL,也不拿作者本机的 默认路径硬试。端口探活用 node:net + 原生 fetch(),跨平台,不需要 netstat / curl。
  • ⚠️ 真实 store 是「活的」:只要 DSH 开着,它的记忆插件就在往里写(记录、accessCount、 scopes.json 别名),MCP 侧检索命中也会刷 accessCount。所以不要拿文件 mtime 判断"测试有没有污染真实库" —— 改动可能来自 DSH 自己的正常写入。要判断污染,看的是内容:global.jsonl 里有没有测试专用标记字符串。
  • acceptance-standalone.mjs 要验证「自拉起向量后端」这一支时,该端口(默认 8199)必须空闲; 已被占用时它只会复用那个服务,并把「自拉起」标成跳过(不算失败)。 本机实测两台 llama-server 常驻(8078/8199 各一个),所以默认就落在"复用"这一支。
  • selftest-vector.mjs 会读(不写)真实 store 里的 global.vec 做向量空间比对; 该文件不存在时自动跳过那几项。
  • selftest-vector-e2e.mjs 在无可用向量后端时直接 exit(0) 跳过 —— 退出码 0 不代表真的测过。CI 里要当心这一点。
  • acceptance-coexist.mjs 本身是 flaky 的(实测 8 次里约 3 次 exit 1)。原因不是本项目的锁, 而是它故意复刻的那个"上游式写者"用固定 ${file}.tmp 名 + rename,在 Windows 上并发 rename 同一个 global.jsonl 会 EPERM。这恰好是"必须用唯一 tmp 名"那条不变量的活证据。看到 FAIL 重跑一次即可, 不是回归。
  • 所有 selftest 都在 os.tmpdir() 下的随机目录里跑,selftest.mjs 开头还有硬守卫 —— 测试路径一旦命中真实 store 就直接中止。加 --keep 可以保留临时目录便于排查。

面向 AI agent

本仓库除了给人看的这份 README,还准备了两个 agent 入口文件:

文件 给谁 内容
AGENTS.md 接手/修改本仓库的 coding agent 运行时事实、启动参数与环境变量全集、17 条改代码前必须知道的不变量、测试地图(含平台差异与 flaky 说明)、故障 → 定位命令、CI 说明
llms.txt 想发现本仓库的 agent / 工具(遵循 llms.txt 约定) 项目简介、快速开始、工具清单、配置、不变量摘要、源码结构,全部绝对 URL

如果你正在用某个 agent 来部署这个服务,直接把 AGENTS.md 喂给它,比喂 README 更省 token 也更准 —— README 里有大量"与上游的差异"「实测数据」这类对部署没用的内容。

MCP 服务本身也在运行时告诉客户端它能干什么:五个工具的 description 里写明了 "什么时候该用我 / 默认 scope 是什么 / 出错怎么改",initialize 时不需要额外文档。

许可与第三方声明

存储层与评分算法复用自上游 @restregular/dsh-memory(MIT), 逐文件来源见 third-party-notices.md。

MIT License

Copyright (c) 2026 restregular
...

About

把 @restregular/dsh-memory 的记忆库暴露成独立 MCP 服务,让 Trae / WorkBuddy 等任意 MCP 客户端与 DSH 共用同一份长期记忆。

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages