DSH工作区内跨会话记忆插件
43.4/100
综合分
19
GitHub 星标
500
npm 周下载
—/5
用户评分
数据截至 2026/10/25 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-workspace-recollectionnpm 源 · dsh-workspace-recollection 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-workspace-recollection @ 2.5.0
✓Node 引擎要求 >=22.12.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/30 15:53:18
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件
查看扫描证据(共 32 处,展示前 21 条)
- 写入本地文件lib/index.js:169
mkdirSync(dirname(path), { recursive: true }); - 写入本地文件lib/index.js:171
writeFileSync(temporary, content, "utf8"); - 写入本地文件lib/index.js:172
renameSync(temporary, path); - 读取本地文件lib/index.js:274
try { return parseLock(readFileSync(LOCK_PATH, "utf8")); } catch { return null; } - 写入本地文件lib/index.js:291
mkdirSync(HOME_DIR, { recursive: true }); - 写入本地文件lib/index.js:292
writeFileSync(LOCK_PATH, lockPayload(process.pid, Date.now(), HOME_DIR), { encoding: "utf8", flag: "wx" }); - 删除本地文件lib/index.js:303
unlinkSync(LOCK_PATH); - 写入本地文件lib/index.js:304
writeFileSync(LOCK_PATH, lockPayload(process.pid, Date.now(), HOME_DIR), { encoding: "utf8", flag: "wx" }); - 读取本地文件lib/index.js:315
const lock = parseLock(readFileSync(LOCK_PATH, "utf8")); - 删除本地文件lib/index.js:316
if (lock !== null && lock.pid === process.pid) unlinkSync(LOCK_PATH); - 读取本地文件lib/index.js:332
const all = readdirSync(BACKUP_DIR).filter((name) => name.startsWith(prefix)); - 删除本地文件lib/index.js:334
try { unlinkSync(join(BACKUP_DIR, stale)); } catch { } - 读取本地文件lib/index.js:349
if (!existsSync(STORE_PATH)) return normalizeStore(null); - 读取本地文件lib/index.js:352
raw = JSON.parse(readFileSync(STORE_PATH, "utf8")); - 读取本地文件lib/index.js:648
if (!existsSync(VECTORS_PATH)) return empty; - 读取本地文件lib/sessions.js:21
const first = zstdDecompressSync(readFileSync(file)); - 读取本地文件lib/sessions.js:43
try { projects = readdirSync(sessionsRoot); } catch { return rows; } - 读取本地文件lib/sessions.js:47
try { ids = readdirSync(projectDir); } catch { continue; } - 读取本地文件lib/sessions.js:50
if (!existsSync(file)) continue; - 读取本地文件lib/sessions.js:53
const stat = statSync(file); - 读取本地文件lib/sessions.js:66
const buffer = readFileSync(file);
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-home-paths@deepseek-ai/dsh-tools给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
README
由 DeepSeek 最新模型翻译生成dsh-workspace-recollection 🧠
给 DeepSeek Harness 的跨会话长期记忆:按工作区隔离、跨会话共享。
DSH 的会话一关,上下文就散了。这个插件把「做过什么、当时怎么决定的、那个坑是什么」沉淀下来,需要时再分层、按需地取回 —— 会话开始时只注入一行提示(告诉模型「你有记忆、去查」),用哪层取哪层。
核心是分层检索:想先看「大概有什么」就调 memory_index 拿目录(浅层要点,置顶在最前);条目很长时,中间还有一层 段级引索 —— 先定位到「哪一段」,只把那一段取回来;深层原文(当时的思考、命令、结论)留在本地向量库里,真要细节时才取。实测一条 1.5 万字的记忆取回一段约 1200 字,省 92%~97%。
工作原理
快速开始(安装插件)
以下命令都在你的命令行终端(PowerShell / CMD / bash 等)中运行。
展开完整文档(README 剩余 1 节)
dsh-workspace-recollection 🧠
给 DeepSeek Harness 的跨会话长期记忆:按工作区隔离、跨会话共享。
DSH 的会话一关,上下文就散了。这个插件把「做过什么、当时怎么决定的、那个坑是什么」沉淀下来,需要时再分层、按需地取回 —— 会话开始时只注入一行提示(告诉模型「你有记忆、去查」),用哪层取哪层。
核心是分层检索:想先看「大概有什么」就调 memory_index 拿目录(浅层要点,置顶在最前);条目很长时,中间还有一层 段级引索 —— 先定位到「哪一段」,只把那一段取回来;深层原文(当时的思考、命令、结论)留在本地向量库里,真要细节时才取。实测一条 1.5 万字的记忆取回一段约 1200 字,省 92%~97%。
工作原理
快速开始(安装插件)
以下命令都在你的命令行终端(PowerShell / CMD / bash 等)中运行。
① 前置要求:确认 Node.js 已安装
node -v
② 安装 DSH 启动器与 pnpm(已装可跳过;装完请重新打开终端)
npm install -g @deepseek-ai/dsh pnpm
dsh --version # 验证 dsh 命令可用
③ 安装本插件:--profile 填你实际在用的那个桌面应用(Electron 版)→ desktop;dsh web → web
dsh plugin --profile desktop add dsh-workspace-recollection
装完重开桌面应用(dsh web 用户则重启 dsh web)才生效 —— 运行中的进程持有内存里的旧插件。
之后点会话头部的「🧠 记忆」(或设置页里的「记忆」)就能打开记忆面板。
兼容性:本插件在 dsh 0.2.0-rc.2 下开发并测试(dsh --version 可查看你的版本)。
建议使用相同版本;其他版本如遇问题欢迎反馈。
从源码安装(clone 本仓库后)
本插件是纯 JavaScript、无构建步骤 —— clone 下来就能直接挂载:
git clone https://github.com/reaxlll/dsh-workspace-recollection.git
dsh plugin --profile desktop add file:/path/to/dsh-workspace-recollection
改源码后重开应用即可(宿主半侧改动必须重启;浏览器半侧刷新页面即可)。
首次启动会自动装好嵌入模型
语义检索需要本地嵌入模型(Xenova/bge-small-zh-v1.5,512 维,ONNX 量化版约 23MB)。
不用你动手:插件启动时会检查它是否在位,缺了就自己从镜像(hf-mirror.com,失败自动回退 huggingface.co)逐个文件拉下来,下完自动加载 —— 实测 24MB 约 18 秒。
之后运行时零联网,也不调任何云端 API。
网络不通或下载失败也不会影响使用:memory_recall 自动退回关键词检索,其他功能照常;面板状态条上的「加载模型」可随时重试。
💬 反馈与发版节奏
欢迎提 issue 与 PR —— bug、体验问题、优化想法都行。
可以随意对插件进行修改因为作者很可能没空维护,也对应了dsh那句,想要啥自己做(^_^)
但动手前请先翻一眼 提交记录,确认不是已经修掉的同款问题。
- 小修不单独发版:不影响使用的小 bug 与优化会累计到下一个版本一起发。
所以「main 上已经修了」不等于「你装的这版已经包含」—— latest 没更新时,以 main 的提交记录为准。
- 报 bug 时请带上:插件版本、dsh 版本、操作系统、复现步骤(有日志或截图更好)——直接用 issue 模板 会把这些列好。
- 想改代码?先看 CONTRIBUTING.md:代码地图、三条硬规矩(纯函数要配单测 / 阈值必须用真实回放来定 / 约束行为要用代码而不是提示词)、常见改动配方,以及一份提 PR 前的检查清单。
插件功能
- 跨会话记忆:记忆按会话 header 的 cwd 分桶 —— 一个工作区一份,这个工作区里的所有会话共享同一份记忆
- 分层检索:浅层要点目录(memory_index,按需)→ 段级引索(定位到段)→ 深层原文(按需取整条),三层都有工具,够用就停
- 一行提示:默认只往 system prompt 里放一行「你有其他会话的记忆,通过 memory_index 查看目录,详细善用 memory_recall」—— 40 字换回「存在感」(旧数据升级时自动从零注入改过来);目录、要点、原文全部走工具按需取
- 时间轴按你的时区显示:首次安装时检测一次本机时区并记下来,会话时间、记忆日期、时间通道都按它格式化
- 噪音过滤:memory_recall 的结果自动过四路信号(绝对底线 / 硬特征 / 相对长尾 / 平局),跑题的别冒充「找到了」;被筛掉的永不静默丢弃,折成一行 id + 分数 留着你复查
- 时间通道:问「10月6日」「九天前」「昨天」这类问题时,按本地日历日直接筛出那天的条目,并给出涉及到的会话 id(可顺着读会话原文)
- 排序分层:深层分是靠正文块「撞」出来的,容易虚高 —— 只有要点分够高、或命中 query 的硬特征、或能被段级引索复核出「实词」的,才承认它的深层分
- 本地嵌入 · 零联网:bge-small-zh-v1.5(512 维,ONNX q8)跑在本机,不调任何 API;加载约 35ms、编码约 2ms/条
- 模型自己装:启动时检测嵌入模型是否在位,缺了就自动从镜像拉取(约 23MB,实测 18 秒),下完自动加载
- AI 压要点:写完记忆直接走人,后台队列用 ctx.llm.stream 把长正文压成一句话要点(也可自己给,或手写)
- 会话导入:把本工作区的历史会话一键收进长期记忆(一条记忆 = 你当时说的话 + 思考原文 + 用到的工具 + 结论)
- 管理台:会话头「🧠 记忆」与设置页「记忆」两个入口打开同一块全屏面板 —— 状态条 / 按工作区浏览记忆 / 新增编辑删除置顶锁定 / 预览注入 / 检索测试 / Memory.md(预览 + 编辑手记区块) / 改工作区归属 / 重载数据 / 重建向量;面板本体是自包含页面 /api/session-memory/page,也能用系统浏览器开成独立窗口(拖到第二块屏)
- 工作区改名自动认领:记忆按路径分桶,目录一改名就认不上了 —— 所以每个工作区另存一份跟路径无关的身份(git remote + 包名 作强指纹,顶层条目名作弱指纹),新旧匹配且旧路径已不存在时自动认领,连带搬向量、重写 Memory.md;判不准就不搬(宁可视而不见,也不把两个工作区的记忆混在一起)
- 不碰内核:只用官方插件 API(systemPrompt.section / tools.register / webServer.register),前后端走 /api/session-memory/
会话生命周期与 memory_recall 的判断链
分层结构
| 层 | 位置 | 角色 |
| 事实源 | $DSH_HOME/session-memory/memory.json | 每条 = text(深层原文,可长)+ summary(浅层一句话) |
| 索引 | <工作区>/Memory.md | 工作区里的镜像文件,写的是浅层要点;默认只注入一行提示(面板可切 hint / index / full / off);底部「手记」区块原样保留 |
| 向量 | $DSH_HOME/session-memory/vectors.json | 每条两份向量:s(浅层,检索定位)/ d(深层,兜底补齐) |
| 段索引 | 不落盘,运行时现算(lib/segments.js) | 把正文按 ## 切成段,用纯 CPU 的 BM25 关键词定位到段号 |
检索流程(为什么省 token)
memory_recall("那个编码的坑") → 浅层向量检索 → 噪音过滤 → 只回「要点 + id + 相似度」
↓ 条目很长时(清单里标了「长文」,上万字的会话导入都是)
memory_locate({ id, query: "zstd" }) → 段级 BM25 定位 → 只回「段号 + 小标题 + 字数 + 命中词 + 预览」
↓ 挑好段号
memory_detail({ ids, segments: [2, 7] }) → 只取这两段切片
浅层文本短、语义集中,定位更准也更省;深层只在真要细节时才付 token; 段级引索让「真要细节」也不必整条取回 —— 一个字都不多花。
浅层向量缺失时自动回退;嵌入不可用再回退关键词检索。
取用顺序(写进了工具描述与每次工具返回的末尾):按需逐层、够用就停 —— ① memory_recall 拿要点 → ② 要点不够就用要点里的关键词 memory_locate 拿段号、memory_detail({segments}) 取切片 → ③ 浅层和切片都还不够(要当时的思考、命令全文)才 memory_detail({ids}) 整条兜底。别为了「完整」把三层都调一遍。
九个工具
| 工具 | 作用 |
| memory_write | 写记忆:text 写完整深层内容,summary 可选(省略则 AI 总结) |
| memory_index | 目录:置顶在前、其余按更新时间,带分页 / 标签 / 期望词过滤 —— 不知道库里有什么时先调它 |
| memory_recall | 浅层语义检索(带时间通道),只回要点 + id;自带噪音过滤(filter / min_score 可覆盖;被略去的折成一行 id+分数) |
| memory_locate | 段级引索:在已命中的那条里按关键词定位段号,只回段号/小标题/字数/命中词/预览 |
| memory_detail | 按 id 取深层完整原文;给 segments: [段号] 就只取那几段切片 |
| memory_search | 关键词精确检索(不走向量) |
| memory_forget | 删除(两份向量一并清;锁定条目删不掉) |
| memory_import | 把本工作区的历史会话导入长期记忆(一条用户消息 = 一条记忆) |
| session_read | 读历史会话原文/摘要的正门(不带 id 列会话;mode:"raw" 取指定轮次原文含思考) |
手工解压 $DSH_HOME/sessions/ 下的 zstd 会话文件已被插件在工具层硬拦(tools/pre-execute deny)——不是靠提示词自律,而是那条路走不通。要走正门用 session_read。
时间轴与时区
时间戳在磁盘上一直是 ISO(UTC),给人看的那一层统一走 lib/localtime.js:
- 首次安装检测一次:插件第一次创建 memory.json 时读一次本机时区(IANA id,例如 Asia/Shanghai),存进 memory.json 的 timeZone 字段。之后一直用它 —— 不会因为换时区设置就让历史时间整体漂移。
- 检测失败有兜底:拿不到 IANA id 时按当前偏移合成 UTC+08:00,永远有一个可用值。
- 哪些地方按它显示:session_read 的会话列表与轮次时间、记忆条目日期(memory_detail / memory_search)、时间通道(memory_recall 里问「九天前」)、Memory.md 的「更新于」。
- 时间通道也按它算「今天」:问「昨天」时按你记下来的时区的日历日筛,而不是按 UTC 的日期。
- 想看当前生效值:POST /api/session-memory/status 的 timeZone(stored / detected / label)。
为什么不在每次显示时现算:一条三个月前的记忆该按当时的钟点显示。
这条也是被实测教训出来的 —— 旧代码用 toISOString().slice(11, 16) 显示时间,结果本地 19:40 在界面上变成 11:40,差整整八小时。
检索噪音过滤
要解决的问题:memory_recall 原本是「排序后取 top-K」,没有任何过滤。
导入大批历史会话后,无关条目照样挤进前几名 —— bge 的相似度整体偏高(无关也能到 0.5+),绝对阈值不可信;而单一分数也切不干净:实测同主题查询里,无关条目的分甚至高于相关条目。
做法(lib/noise.js,纯函数零依赖,可脱离 DSH 单测)—— 四路信号,从硬到软:
| 信号 | 判定 | 为什么 |
| ① 绝对底线 | top1 < 0.45 → 一条都不留,明说「没有明显相关的记忆」 | 纯噪音查询的 top1 实测落在 0.40 附近 |
| ② 关键词硬特征 | query 里的专有名词 / ASCII 词 / emoji,在要点或标签里一个都没命中 → 略去(分数多高都不留) | 跨主题噪音靠这个切,分数上看不出来 |
| ③ 相对长尾 | 低于 top1 × 0.88 且绝对差 > 0.08 → 略去 | 砍掉明显跟不上的尾巴 |
| ④ 平局 | 前四条挤在 0.05 跨度内 → 只留前三条 | 没有谁真正胜出时,后面的往往正是跑题的 |
两条反误杀的护栏(都是实测踩出来的):
- 硬特征只查要点 + 标签,不查正文:两万字的会话正文里随便提过一句「PowerShell」就算命中,会把「配置某个工具」这种跑题条目留下来。
- 只在 query 给出两个以上硬特征时才开硬切:单个硬特征(如「emoji」)会误杀真相关的条目(它讲了这件事,只是没写那个词)。另外硬特征命中的条目豁免③的长尾规则(主题对上号了,分低一点也留)。
可覆盖:memory_recall({ filter: "auto" | "off" | "strict", min_score: 0.5 }) ——
off 回到旧行为看原始排名,min_score 是显式下限(一律生效、不豁免)。
阈值集中在 lib/noise.js 顶部常量,调完跑 npm test(用例里的分数直接取自回放,是「阈值有没有被改坏」的回归网)。
排序分层:深层分要有「资格」
深层的排序分是正文分块取最大值得来的 —— 块越多,撞上一个高分块的期望越高,于是长文天然占便宜,无区分度的查询也会被刷出虚高的分(实测里三条八竿子打不着的长文霸占了前三名)。
所以深层分现在要有资格才被承认,三条任意一条成立即可:
1. query 的硬特征(专有名词 / ASCII 词 / emoji)命中了这条的要点或标签;
2. 要点分本身够高(≥ 0.45,与绝对底线同语义:连底线都够不着 = 这条在要点层没有落点);
3. 通过段级引索的二级验证 —— 用 BM25 在这条正文里复核,且命中的词里至少有一个是「实词」。
第 3 条的门槛是文档频率(词出现在多少条记忆里):中文 bigram 的泛词毫无区分度(实测「什么」出现在 98 条里、「10」122 条、「日」60 条),拿「命中 ≥2 词」当判据时 8 条跑题条目里 7 条都能蒙混过关;限到「至少一个词的文档频率 ≤ 15」之后,跑题组 0/8 通过、真相关组 6/6 通过。三条都不成立的条目会被降级(退回要点分排序,结果里标「要点弱命中」)——它不会被丢弃,但不再用正文撞出来的高分冒充相关。
段级引索层
要解决的问题:memory_recall 只能定位到「哪一条」,memory_detail 一给就是整条正文(上限 2 万字)——记忆越长取一次越贵,而里面常常只有一小段是真正要找的。
做法(全部本地 CPU:零依赖、零联网、零模型、确定性;代码在 lib/segments.js,可脱离 DSH 单测):
| 步骤 | 做法 |
| 切段 | ## 小节是唯一可靠的语义边界 → 按标题切「节」;一节短于 900 字就整节成一段,长了才按 600 字在空行/句末断开,每片带 节名 (i/n) 序号;不足 40 字的碎屑节(如一行代码围栏)并入前节,标题写回正文保证信息不丢 |
| 抽词 | 中文 bigram + ASCII 词元 —— 不引 jieba;路径、命令、常量名原样保留(MAX_TEXT_CHARS、pnpm、/srv/app/deploy) |
| 打分 | 段级 BM25(k1=1.2, b=0.75);含 _ - . : / \ 的精确形态词加权 ×2.2(这类词命中率极高,也正是记忆里最常搜的东西) |
| 返回 | 只回段号 / 小标题 / 字数 / 命中词 / 命中位置的上下文片段(围绕命中的词取,不是固定截开头),正文一个字都不回 |
性能与效果:切段 + 全量定位在几百条量级只要几十毫秒(纯 CPU);用每条记忆自己的要点当 query 回查,能全部命中到自己的正文段;1.5 万字的记忆取 2 段 ≈ 1200 字。
三个坑(都已处理):
1. 空行不是硬边界:会话导入的「当时的思考」内部全是空行,按空行切会把它剁成几十个没名字的碎片(实测 1.5 万字 → 64 段、段名全丢)。→ 先按标题切节,节内不打散。
2. 段数超上限时必须合并尾部,不能截断:一份 1.2 万字的部署命令清单有几十个小节,截断会静默丢掉后半截(实测覆盖率 81.8% → 91.5%)。另外覆盖率不到 100% 是正常的 —— 段里不含小标题行与 --- 分隔线,标题信息在段号的 label 里。
3. 预览不能固定截段开头:关键词常落在段的中后部(实测一条 532 字的段里, font_.png / click_font / 路径全在中后段),而预览只截前 80 字,于是模型看到的是开头那句闲聊 ——它据此判断「词碰上了、内容没对上」,直接跳过 memory_detail,白白浪费一次已经成功的定位。
→ 预览改成围绕最先出现的命中词取上下文(前 36 / 后 76 字,两端补省略号)。
边界:短条目(不足 900 字)整条就是 1 段,切片对它没意义 —— 直接用 memory_detail 取全文即可。
时间通道
「九天前我问了什么」「10月6日做了什么」这类问题,向量检索答不了 ——记忆的各层文本里从来没有时间戳,日期型 query 在向量层没有任何落点(只会靠正文块零散撞词,把语义相近但时间无关的长文刷上来)。
所以 query 里带日期或相对时间时,直接走结构化过滤:按本地日历日筛出那天的条目,按发生时间列出,并把涉及到的会话 id 一并给出(顺着 session_read 就能读回那天的对话原文)。认这些形态:
| 形态 | 例子 |
| 绝对日期 | 2026-10-06、2026年10月6日、2026/10/6 |
| 月日 | 10月6日、10月6号(晚于今天则算去年) |
| 相对日 | 今天、昨天、前天、大前天 |
| N 天前 | 9天前、九天前、十五天前(中文数字也认) |
⚠️ 时间字段的可信度是分来源的:只有助手自己写的条目(source.origin 为 agent / panel),时间字段才代表「事件发生时间」;从旧环境导入的存档,时间字段是落库时间(那批条目全挤在导入那一天),拿它做时间反查会把整批一起捞出来 —— 所以这类来源不参与时间反查。
AI 要点总结
- 可以不写 summary 直接写记忆:后台队列自动用 ctx.llm.stream 压成一句话(实测 600~900ms)。
- 也可以直接给 summary(写入方刚做完事,最清楚要点)——状态记作 manual。
- 总结失败不阻塞写入:规则摘要兜底,状态标 failed,面板可一键重跑。
- 默认路由 deepseek-official / deepseek-flash,可在面板或 memory.json 的 summaryProvider / summaryModel 改。
- 面板「测试 AI 总结」一键验证路由与凭据。
会话导入
面板的「导入会话」按钮:列出该工作区的历史会话 → 勾选 → 预览 → 导入。
每条记忆 = 你当时说的话 + 思考原文 + 用到的工具 + 结论;深层原文直接存、不过 LLM(会话正文的量级不是模型能吞的),要点交给 AI 总结队列。导入的条目带 source.origin === "import",和普通记忆各记各的「已导过」,重复导入走更新分支、不会翻倍。
注入策略(面板四档)
- 一行提示(默认,v2.7 起):只注入 40 字 ——「你有其他会话的记忆,通过 memory_index 查看目录,详细善用 memory_recall。」目录、要点、原文全部走工具按需取
- 关闭:完全不注入 —— 目录用 memory_index、要点与原文用检索工具按需取
- 索引:Memory.md(浅层要点)+ ★ 置顶记忆的要点,每轮进 system prompt
- 全量:所有条目的要点逐条注入
v2.6 曾经默认「关闭」,实测的代价是记忆没有存在感:模型看不到任何与记忆相关的字眼,遇到「之前做过什么」时会直接去翻磁盘文件,而不是先 memory_recall。
v2.7 起改回「一行提示」:常驻只留一句提醒「你有记忆」,其余仍然按需取。
旧数据会在首次启动时自动迁移(v3 的 index/full → v4 的 off → v5 的 hint),只迁一次 ——之后你在面板里改档位不会被再重置。
标了 meta: true 的条目(施工/状态记录之类,生来就会过期)只留在库里可检索,不进目录。
工作区改名怎么办(自动认领)
记忆是按路径存的,目录一改名,路径就变了。原来的结果是:新路径是一个空工作区,旧记忆还在库里躺着没人找。
现在每个工作区都有一份跟路径无关的身份:
- 强指纹 = git remote URL + package.json 的 name —— 一个仓库搬到哪都是它
- 弱指纹 = 顶层条目名(排序后取前 30 个)—— 没有 git 也没有 package.json 的工作区只能靠它
新建工作区之前先试着认领:
| 情况 | 行为 |
| 强指纹一致 且 旧路径已不存在 | 自动认领(连带搬向量、重写 Memory.md) |
| 弱指纹一致 且 旧路径已不存在 | 自动认领(记 migratedFrom,可追溯) |
| 旧路径还在 | 只提示,不搬 —— 可能是复制出来的副本 |
| 多个候选身份相同 | 一律不动,等你在管理台点名 |
判不准就不搬:两条工作区的记忆混在一起,比「暂时看不见」糟得多。手动搬家在管理台里(workspaceMove,合并而不是覆盖,按 id 去重)。
界面与管理台
| 入口 | 位置 | 用途 |
| 右上角「🧠 记忆」 | 会话标题栏 | 打开全屏记忆面板;徽章显示当前工作区的条数 |
| 设置页「记忆」 | 系统设置 | 同一个面板的入口 |
两个入口打开的是同一块全屏面板:左边所有工作区、右边条目管理,按 Esc 或点空白处关掉,会话还在后面没被卸载。
面板本体是一个自包含页面 GET /api/session-memory/page,用 iframe 载在应用内 —— 不跳系统浏览器。
这点是刻意设计的:跳浏览器依赖默认浏览器存在、依赖 127.0.0.1 不被防火墙拦、依赖用户认得 dsh-app:// 这种协议,打包给别人用时任何一环断了都表现为「点了没反应」。想要真正独立的窗口(拖到第二块屏、跟对话并排),面板里点「独立窗口」——由 host 侧 openConsole 起系统浏览器打开同一个页面。
面板里能做的事:
- 状态条:嵌入模型状态 · 浅层 N · 深层 N / 总数 · 待总结数 · 注入模式 · 「重建索引」「重新总结」「加载模型」「重载数据」
- 按工作区切换(跨工作区浏览,不用切会话):列表浅层要点为主,深层原文可折叠(带总结状态)
- 新增 / 编辑 / 删除 / 置顶 / 锁定;条目多了可以按标签筛
- 三个页签:预览注入 / 检索测试 / Memory.md(预览 + 编辑手记区块)
- 工作区管理:看身份指纹与认领记录、改归属(workspaceMove)、一键重建向量
HTTP 接口
- GET /api/session-memory/page —— 管理台页面(自包含 HTML,不依赖 DSH 前端运行时;改 assets/console.html 刷新即生效,不用重启)
- POST /api/session-memory/<method> —— body { sessionId, input },返回 { ok: true, value } | { ok: false, error: { message } }
status clientReady clientError list create update remove setEnabled setInjectMode
clear setBudget setSummaryRoute setTimeouts reindex resummarize recallTest warmup testSummary
readMemoryFile writeManualBlock
importList importPreview importRun importProgress(历史会话导入) workspaceEntries workspaceMove workspaceRemove claimInfo refreshIdentity(跨工作区 / 认领) reloadStore(从磁盘重读记忆与向量,不重启) openConsole(起系统浏览器开管理台) diag(排障:请求来源与本机地址)
嵌入模型与向量化
检索不靠关键词,靠语义向量 —— 所以「那个编码的坑」能找到「PowerShell 写出 BOM」。这一层是怎么跑的:
模型:Xenova/bge-small-zh-v1.5(512 维,ONNX q8,约 23MB),落在 $DSH_HOME/session-memory/models/Xenova/bge-small-zh-v1.5/。首次启动自动拉取,之后零联网。
实测:加载 ~35ms、编码 ~2ms/条、上千条 × 512 维暴力余弦 ~1ms —— 这个量级不需要向量数据库, JSON + 内存暴力检索就够,还省掉一个原生依赖。
写入记忆时(memory_write,或会话导入)—— 同一条记忆编出两种粒度的向量:
1. 要点(summary)单独编码成一个浅层向量 s:它短、语义集中,就是这条记忆的「墓志铭」;
2. 正文(text)按 380 字分块(尽量在换行 / 句号处断开),每块编码成一个向量,单条最多 24 块,到上限时末块承载结尾,保证头尾都被索引 —— vectors.json 里 d 是一串 base64(每个元素是 512 维 float32 的原始字节)—— 比写成数字数组小 37%、落盘快约 4 倍。
取出记忆时(memory_recall):
1. 把 query 编码成同一个 512 维空间里的向量;
2. 对每条记忆算两个分:浅层分 = 与要点向量的余弦;深层分 = 与它全部正文块的余弦里最大的那个;
3. 取更相似的那一层作为排序依据 —— 浅层通常赢(定位准又省),只在正文后半段出现过的细节靠深层兜住;深层分要有资格才被承认(见上「排序分层」),否则退回要点分;
4. 排序后过一道噪音过滤,只把「要点 + id + 相似度」交回去 —— 向量本身不占 token,真要原文再用 memory_locate / memory_detail 分层取回。
一句话:向量决定「哪一条、哪一段值得看」,token 只花在真正要看的那部分上。
超时:AI 总结 45s(summaryTimeoutMs)、嵌入 120s(embedTimeoutMs),范围 3s~10min。
总结超时用 AbortController 真中断;嵌入超时只是不再等待。
降级链(每一层都兜得住,不会因为嵌入不可用就瘫掉):模型缺失 / 加载失败 → memory_recall 退回关键词检索;某条浅层向量还没算出来(刚写入、要点待总结)→ 该条只按深层分参与排序;单独一条完全没向量 → 它不会进 memory_recall,但 memory_search 仍找得到。
兼容性
- DSH 版本:在 0.2.0-rc.2 下开发并测试;0.1.x 不兼容(插件 API 有变动)
- 操作系统:Windows / macOS / Linux 均可(纯 JS + N-API 预编译的 onnxruntime,与平台 Node 的 ABI 无关)
- Node.js:>= 22.12.0(node:sqlite 等新 API 的底线;实际运行在 DSH 的进程里)
- 浏览器:面板走 DSH 官方 slot 机制,Chromium 内核浏览器均可
- 体积:插件自带 @huggingface/transformers + onnxruntime 约 150MB(随 profile 的 node_modules 走),装的时候 pnpm 报 Ignored build scripts: onnxruntime-node 可以无视(N-API 预编译,ABI 无关,实测可用);嵌入模型额外约 23MB(首次启动自动下载到 $DSH_HOME/session-memory/models/)
- 规模上限:单工作区 500 条;深层正文上限 20000 字;浅层要点上限 200 字
数据安全
memory.json / vectors.json 里都记着版本号,读入时按版本决定行为 —— 下面三种情况都不会让你的记忆悄无声息地消失:
| 情形 | 行为 |
| 版本一致 | 正常读写 |
| 数据版本比插件更新(先用了新版插件,又换回旧版) | 只读保护:照常可读可检索,但绝不写回 —— 写回去会丢掉新字段;日志与面板都会说明原因 |
| 数据解析失败(文件损坏、手工改坏) | 先把原文件自动备份到 backups/,再按空库启动 —— 而不是静默显示「没有记忆」 |
| 版本更旧且备有迁移步骤 | 迁移前自动备份,然后逐级升级 |
| 另一个实例正在写(同一个 $DSH_HOME 开了两份 DSH) | 单实例写锁:拿不到锁就进只读保护 —— 两边都是「全量读改写」,同时写会互相覆盖。想写入请先关掉那个实例;确认它已异常退出的话,删掉 write.lock 再重启即可 |
备份落在 $DSH_HOME/session-memory/backups/,文件名形如 memory.json.2026-10-16T13-18-41.bak(可排序即时间序),每个数据文件最多保留 5 份。
写锁落在 $DSH_HOME/session-memory/write.lock;崩溃残留的锁会被自动接管(先看持有者 pid 还在不在,再看时间戳兜底)。
迁移链目前只有一条 vectors v2 → v3(向量从「数字数组」改成 Float32 base64:体积 -36%、落盘 CPU -78%);下次再改数据结构时,往 lib/migrate.js 的链里加一条函数,老用户打开插件就会自动升级。
已知边界
- 只按工作区(会话 header 的 cwd)分桶,没有跨工作区全局层
- bge-small-zh 的相似度整体偏高(无关也能到 0.4+):排序可信、绝对阈值不可信 —— 这也是噪音过滤要四路信号的原因
- AI 总结是额外一次模型调用,失败降级到规则摘要,不会堵住写入
- 记忆是单个 JSON(memory.json + vectors.json),没有增量存储;几百条量级无感,上千条需要重建索引
- 工具层守卫只在全新会话的第一个动作出手:会话里已经有过对话轮次就不拦(读不到会话结构时按「有上下文」处理,宁可少拦)
- 工作区认领靠指纹,指纹只能证同、不能证异:同一个仓库的两份克隆会被判成「旧路径还在」而不搬,这种情况用管理台的「改归属」
- 中英混排会让段级 BM25 的长度归一化失真:抽词对英文按词切、对中文按 bigram 切,同一段字数下 token 数差好几倍(实测真实数据里 79% 的多段记忆,段间 token 密度差 ≥2 倍,中位数 3.58 倍),于是英文段被系统性高估。
试过改用「字符数归一化」:能修好语言偏差,但对长段的惩罚更重,净效果打平 —— 729 个真实查询实测,91.2% 的 top-1 不变,变化的 8 个里新旧各胜一半。所以没改;真要解决得按语言分组各自归一化,等真实使用中出现具体坏 case 再说。
项目结构
├── lib/
│ ├── index.js 宿主半侧入口(插件定义 + 9 个工具 + HTTP 路由 + 注入) │ ├── client.js 浏览器半侧(记忆面板界面) │ ├── store-logic.js 存储层纯逻辑:排序 / 超限裁剪(置顶必须留下)/ 批量删除 / 工作区键 │ ├── sessions.js 历史会话的读取与切段(会话导入与 session_read 的地基) │ ├── noise.js 检索噪音过滤 + 排序分层 │ ├── segments.js 段级引索:切段 + BM25 定位 │ ├── lexical.js 词法通道:中文 BM25 + RRF 融合(与向量通道并列) │ ├── rerank.js 交叉编码重排(可选,默认关) │ ├── discriminants.js 判别串抽取与命中加分 │ ├── supersede.js 覆盖链:新条目取代旧条目,旧的不删只降权 │ ├── workspace-id.js 工作区身份指纹(强 / 弱,跟路径无关) │ ├── workspace-migrate.js 改名认领判定(够不够格搬、搬哪去) │ ├── timehint.js 时间提示解析 │ ├── localtime.js 时区检测与时间显示(时间轴都走这里,不用 toISOString) │ ├── lock.js 单实例写锁的状态判定 │ ├── migrate.js 存储版本判定、迁移链、备份命名与保留 │ ├── vector-codec.js 向量 Float32 ↔ base64 编解码、v2→v3 迁移 │ └── model.js 嵌入模型清单与下载地址 │ (以上除 index / client 外都是纯函数模块:零依赖、可单测) ├── tests/ node --test:245 条用例(分数取自真实回放) ├── types/ 外部模块的类型声明(供 tsc --checkJs 使用) ├── assets/icon.svg 插件图标 ├── assets/console.html 管理台页面(自包含 HTML,改完刷新即生效) ├── assets/mindmap.svg 工作原理思维导图(README 用,图上文案是手写的) ├── assets/architecture.svg 会话生命周期与检索判断链(README 用) ├── cordis.patch.yml 包自带 patch(安装后自动进层栈) ├── CONTRIBUTING.md 贡献指南(改代码前先看) ├── .github/ issue / PR 模板 + 两个 CI job(单测 / 类型检查) ├── tsconfig.json tsc --checkJs 的配置(拿真类型校验 JSDoc) └── package.json
数据落点:$DSH_HOME/session-memory/(memory.json / vectors.json / models/),外加每个工作区根目录的 Memory.md。
开发
npm test # 245 条单测:零依赖、几百毫秒(不需要装任何东西)
npm run typecheck # tsc --checkJs:拿真类型校验 JSDoc(需要先 npm install)
CI 有两个 job:单测(Node 22 与 24 各跑一遍)与类型检查。
改代码之前请先翻 CONTRIBUTING.md —— 里面写了代码地图、三条硬规矩(纯函数必须配单测 / 阈值必须用真实数据回放 / 约束行为要用代码而不是提示词),以及最容易踩的坑。
许可
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
reaxlll/dsh-workspace-recollection
DSH工作区内跨会话记忆插件
相关插件 · 对话 / 记忆
上下文中枢oceanbase/powercontext
上下文跟着工作走 —— 跨会话、跨智能体、跨人。
★ 1,487已汉化DSH
73
智能体记忆库plastic-labs/honcho
Honcho 是记忆基础设施,用来构建有状态的智能体。这些智能体能理解不断变化的人、智能体、群组、项目和想法。
★ 7,894已汉化DSH
71.7
长程智能体控制面loopx-project/loopx
给 Agent 一个目标,让工作持续向前。
★ 6,358已汉化DSH
71.3
智能体上下文数据库volcengine/OpenViking
给 AI 智能体用的上下文数据库
★ 41,379已汉化DSH
71.3
长效机器人团队Fei2411/DS-Bot
DS Bot 是一个基于 DeepSeek Harness简称 dsh的 bot 插件,插件将 dsh 改造为 bot…
★ 5,084已汉化DSH
71.1
可携带记忆层EverMind-AI/EverOS
为什么选择 EverOS
★ 13,778已汉化DSH
71.1