🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

跨会话记忆 Max-Null/dsh-memory

DeepSeek Harnessv0.19.1
Max-Null4 天前有提交已提供中文文档扫描:含敏感能力

本插件属于 @max-null/ 插件系列——这一系列共同构成 SSID思灵 · Seek Soul in…

34.3/100
综合分
6
GitHub 星标
2,103
npm 周下载
—/5
用户评分

数据截至 2026/10/31 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add @max-null/dsh-memory
npm 源 · @max-null/dsh-memory 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · search
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 4 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包@max-null/dsh-memory @ 0.19.1
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/27 17:54:57

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 40 处,展示前 29 条)
  • 发起网络请求
    client.js:334
    const res = await fetch(`/memory/api/${method}`, {
  • 读取本地文件
    dist/engine.js:92
    if (!existsSync(path))
  • 读取本地文件
    dist/engine.js:94
    const document = JSON.parse(readFileSync(path, 'utf8'));
  • 读取本地文件
    dist/engine.js:99
    const document = JSON.parse(readFileSync(path, 'utf8'));
  • 写入本地文件
    dist/engine.js:103
    writeFileSync(path, `${JSON.stringify(document, null, 2)}\n`);
  • 读取本地文件
    dist/engine.js:107
    const document = JSON.parse(readFileSync(legacyPath, 'utf8'));
  • 写入本地文件
    dist/engine.js:111
    writeFileSync(canonicalPath, `${JSON.stringify(document, null, 2)}\n`);
  • 删除本地文件
    dist/engine.js:112
    rmSync(legacyPath);
  • 读取本地文件
    dist/engine.js:317
    else if (canonicalHeader === undefined && existsSync(legacyFile)) {
  • 读取本地文件
    dist/engine.js:381
    const stat = statSync(file);
  • 写入本地文件
    dist/engine.js:1248
    writeFileSync(backup, readFileSync(file));
  • 删除本地文件
    dist/engine.js:1811
    rmSync(file.path, { force: true });
  • 读取本地文件
    dist/backup.js:46
    if (!existsSync(root))
  • 读取本地文件
    dist/backup.js:49
    return readdirSync(root).filter((name) => BACKUP_FILE.test(name));
  • 读取本地文件
    dist/backup.js:78
    if (!existsSync(diaryRoot))
  • 读取本地文件
    dist/backup.js:82
    names = readdirSync(diaryRoot).filter((name) => DIARY_FILE.test(name));
  • 读取本地文件
    dist/backup.js:98
    if (existsSync(to) && readFileSync(from).equals(readFileSync(to))) {
  • 读取本地文件
    dist/backup.js:98
    if (existsSync(to) && readFileSync(from).equals(readFileSync(to))) {
  • 写入本地文件
    dist/backup.js:102
    mkdirSync(archiveDir, { recursive: true });
  • 写入本地文件
    dist/backup.js:103
    copyFileSync(from, to);
  • 写入本地文件
    dist/backup.js:145
    mkdirSync(dir, { recursive: true });
  • 写入本地文件
    dist/backup.js:155
    copyFileSync(source.from, join(dir, source.name));
  • 写入本地文件
    dist/backup.js:163
    writeFileSync(join(dir, 'manifest.json'), `${JSON.stringify({
  • 删除本地文件递归
    dist/backup.js:203
    rmSync(join(targetRoot, name), { recursive: true, force: true });
  • 读取本地文件
    dist/anchors.js:52
    return existsSync(target) ? 'present' : 'absent';
  • 读取本地文件
    dist/query-log.js:197
    if (!existsSync(this.options.path))
  • 读取本地文件
    dist/query-log.js:199
    return parseQueryLog(readFileSync(this.options.path, 'utf8'));
  • 写入本地文件
    dist/query-log.js:212
    mkdirSync(dirname(this.options.path), { recursive: true });
  • 写入本地文件
    dist/query-log.js:213
    writeFileSync(this.options.path, serializeQueryLog(next));
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/cordis-plugin-loader@deepseek-ai/dsh-storage@deepseek-ai/dsh-storage-domain@deepseek-ai/dsh-storage-json@deepseek-ai/dsh-system-prompt@deepseek-ai/dsh-tools@deepseek-ai/dsh-typert-protocol
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

界面预览

1 张 · 点击查看大图

功能亮点

  • 跨会话明文记忆
  • 模型写入即生效
  • 沿工作区链检索

DeepSeek Harness 的跨会话明文记忆:模型写入即生效,明文可审计,沿工作区链检索,每轮召回带预算

v0.20.02026-10-30
· Added 记忆主题 topic。 与 kind(fact / prompt,区分「记忆还是提示词模板」)正交的一个自由字符串字段,回答的是「这条记忆讲什么」:见闻 / 约定 / 坑 / 偏好 / 事实 / 判据 … · memory_save 新增 topic 参数;memory_list / memory_search 支持按主题过滤。 · 存自由字符串而不是枚举:新增主题不该要求改 schema,而枚举一旦写死,存量文件里出现未登记的值会被门三判成不合规、进而整块读不出来。 · 空白串按「未分类」处理,不落一个看不见的空主题;省略即不写该字段——旧记录读回来仍是 topic: undefined,不需要迁移。 动机是让「自己出去逛到的」和「一起干活时记下的」能分开翻:过去只能靠关键词约定(在正文前缀手写 【见闻】),既污染正文,又依赖人记得住那条约定。
v0.19.12026-10-25
· Added 日记进定时备份。 /diary/*.md 与记忆文件同一轮复制,落在 /diary-archive/:固定目录、只增不删,不参与记忆快照的份数轮转。 保留策略之所以不同于记忆:记忆快照是同一份数据的多个时间点副本,丢一份还有别的份;归档里每一篇都是日记的唯一副本,丢了没有任何地方能重建。所以归档目录名刻意不匹配轮转正则(^\d{8}-\d{6}(?:-\d+)?$),靠名字天然躲开清理,不必给 pruneBackups 开白名单。 · 三档处理:归档里没有 → 复制;逐字节相同 → 跳过;不同 → 以日记为准覆盖(人可能事后改过错字)。 · 归档里有、日记里已没有的篇目一律保留——那正是归档的价值。 · manifest.json 增 diary: { root, copied, kept },记这一轮归档做了什么。 · 日记归档先于「记忆文件是否存在」的判断执行:只剩日记可备份时照样归档。 · 修正 备份能力归包,不归宿主壳。 日记备份曾被写进思灵的 shell/scripts/backup-storages.mjs,那等于「装了那个壳才有日记备份」,而日记是本包产生的数据。改在包内后,换任何宿主编排这条能力都成立。
v0.19.02026-10-24
· Added 日记:第二套记忆。 新增 diary_read / diary_write,正文落在 /diary/YYYY-MM-DD.md。 记忆与日记的差别不在存储,在写的时候记什么:记忆记「从中得出了什么」(准入判据是有用性),日记记「今天发生了什么」(没有准入判据)。memory_save 那套判据对「他说手麻了」这类经历本来就失效——那不是事实,是经历。 · 一天一个文件,按 ## HH:MM 分段;同日多场对话是追加,不覆盖。 · 日记正文不经过 ctx.storage:那条路是 *writer per process and last-write-wins*,日记不该被另一个实例的「读—改—写」周期吃掉。 · 不参与检索、不占常驻注入预算;注入里只有一行指针(目录 + 最近日期)。 · 指针在距上次写日记超过 6 小时后附带一句提醒,同一次挂载只出现一次。 Config schema(schemastery)。 本包此前只有 TS 接口 MemoryConfig,没有可被 loader 识别的配置 schema,因此在插件页里是空白。现在导出 Config,并把 diary.enabled 标 .volatile()——表单由 loader 依 schema 自动构成,写进 profile 的 cordis.patch.yml,本包不另设存储文件。 · 边界 · 关闭日记后两个工具拒绝执行而不是被注销:动态注销要响应式状态,而插件配置改动走 cordis 的 entry 重跑——为一次开关把注册生命周期做成可变的,代价大于收益。拒绝信息里写明「已有日记不会被删」。 · 提醒搭在指针行上而不是 agent/pre-step 的消息:后者要引入 dsh-llm 的 createUserMessage 与三个 peer 依赖,而效果只是从「system prompt 里一句话」变成「对话里一条消息」。 · 除 diary 外的配置字段一律透传(schemastery 字段默认可选),不加 .default():它们的默认值依赖运行时($DSH_HOME、平台路径),schema 里硬填一个会顶掉 MemoryConfig 的 ?? 默认 解析。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-memory 本插件属于 @max-null/ 插件系列——这一系列共同构成 SSID(思灵 · Seek Soul in Darkness) 桌面体验。SSID 是整合它们的盒:dsh-capture · dsh-chat-rail · dsh-chinese-thinking · dsh-draft-polish · dsh-guardian · dsh-habit · dsh-memory · dsh-node-appearance · dsh-plugin-center · dsh-quick-toolbar · dsh-skill-mcp-center · dsh-ssid-panels · dsh-ssid-zh-ui · dsh-achievements。 本插件属于 @max-null/ 系列——这一系列插件共同构成 SSID(思灵 · Seek Soul in Darkness) 桌面体验。 一个面向 DeepSeek Harness 的跨会话明文记忆插件。遵循「一切皆插件」——它不修改 DSH 源码,声明 name/inject/apply,由 Loader 从 cordis.yml 加载。
展开完整文档(README 剩余 1 节)
dsh-memory

本插件属于 @max-null/ 插件系列——这一系列共同构成 SSID(思灵 · Seek Soul in Darkness) 桌面体验。SSID 是整合它们的盒:dsh-capture · dsh-chat-rail · dsh-chinese-thinking · dsh-draft-polish · dsh-guardian · dsh-habit · dsh-memory · dsh-node-appearance · dsh-plugin-center · dsh-quick-toolbar · dsh-skill-mcp-center · dsh-ssid-panels · dsh-ssid-zh-ui · dsh-achievements。

本插件属于 @max-null/ 系列——这一系列插件共同构成 SSID(思灵 · Seek Soul in Darkness) 桌面体验。

一个面向 DeepSeek Harness 的跨会话明文记忆插件。遵循「一切皆插件」——它不修改 DSH 源码,声明 name/inject/apply,由 Loader 从 cordis.yml 加载。

设计原则

1. 写入即生效,人是例外干预者:模型写入的记忆直接生效(approved),不再逐条等人放行;人保留随时查看、改写、删除、钉住或回滚的能力——人不在场不等于失控。
2. 可观测先于精准:每条记忆是明文,memory_list 随时可见、memory_forget 随时删除——不存在「静默暗礁」。
3. 明文是人机共享的审计窗口:记忆是可读文本,模型可自检其是否过期(有效性锚点会在所绑环境值变化后标记 stale),人可随时查看与改写。
4. 确定性且缓存安全:BM25 关键词检索是存储的纯函数、无 LLM 调用;固定指引进 system-prompt section,approved + injected 记忆进 recall context(global 全量 + 当前会话工作区),逐条为单行摘要并按注入预算截断(超预算按最近更新优先取舍,省略数在面板可见)。

截图

装完后在设置里多出「记忆」一项,可查看与管理跨会话记忆:

入口: 设置 → 记忆

| 设置入口与面板 |
| 设置 → 记忆 |

用法

npm install @max-null/dsh-memory

在你的 cordis.yml 加一条(其余 storage / system-prompt / tools 由宿主已有;记忆的存储后端由插件自己注册):

- id: memory
name: '@max-null/dsh-memory'

提供的服务与工具

- 服务 ctx.memory:remember / list / search / forget / update / move / setStatus / setInjected / supersede / retract / sweep / demoteStale / verifyAnchors / reload 等
- 工具(14 个):memory_save、memory_list、memory_search、memory_update、memory_confirm、memory_forget、memory_move、memory_sweep,提示词模板四件套 prompt_search、prompt_get、prompt_list、prompt_add,加日记两件套 diary_read、diary_write
- 注入:tool:memory 指引 section(工具用法 + 常驻注入判据)+ memory:self 机制自述 + memory:recall 召回 context(global 的 approved + injected + 当前会话工作区的 approved + injected,带 [memory:<id>:<namespace>] 来源标记;摘要化 + 预算截断)+ memory:diary 指针(只有目录与最近日期,不含正文)
- 检索:BM25(CJK 单字 + 2-gram,content 与 keywords 字段分离加权;中文多字查询精度显著优于单字切分);可选语义融合(见「可选配置」)

日记(第二套记忆,2026-10-24)

记忆与日记是两套东西,不是同一套的两个视图:

| | 记忆 | 日记 |
| 写什么 | 从中得出了什么 | 今天发生了什么 |
| 准入判据 | 有用性 | 无——写就是了 |
| 怎么被读到 | 检索命中 / 常驻摘要 | 整篇读 |
| 存储 | ctx.storage 里的 JSON 块 | <DSH_HOME>/diary/YYYY-MM-DD.md |

日记正文永远不经过 ctx.storage:那条路的写入模型是 writer per process and last-write-wins,而日记不该被另一个实例的「读—改—写」周期吃掉。日记也不进检索、不占常驻注入预算——注入里只有一行指针(目录 + 最近日期)。

- 一天一个文件,按 ## HH:MM 分段;同日多场对话是追加,不覆盖。
- 工具:diary_read(省略 date 读最近一篇)、diary_write(写一段)。
- 写进日记的是经历:发生了什么(有细节)、我怎么想(可以有情绪、可以没结论)、关于他(他说了什么、我注意到了什么)。不写任务清单(那是 git log),不写结论(那是 memory_save)。
- 提醒:指针行在距上次写日记超过 6 小时后附带一句提醒,同一次挂载只出现一次。
- 开关:插件页的「日记」开关(diary.enabled,默认开)。关掉后两个工具都拒绝执行、指针不注入;已有 md 文件原样保留,不会被删。

设计依据、取舍与未决项见 docs/设计/2026-10-24-日记机制-设计方案.md。

两层存储(global / project)

记忆按 namespace 分两层物理存储,各落在独立的明文 JSON:

| namespace | 默认位置 | 用途 |
| global | $DSH_HOME/storages/memory.json | 跨项目的个人偏好 |
| project | <cwd>/.dsh/storages/memory_project_<hash>.json | 跟随仓库的项目共识,可 git 分享 |

两个根都可用 config 覆盖(globalRoot / projectRoot)。memory_list / memory_search 不带 namespace 过滤时会同时查两层。旧版双重前缀文件名(memory_project_memory_project_<hash>.json)在打开时自动迁移为规范名。

主题(topic,0.20.0)

namespace 管「在哪生效」,topic 管「这条讲什么」,两者正交:

| 字段 | 回答的问题 | 取值 |
| namespace | 换个项目还成立吗 | global / project |
| kind | 是记忆还是提示词模板 | fact / prompt |
| topic | 这条记忆讲什么 | 自由字符串:见闻 / 约定 / 坑 / 偏好 / 事实 / 判据 … |

memory_save 传 topic 落库,memory_list / memory_search 传 topic 切片。留空或省略即「未分类」。用它把「自己出去逛到的」(见闻)和「一起干活时记下的」分开翻,不必再往正文里手写前缀。

跨工作区可见性(0.10.0 / 0.11.0)

project 记忆按会话工作区分文件存放,所以「工作区」就是可见性的边界。0.10.0 起补了三个方向——针对的是同一个盲区:不知道存在(检索是有意图的动作,搜不出自己不知道存在的东西)。

| 方向 | 机制 | 效果 |
| 向上 | 祖先链 | 在子目录里开会话能检索到父目录的记忆(反向不行,链是单向向上的),结果带 .. / ../.. 来源标记 |
| 向下 | 子项目索引行 | 注入里多一行「本工作区下另有 N 个子项目带记忆:xxx(M 条)——用 memory_search 检索」 |
| 全局 | 记忆索引行 | 注入里多一行「索引:当前可见 N 条记忆,主题集中在 X(14)、Y(12)…——用 memory_search 检索」 |

三条边界:写入永远只落当前工作区(「我在这个项目里记的东西」不该被推理到别处);但检索得到的记录就改得动——memory_update / memory_forget 同样沿链定位;注入路径只读当前工作区(远处的记忆进检索、不进每轮成本)。

0.11.0 起,「放错了层」有了显式的修正通道:memory_move(id, to) 把一条记忆移到 global、self(当前工作区)或相对祖先(.. / ../..)。它是例外动作——默认写入仍然只落当前工作区,只有当你判定「这条其实属于上一层、或上一层的那一支」时才动它。移动保持 id、从源层删除(不是复制),并报告移动后源层是否仍看得见它:向上移动源层不失明(继承只向上,global 人人可见),向下移动则会让源层及其下层从此看不见它。典型用途是把放高了的记忆降回它真正属于的工作区——比如只属于某一个生态的规矩躺在 global 里、漏进了无关项目。判据与边界见设计文档 §3.2。

后两行不占注入预算、自消除(没有内容时整行不出现),且长度常数级——不随记忆增长。索引行里的主题取自各条的关键词词频,与检索用的是同一套词汇,所以索引里出现的词就是能搜到的词。

孤儿文件清点:node scripts/scan-orphans.mjs [根目录] 报出「哪些记忆文件当前代码已经打不开」(历史命名遗留:早期无哈希后缀、双重 memory_project_ 前缀)。只读,不做任何处置——旧文件可能含未过隔离检查的内容,处置需人工拍板。

祖先链诊断与探针:node scripts/ancestor-probe.mjs plan [--cwd <会话 cwd>] 只读地复算该 cwd 的祖先链(哪些层级带记忆文件、引擎会纳入几级),并回答「这个 cwd 上祖先链是否可观测」——若以上祖先层都没有记忆文件,祖先链生效与否行为逐字相同,那是个零差异的观测点。要实测检索侧跨链,用 seed / check / clean 在祖先层造一条临时探针再撤销。前提:探针只对没打开过该层的会话有效——引擎的表打开即缓存,而写路径的新鲜度门(refreshForWrite)只比对 global 与当前 cwd,不含祖先层,被缓存过的层会用陈旧内存态覆盖掉外部写入。

多实例共存(0.7.1)

两个 DSH 实例(例如 DSH web 与 SSiD 桌面壳)可以同时运行、共用同一个 DSH_HOME,记忆不再互相抹掉。

DSH 存储层的写入是「读—改—写全量覆盖」,且官方两个后端都声明不做跨进程协调(storage-json:writer per process and last-write-wins is correct;storage-sqlite:cross-process coordination is out of scope)。插件层的处置是写前重读(等价于 update 前先 select):每次写入在串行区里先比对存储文件指纹(mtime + size),发现磁盘被别的实例动过就先重载再写。代价是磁盘没变时的一次 statSync。

残余窗口只剩两个实例在同一瞬间写——此时后写者赢;同时写不同记忆已不再互相影响。
未覆盖:workspace.json(工作区登记)由 DSH 自己的 workspace 服务写入,插件层够不着,仍会被双实例互相覆盖;根因处置在上游——discussion #6882。当前防线是轮转快照备份(SSiD 侧 shell/scripts/backup-storages.mjs + 计划任务)。

定时备份(2026-10-18;日记归档 2026-10-25)

动机:2026-10-18 的事故——.dsh 下的 storages 被外部整目录删除后,本插件的写入路径(读—改—写全量覆盖)在空磁盘上把内存里的 global 记忆覆盖成一条;而当时 memory.json 没有任何一份存在于 .dsh 之外,无从恢复。

机制:插件启动后每小时(可配)把记忆文件只读复制到 <home>/.dsh-backups/memory/<YYYYMMDD-HHmmss>/,并写一份 manifest.json 记录来源与文件清单。插件关闭(dispose)时再补一次——定时器只在运行期间生效,关 DSH 前最后一段变更靠这一次兜住。保留最近 14 份(可配,只作用于记忆快照),超期自动清理。

日记另走一条路:<DSH_HOME>/diary/.md 与记忆同一轮复制,但落在固定目录 ~/.dsh-backups/memory/diary-archive/,只增不删,不参与上面的轮转。两边的不对称是实的:记忆快照丢一份还有别的份(同一份数据的多个时间点副本),归档里每一篇都是日记的唯一副本,丢了没有任何地方能重建。日记那一侧改过就以日记为准覆盖(人可能在事后改过错字),归档里有而日记里已没有的篇目一律保留——那正是归档的全部价值。归档目录名不匹配轮转正则,因此不需要给清理逻辑开白名单。

无界面:这是行为类功能,不新增按钮、面板或设置项——行为效果即上面那段:部署后 ~/.dsh-backups/memory/ 下每小时多一个目录,目录里是当次复制出的 memory.json、memory_project_.json 与 manifest.json;diary-archive/ 里则按天累积 YYYY-MM-DD.md。

| 项 | 默认值 |
| 备份内容 | memory.json(global)+ memory_project_.json(project)+ <DSH_HOME>/diary/.md |
| 目标目录 | 记忆:~/.dsh-backups/memory/<时间戳>/(在 DSH_HOME 之外);日记:~/.dsh-backups/memory/diary-archive/ |
| 间隔 | 60 分钟(首次延迟 1 分钟);插件关闭时另补一次 |
| 保留 | 记忆:最近 14 份;日记:全部(只增不删) |
| 失败行为 | 只写日志,不影响记忆读写与启动 |

边界:

- 只备份当前工作区的 project 文件(<cwd>/.dsh/storages/);其它工作区的 project 记忆不在备份范围。
- 只读文件、只复制——绝不复用引擎的写入路径。写路径会先 reload() 再合并,磁盘缺失时它会把内存覆盖掉,正是这次事故的成因;备份不能走同一条路。
- 日记按逐字节内容判断是否已备份过,不按 mtime:复制与编辑都会动 mtime,内容才是事实。

使用流程(写入即生效,人为例外干预)

提示词模板库(0.6.0):prompt_search / prompt_get / prompt_list / prompt_add 四个工具管理模板库—— md 文件是唯一事实源(~/.dsh/prompt-library/*.md 为 global;<workspace>/.dsh/prompt-library/ 随工作区分享),前端(记忆面板「模板」tab / 模型工具)检索同一份索引;模板存在即生效(source: agent 角标标识模型新增), 永不注入 system prompt。

模型 memory_save → status: approved,立即生效;命中密钥/凭据规则则隔离(不进检索也不进注入)给 injected 则同时钉住常驻(0.8.0),不受自动规则影响钉成常驻时返回 resident(total / injected / omitted / budget)——钉的那一刻就知道挤掉了几条,不必等下一轮的出局诊断行 memory_search → 关键词/语义召回(只搜记忆;模板走 prompt_search 通道,不会混进候选池)默认最多返回 20 条,limit: 0 取消上限;条数等于 limit 时可能还有更多命中记账只记 BM25 前 5 名——hitCount 是"排前"的信号,不是"被检索到"的面;一次查询的真实命中数看 query-log.json 的 hits 字段人(面板 / 开关) → 钉住 / 删除 / 放行隔离记录 / 回滚 memory_forget → 随时删除(删除始终是人的动作)

常驻开关只由人(或模型显式传 injected)决定。 两条自动规则都已在 0.12.0 / 0.13.0 停用:命中次数不再自动升常驻(它只喂体检报告的候选提示),30 天未命中的自动撤下也改为只计量不生效——撤下来就基本不会自己回去,那种动作不该由一个没有分布依据的阈值决定(阈值来历见 docs/设计/2026-09-29-借阅-dsh-mneme-后的自查.md §二)。两者都不删除任何内容——记忆不会无限累积,也不会被系统自行清空。

低频但关键的记忆(长期约定 / 判据 / 委托)本来就够不着旧阈值,而自动规则停用之后,问题从「够不着」变成了「必须显式说」。这类走显式路径——在 memory_save / memory_update 里给 injected 即钉住,写的是与面板开关同一套语义(injectedAuto: false)。未审核与已隔离的记录不接受注入。
注入有字符预算(默认 1500,config injectionBudget):装不下的条目整条丢弃(不截内容),按「最近更新优先」取舍——所以 injected: true 不等于「每轮真的在场」,它只保证有资格排队。0.9.0 起这种出局不再无声:注入末尾会追加一行 (另有 N 条常驻因预算未注入:…),0.9.1 起列出的是每条的短摘要而不是 id——查 id 是什么的那一步最容易省略,省略了就等于没报;清理干净即自行消失。面板的注入预览里也能展开看明细。诊断行本身不占预算。

记忆还可声明有效性锚点(环境变量 / 工具清单 / 插件版本):所绑的值变化后,这条记忆被标记 stale 并撤下常驻,检索结果里也带出失效提示——环境变了,旧结论就不再被当成仍然正确。

模型写入不需要人点头,但人始终能看见并推翻:面板按「隔离区 / 常驻 / 冷数据」分组,被隔离的记录必须由人放行才会重新进入检索。

为什么明文 + BM25,而不是向量检索

向量检索的记忆本体是一串不可读的数字,过期信息会成为无法观测、无法修复的静默暗礁;BM25 + 明文让每一次召回都可解释、每一条记忆都可见可删。语义(向量)检索作为可插拔的可选项(0.5.2,见「可选配置」)——记忆本体仍是明文,向量仅作为检索辅助字段(vector,明文可读),且以"可观测 + 可修复"为门槛。

明文还有一层跟随仓库分享的好处:project 命名空间的记忆落在项目文件夹内(<cwd>/.dsh/storages/memory_project_<hash>.json),随 git 提交、分享给所有协作者;global 命名空间的记忆留在本地 $DSH_HOME。团队的共识("本项目统一用 Vue3 ")能沉淀进仓库,而不是散落在每个人的本地。FTS5 的 SQLite 二进制无法这样"跟着仓库走"。

可选配置

在 cordis.yml 的 config 里传给插件(均可省略):

- id: memory
name: '@max-null/dsh-memory'
config:
injectionBudget: 1500 # 常驻注入预算(字符;null = 不限制) summaryChars: 80 # 单条注入摘要截断上限(字符) semanticTopK: 5 # 语义侧参与融合的 topK(仅配置 embeddings 时生效)混合语义检索(可插拔;缺省 = 纯 BM25)
embeddings: { embed: '...' 提供 embed(texts) 的宿主函数 或 插件名 ... }
定时备份(2026-10-18;缺省启用,目标在 DSH_HOME 之外) backup: intervalMinutes: 60 # 间隔(分钟) keep: 14 # 保留份数 targetRoot: 'D:/dsh-backups/memory' # 自定义备份根 enabled: false # 关掉日记开关(2026-10-24;也可在插件设置页里直接改,等价) diary: enabled: true

embeddings 接受 { embed(texts): Promise<number[][]>, similarity? } 对象——由宿主装配层提供嵌入实现(如 DeepSeek 嵌入端点);配置后 memory_search 以 BM25 + 语义 RRF 融合,向量增量生成并持久化,嵌入调用失败自动降级为纯 BM25。

SSID 系列

本插件是 SSID(思灵 · Seek Soul in Darkness) 全家桶的一员;也可以单独安装到任意 DSH profile——除设置页的记忆面板外不依赖其它同系列插件。

开发

npm install
npm run typecheck # tsc 严格类型检查
npm test # vitest 单测
npm run build # 产出 dist/
node scripts/verify-loader.mjs # 用 Loader 端到端验证插件可加载
node scripts/scan-orphans.mjs [根目录] # 清点「当前代码打不开」的记忆文件(只读)
node scripts/unzstd-frames.mjs <源> <目标> # 解多帧 zstd(读 DSH 会话日志用)
node scripts/ancestor-probe.mjs plan [--cwd <cwd>] # 复算祖先链(只读);seed/check/clean 验 ④-A
node scripts/analyze-cooccurrence.mjs # 关键词共现分析(记忆之间有没有关系网,只读)
npm run check:memory # 记忆文件存量体检(只读;0 = 全绿 / 1 = 有不合规 / 2 = 用法错)

依赖(peerDependencies,由宿主提供)

@deepseek-ai/cordis(^4.0.1),以及 @deepseek-ai/dsh-storage、@deepseek-ai/dsh-storage-domain、@deepseek-ai/dsh-storage-json、@deepseek-ai/dsh-system-prompt、@deepseek-ai/dsh-tools、@deepseek-ai/dsh-typert-protocol——后面六个声明为 >=0.1.1-rc.1 <0.3.0。

为什么写显式范围而不是 caret:caret 的上界是「下一个 minor」——^0.1.1-rc.1 展开为 >=0.1.1-rc.1 <0.2.0-0,内核一跨 minor 就判定不满足。而 peer 不满足时插件不会被加载,也不会出现在 did not activate 列表里:它在进入 fiber 图之前就被跳过,界面不报错,只表现为少一批功能。写成显式范围让 0.1 线与 0.2 线都能装载。
验证过的内核:0.1.1-rc.2(原基线)与 0.2.0-rc.1(0.12.2 起——typecheck 用其类型定义零错误,并在隔离实例实机加载,三个失败信号全为 0)。下限保持原值不变:它是作者实际验证过的最早版本,比它更早的内核没验证过,不声称支持。
预览分享卡片长什么样 ↓Max-Null/dsh-memory
分享 · DeepSeek Harness Hub
Max-Null/dsh-memory

本插件属于 @max-null/ 插件系列——这一系列共同构成 SSID思灵 · Seek Soul in…

这是你的仓库?认领条目

回到顶部