dsh-memory-file
简体中文 · English(精简版)
给 DeepSeek Harness 的文件型长期记忆:每个会话开始注入你自己维护的 Markdown 记忆文件,并提供工具让 agent 读写它、把拿不准的放进待审队列、撤回写错的一条、核对它到底读过什么。 它默认不读你的对话、不联网、不删文件 —— 这几条都能对照源码逐条核实。
截图
三张都是示意图(图内已标注):路径与内容为虚构样本,不是真实界面截图。
| 示意 | 说明 |
|---|---|
![]() |
每个会话的第一次 pre-step 注入一次:全局记忆与工作区记忆各一份,同一会话不重复注入、不重复烧 token。 |
![]() |
打开 autoScan 后,把你自己最近说过、还没合并的话摆在 agent 面前;只认带 source.clientTimeZone 的 user/message。 |
![]() |
记忆文件同目录的结构:MEMORY.md、MEMORY.pending.md、.autoscan.json、backup/MEMORY-<scope>-<时间戳>.md、backup/undo.log。 |
快速上手
- 装:设置(Ctrl+,)→ 插件市场 → 自定义安装 → 填
github:wojiao42/dsh-memory-file→ 重启 DSH。 - 在对话里说「把这条记到记忆里」—— agent 调
memory_add写进工作区.dsh/memory/MEMORY.md,写前留整份备份。 - 新开一个会话:第一条消息里就能看到记忆被注入;用
memory_list/memory_status核对它到底读了什么。
功能
注入
每个会话的第一次 pre-step,插件读取记忆文件并注入。同一会话只注入一次,不会重复烧 token。
七个工具
agent 可调用,你也可以让它调:
| 工具 | 作用 |
|---|---|
memory_add |
写一条事实进记忆文件。可带 source(来源)与 date;pending: true 则只进待审队列不写记忆。写前整份备份。 |
memory_recall |
在记忆文件里做纯文本关键词搜索:多词是「或」、按相关度排序、每条带 文件:行号;中文长词支持部分命中(v1.3.0 起) |
memory_list |
列出记忆文件位置、大小、修改时间、备份份数、待审条数 —— 用来核对插件到底读了什么 |
memory_undo |
撤回最近一次写入:恢复成写入前的整份内容(list: true 只列备份)。会一并丢弃那之后的改动,包括手写的 |
memory_pending |
处理待审队列:list / accept(收进记忆,写前备份)/ drop(只改标记,不删文件) |
memory_scan |
查看/处理「新对话待合并」:status / preview(列出原话)/ scan(立即扫一次会话日志)/ ack(推进游标,表示已处理完) |
memory_status |
报告 DSH_HOME、会话数、同级其它 home、两个记忆文件是否就位 —— 用来发现「历史对话凭空消失」 |
记忆文件格式
就是普通 Markdown,没有私有语法。你可以随时手改。
# 记忆
- [2026-10-02] 喜欢简洁的回答,不要导语(来源:用户原话)
- 每周三晚上有固定安排
配置
插件行的 config 字段:
- id: memory-file
name: 'dsh-memory-file'
config:
maxBytes: 16384 # 单次注入的字节上限,超出会被截断(源码默认 1048576,即 1MB)
dshHome: null # 覆盖 $DSH_HOME(一般不用)
header: '以下是用户长期记忆文件的内容。' # 注入时用的引导语
autoScan: false # ★ 打开后才会读会话日志(默认关闭,见下文)
autoScanMaxTurns: 30 # 单次最多列出多少条待合并发言
autoScanMaxChars: 4000 # 待合并块的字符上限
autoScan:把「新对话」交给 agent 归档(v1.2.0,默认关闭)
打开 autoScan: true 后,每个会话的第一次 pre-step 会顺带做一件事:
- 增量扫描
$DSH_HOME/sessions/,只解码大小变过的会话日志 - 只提取你自己说的话 —— 带
source.clientTimeZone的user/message(运行时注入、技能目录、编排提示词都没有这个字段,因此不会被当成你说的话) - 把"还没合并"的发言(默认最近 30 条、≤4000 字符)附在记忆注入之后,并提示 agent:
- 有把握的事实 →
memory_add(带source) - 拿不准的 →
memory_add(pending: true) - 都没有 → 直接结束
- 有把握的事实 →
- agent 处理完调
memory_scan(action: 'ack')推进游标;不 ack 下次会话继续提示,不会丢
它读了什么、没读什么
| 读 | $DSH_HOME/sessions/**/session.v*.jsonl(.zstd),只读,只用来提取你自己的发言 |
| 不读 | messages 参数(从不解构);助手回复的内容不会被当成"你说的话" |
| 不写 | 会话日志一个字都不改;游标写在记忆文件同目录的 .autoscan.json |
| 上限 | 单文件 >8MB 跳过;单轮读取总量 >32MB 收工(没处理完的下轮继续);坏帧 / 坏 JSON 跳过 |
| 失败时 | 整段扫描包在 try/catch 里 —— 扫描出任何问题都只当"没有新发言",不影响注入,更不影响对话 |
两个必须知道的技术细节
session.v*.jsonl.zstd是追加写的多帧 zstd:zlib.zstdDecompressSync只解第一帧,插件按魔数28 b5 2f fd逐帧切开再解(smoke.mjs用两帧夹具验证了这一点)- 日志里的
user/message大多数不是人说的:判别依据是source.clientTimeZone,没有它的全部跳过
实测开销(一个几十个会话日志的环境)
| 首次全量扫描 | 亚秒级 |
| 之后每次会话 | 只 stat 文件大小,没变的文件不解码(毫秒级) |
| 注入块(30 条) | ~1.7 KB 字符 |
不想让它扫:保持 autoScan: false(默认)即可。此时 memory_scan 仍可手动 scan —— 那是你显式要求的动作,不是默认行为。
有待审就提醒(v1.2.1)
注入时如果待审队列里还有未处理的条目,会在注入块末尾附一行:
<!-- 待审提醒 -->
还有 **3** 条待审记忆(尚未写进记忆文件)。用 memory_pending 的 list 看内容、
accept 收进记忆、drop 丢弃 —— 不用等用户自己去翻文件。
这样待审不会静默积压:每个会话开头提醒一次,处理完自动消失。数不出来(文件损坏/权限问题)就静默跳过,不影响注入本身。
怎么实现「自动更新记忆」(三条路)
判断"值不值得记"不在插件里 —— 它不做模型调用、不总结、不推断。插件提供的是素材(你最近说过的话,来自 autoScan)和护栏(备份 / 撤销 / 待审),判断由对话里的 agent 做。按侵入性从小到大:
路径 1:让对话里的 agent 顺手记(零配置)
在任何会话里,agent 都能看到本插件的工具。在你的约定文件(如工作区 AGENTS.md)里加一条:
会话中发现关于用户的持久事实时:有把握的直接
memory_add(带source);拿不准的用memory_add(pending: true)进待审。不要问用户"要不要记住"。
- 判断发生在有完整上下文的对话里,质量最高 —— 它知道刚才聊了什么
- 不需要任何额外进程、调度或外部程序
- 漏记风险:agent 自己忘了。兜底办法是收尾前回顾一次本次会话
路径 2:打开插件自带的 autoScan(推荐与路径 1 搭配)
autoScan: true —— 插件负责"把新说的话摆到你面前",agent 负责判断。细节见上面「autoScan」一节。
- 触发时机:每个会话的第一次 pre-step,你一发消息素材就到位
- 不会丢:agent 没 ack,游标不推进,下次会话继续提示
- 之所以默认关闭:让"读会话日志"成为你的显式选择,而不是装完就悄悄发生
路径 3:自己的脚本 + 模型判断(无人值守,最重)
定时扫会话日志 → 用一个 headless 会话把候选提取成 JSON → 逐条交给 memory_add / memory_add(pending: true)。
- 插件这边已经备好护栏:写前整份备份、
memory_undo回滚、低置信进待审 - 你还要自己加:单次写入条数上限、冲突不静默覆盖
- 代价:无人值守的判断看不到完整对话,误写概率高于前两条,而且要花钱
三条路对比
| 路径 | 触发时机 | 判断者 | 判断质量 | 外部依赖 | 漏记风险 |
|---|---|---|---|---|---|
| 1 agent 顺手记 | 对话进行中 | 当前对话的 agent | 最高(上下文完整) | 无 | 中(靠 agent 自觉) |
| 2 autoScan 供素材 | 每个会话首次 pre-step | 当前对话的 agent | 高(同一次对话上下文) | 无(插件内置) | 低(未 ack 会再提示) |
| 3 脚本 + 模型 | 定时任务 | 后台 headless 会话 | 中(只见发言碎片) | 调度器 / 常驻进程 | 低 |
建议:1 + 2 一起用 —— 这就是"在自己的机器上全自动、且判断仍有完整上下文"的组合。只有当你要求"完全不参与"时才用路径 3,并务必保留备份与单次上限:无人值守写错一条,错的那条会在之后每个会话里反复生效。
口径与安全设计
核心卖点是可核实性
这个插件的核心卖点不是功能,是可核实性。 单文件、无构建、无第三方依赖:index.js 约 1,040 行(含注释与说明),读完大约十分钟,你就能自己确认它做了什么。
从 v1.1.0 起还多了一层机制:任何敏感操作(列目录、复制文件、读会话日志)都必须在调用处逐个登记用途,smoke.mjs 会核对登记数量与实际调用数量 —— 偷偷加一处,自测立刻变红。
它默认不读你的对话。 只有你显式打开 autoScan 之后,才会读本机 $DSH_HOME/sessions/ 下的会话日志,用来提示 agent"有新的持久事实可记"(v1.2.0 新增,见「autoScan」一节)。
它不做什么(这是设计约束,可对照源码逐条核实)
| 主张 | 怎么自己核实 |
|---|---|
不读 messages |
全文唯一的订阅是 ctx.on('agent/pre-step'),且回调参数里没有解构 messages({ agent, step, signal })。搜索 ctx.on( 只有一处。 |
| 默认不读会话日志 | autoScan 的默认值是 false(源码里 const DEFAULT_AUTO_SCAN = false;),扫描调用被 if (autoScan) 包住并整段 try/catch。smoke.mjs 有专门断言核对这两点,还有一条断言跑真实注入验证"关闭时日志内容一个字都不出现"。 |
| 开着 autoScan 时也只读不写 | 会话日志只用 readFileSync 读取,不复制、不上传、不修改;读取点用 [audit:sessions] 登记,且有单文件 8MB / 单轮 32MB 上限。 |
| 不联网 | 全文没有 fetch / http / https / net / dns / tls / WebSocket。检查 import 语句:只有 node:fs、node:os、node:path、node:zlib(解压会话日志用),加两个 DSH 官方包。 |
| 不执行命令 | 没有 child_process,也没有 spawn / execSync。 |
| 不删、不改名、不改权限 | 没有 rmSync / unlinkSync / renameSync / chmodSync / rmdirSync。(待审条目「丢弃」只是改标记 - [-],不删文件。) |
| 只碰记忆文件附近的那几个位置 | 全部路径由 resolveMemoryPaths() + sidecarPaths(memoryFile) 推导,不接受任何外部传入的路径。见下节。 |
| 敏感操作逐个登记 | readdirSync / copyFileSync 每次调用,上一行必须有 [audit:readdir] / [audit:copy] 说明;smoke.mjs 断言登记数 == 调用数。读会话日志另用 [audit:sessions] 登记。 |
它到底会写哪些文件
| 位置 | 内容 | 谁写 |
|---|---|---|
$DSH_HOME/memory/MEMORY.md |
全局记忆 | memory_add(scope: global) |
<工作区>/.dsh/memory/MEMORY.md |
工作区记忆(默认目标) | memory_add、memory_pending(accept) |
同目录 backup/MEMORY-<scope>-<时间戳>.md |
每次写入前的整份备份 | memory_add、memory_pending(accept) |
同目录 MEMORY.pending.md |
待审队列(低置信条目) | memory_add(pending: true) |
同目录 backup/undo.log |
撤销记录(一行一次) | memory_undo |
同目录 .autoscan.json |
autoScan 游标(已扫描的文件大小 + 已见过的发言 + 未合并队列) | memory_scan、开启 autoScan 时的 pre-step |
没有第六类文件。会话日志是只读的:不进这张表,因为从不写入。
memory_status 做什么
报告"记忆看守"状态:当前 DSH_HOME 指向哪、有多少会话、是否存在其它 home 目录、两个记忆文件是否就位。
起因是真实故障:Harness 换过安装目录(改名后 DSH_HOME 指向新目录),
旧会话因不在新 home 里而从列表消失,看起来像"历史全丢了"。
有了这个工具,agent 可以直接查出来,不需要用户自己发现。
让用户"不用操心"但不失控
自动写记忆的难点不在"能不能自动",而在写错了怎么办。写错一条不像答错一次 —— 它会在每个会话里反复生效。
所以这个插件的分工是:机制自动,内容可见可撤回。
| 场景 | 怎么办 |
|---|---|
| 有把握的事实 | 直接 memory_add 写进去(自动留备份) |
| 拿不准的 | memory_add(pending: true) 进待审队列,等你或 agent 之后 accept / drop |
| 写错了 | memory_undo 一键回到写入前(备份是整份文件,不只是那一行) |
| 想知道记了什么 | 记忆文件本身就是明文 Markdown;memory_list 报位置与大小 |
判断"值不值得记"这件事仍然不在插件里(它不读对话,也就无从判断)。它由每次对话里的 agent 做,或由你自己的脚本做 —— 插件负责把"写错了能撤回"这条路铺好。
设计取舍(先说清,免得期待错位)
它不做判断,也不调模型。 它不会总结、不会推断"什么值得记"。它只做两件事:把素材找来(可选,autoScan)和把你写进去的东西管好(备份 / 撤销 / 待审)。
这是故意的:一旦让插件自己判断,它就得调模型、就得花钱、就得"请信任我",而它的全部价值恰恰是"你能自己读完并核实"。所以分工是:
取素材(可选:插件 autoScan —— 只读本地会话日志里你自己的发言)
↓
判断(对话里的 agent,或你自己的脚本 —— 明文可审)
↓ 写(memory_add / memory_add pending)
Markdown 记忆文件(+ 备份 + 待审队列,都可以用工具撤回)
↓ 读(会话首次 pre-step 注入)
本插件(不联网、不执行命令、不删文件)
代价:判断质量取决于你用哪个 agent —— 插件不替你想。 收益:整个过程你能逐行读懂、逐条核实;写错的成本被备份与撤销压到最低;读不读会话日志,由你一个开关决定。
安装
图形界面(桌面端,推荐)
设置(Ctrl+,)→ 插件市场 → 自定义安装 → 填 github:wojiao42/dsh-memory-file → 重启 DSH。
桌面端的 profile 由应用托管:直接跑
dsh plugin --profile desktop add可能被拒绝,遇到就走上一步。
命令行(Web profile)
dsh plugin --profile web add github:wojiao42/dsh-memory-file
装完刷新页面。
本地源码开发
插件市场 → 自定义安装 → 填本机绝对路径;改完按下面「改完代码怎么生效」重启/重装。
改完代码怎么生效
重启 DSH(插件在 Host 启动时加载)。从本机路径装的是拷贝、不是链接,所以改完代码还得重新装一次。
自测
两套断言都可离线跑,不需要额外依赖:
node smoke.mjs # 51 项断言:12 项审计(静态)+ 39 项行为
node tools/publish-test.mjs # 25 项断言:发布脚本与隐私扫描的离线断言(纯函数,不联网)
smoke.mjs 把「它不做什么」那一节写成了静态断言,跑一次就知道有没有人偷偷改过;它同时做行为测试(假 ctx / 假 agent / 两帧 zstd 夹具)。
任何一条审计断言失败,就不该再信任这个插件。(2026-11-08 实测两套全绿。)
兼容
- 安装源:npm / github / 本地路径
- 平台:DSH 桌面端与 Web 端(host 侧插件,没有客户端界面,因此不声明
dsh.client;靠package.json的dsh.bundle.patch→cordis.patch.yml生效) - 依赖:仅
@deepseek-ai/dsh-llm(构造注入消息)与@deepseek-ai/dsh-tools(注册工具),均为 Harness 官方包;无第三方依赖。package.json里没有engines,也没有peerDependencies(这两个包由宿主提供) - Node:源码从
node:zlib具名导入zstdDecompressSync(该 API 自 Node 22.15 / 23.8 起提供),更旧的版本加载插件就会失败;以源码实际引入为准,用node -v自查
已知边界
- 注入发生在会话首次 pre-step。中途手改记忆文件,本会话不会生效(下个会话才读)。
- 注入内容按
maxBytes截断,且会转义</system-reminder>防止记忆内容闭合框架。 memory_recall是字面匹配,不是语义检索。没搜到不代表没记过。它按相关度排序:全部关键词整词命中 > 部分命中 > 中文长词退化成 2 字片段命中;同分时短行优先。memory_add不去重。重复写入会得到重复行。memory_undo是整份回退,不是"撤销那一行":写入之后你手改的内容会一起丢。只保留最近一次写入前的状态,不能连续回退多步。- 备份不清理:每写一次多一份备份,不自动删除(因为插件不删文件)。介意的话自己删
backup/下的旧文件。 - 待审队列每 scope 一个:工作区与全局各有自己的
MEMORY.pending.md,accept只能收进同 scope 的记忆文件。 - autoScan 首次扫描会把历史也算进来:第一次打开时可能一次冒出几百条(队列上限 300),
memory_scan(ack)一次清空即可。 - autoScan 游标跟着工作区走:
.autoscan.json在记忆文件同目录,换工作区会看到那个工作区尚未 ack 的素材。 - 助手回复不算"你说的话":autoScan 只认
user/message。所以只在回复里出现过的结论("那就定 X 吧")不会自动被找到。
版本
- v1.3.0(2026-11-03):
memory_recall重写检索 —— 多词从「与」改为「或」(全部整词命中排最前)、中文长词允许 2 字以上部分命中、结果带文件:行号并按相关度排序、新增limit(默认 20,上限 200)。起因:记忆里写「三年级奥数」,搜「孩子奥数」旧实现命中 0 行。 - v1.2.1(2026-10-06):注入时附「还有 N 条待审」提醒,待审不再静默积压。
- v1.2.0(2026-10-06):新增
autoScan(可选、默认关闭)—— 读本机会话日志、只提取你自己的发言、附在注入里提示 agent 归档;新增memory_scan工具;会话日志读取纳入审计登记,并有单文件 / 单轮资源上限。 - v1.1.0(2026-10-04):写入前整份备份、
memory_undo、memory_pending(待审队列)、memory_add的来源标注;审计升级为「敏感操作逐个登记」。 - v1.0.0(2026-10-03):注入 + 四个工具(add / recall / list / status),修复工具结果渲染。
收录状态
- 仓库:https://github.com/wojiao42/dsh-memory-file(已带
dsh-plugintopic) - 目标目录:社区精选 awesome-dsh-plugin,
dsh-plugin.org上的条目从它同步 —— 进了这个目录就等于进了插件市场 - PR:#6539(2026-10-03 合并)
dsh-plugin.org:2026-10-03 已收录- 分类:
memory - 不放 npm:市场客户端是 npm 优先、GitHub 兜底,
github:安装即可用 - 投稿流程与命令见 PUBLISHING.md,脚本是
tools/publish.mjs
许可
MIT,全文见 LICENSE。



No comments yet. Be the first to write one.