DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

reaxlll /

reaxlll/dsh-workspace-memory

Verified

DSH工作区内跨会话记忆插件

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@e79efaf7

dsh-workspace-memory 🧠

npm version npm monthly downloads total downloads stars license awesome dsh plugin platform embeddings

给 DeepSeek Harness 的跨会话长期记忆:按工作区隔离、跨会话共享。

DSH 的会话一关,上下文就散了。这个插件把「做过什么、当时怎么决定的、那个坑是什么」沉淀下来, 需要时再分层、按需地取回 —— 常驻 prompt 的永远只有最便宜的那一层。

核心是分层检索:常驻的只有浅层要点(「大概有什么」一眼可见);条目很长时,中间还有一层 段级引索 —— 先定位到「哪一段」,只把那一段取回来;深层原文(当时的思考、命令、结论) 留在本地向量库里,真要细节时才取。实测一条 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-memory

装完重开桌面应用(dsh web 用户则重启 dsh web)才生效 —— 运行中的进程持有内存里的旧插件。 之后点会话头部的「🧠 记忆」就能打开记忆面板。

兼容性:本插件在 dsh 0.2.0-rc.2 下开发并测试(dsh --version 可查看你的版本)。 建议使用相同版本;其他版本如遇问题欢迎反馈。

从源码安装(clone 本仓库后)

本插件是纯 JavaScript、无构建步骤 —— clone 下来就能直接挂载:

git clone https://github.com/reaxlll/dsh-workspace-memory.git
dsh plugin --profile desktop add file:/path/to/dsh-workspace-memory

改源码后重开应用即可(宿主半侧改动必须重启;浏览器半侧刷新页面即可)。

首次启动会自动装好嵌入模型

语义检索需要一个本地嵌入模型(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_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(systemPrompt.section / tools.register / webServer.register),前后端走 /api/session-memory/*

分层结构

层 位置 角色
事实源 $DSH_HOME/session-memory/memory.json 每条 = text(深层原文,可长)+ summary(浅层一句话)
索引 <工作区>/Memory.md 常驻注入 system prompt,写的是浅层要点;底部「手记」区块原样保留
向量 $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_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。

检索噪音过滤

要解决的问题: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(这类词命中率极高,也正是记忆里最常搜的东西)
返回 只回段号 / 小标题 / 字数 / 命中词 / 80 字预览,正文一个字都不回

性能与效果:切段 + 全量定位在几百条量级只要几十毫秒(纯 CPU); 用每条记忆自己的要点当 query 回查,能全部命中到自己的正文段;1.5 万字的记忆取 2 段 ≈ 1200 字。

两个坑(都已处理):

  1. 空行不是硬边界:会话导入的「当时的思考」内部全是空行,按空行切会把它剁成几十个没名字的碎片 (实测 1.5 万字 → 64 段、段名全丢)。→ 先按标题切节,节内不打散。
  2. 段数超上限时必须合并尾部,不能截断:一份 1.2 万字的部署命令清单有几十个小节,截断会静默丢掉后半截 (实测覆盖率 81.8% → 91.5%)。另外覆盖率不到 100% 是正常的 —— 段里不含小标题行与 --- 分隔线, 标题信息在段号的 label 里。

边界:短条目(不足 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",和普通记忆各记各的「已导过」,重复导入走更新分支、不会翻倍。

注入策略(面板三档)

  • 索引(默认):Memory.md(浅层要点)+ ★ 置顶记忆的要点
  • 全量:所有条目的要点逐条注入
  • 关闭:完全不注入

标了 meta: true 的条目(施工/状态记录之类,生来就会过期)只留在库里可检索,不占注入预算。

面板

会话头「🧠 记忆」→ 右侧抽屉:

  • 状态条:嵌入模型状态 · 浅层 N · 深层 N / 总数 · 待总结数 · 注入模式 · 「重建索引」「重新总结」「加载模型」
  • 列表:浅层要点为主,深层原文可折叠(带总结状态)
  • 新增/编辑:要点框 + 正文框
  • 三个面板:预览注入 / 检索测试 / Memory.md(预览 + 编辑手记区块)

HTTP 接口

POST /api/session-memory/<method>,body { sessionId, input }

status clientReady clientError list create update remove setEnabled setInjectMode clear setBudget setSummaryRoute setTimeouts reindex resummarize recallTest warmup testSummary readMemoryFile writeManualBlock importList importPreview importRun importProgress(历史会话导入)

嵌入模型与向量化

检索不靠关键词,靠语义向量 —— 所以「那个编码的坑」能找到「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: [[512], [512], …]。

取出记忆时(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 字

已知边界

  • 只按工作区(会话 header 的 cwd)分桶,没有跨工作区全局层
  • bge-small-zh 的相似度整体偏高(无关也能到 0.4+):排序可信、绝对阈值不可信 —— 这也是噪音过滤要四路信号的原因
  • AI 总结是额外一次模型调用,失败降级到规则摘要,不会堵住写入
  • 记忆是单个 JSON(memory.json + vectors.json),没有增量存储;几百条量级无感,上千条需要重建索引

项目结构

├── lib/
│   ├── index.js       宿主半侧入口(插件定义 + 8 个工具 + HTTP 路由 + 注入)
│   ├── client.js      浏览器半侧(记忆面板界面)
│   ├── noise.js       检索噪音过滤 + 排序分层(纯函数,可单测)
│   ├── segments.js    段级引索:切段 + BM25 定位(纯函数,可单测)
│   ├── timehint.js    时间提示解析(纯函数,可单测)
│   └── model.js       嵌入模型清单与下载地址(纯函数,可单测)
├── tests/             node --test 单测(用例里的分数取自真实回放)
├── assets/icon.svg    插件图标
├── cordis.patch.yml   包自带 patch(安装后自动进层栈)
├── CONTRIBUTING.md    贡献指南(改代码前先看)
├── .github/           issue / PR 模板
└── package.json

数据落点:$DSH_HOME/session-memory/(memory.json / vectors.json / models/), 外加每个工作区根目录的 Memory.md。

许可

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit e79efaf7edf5

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout