dsh-memory 设计文档
August 19, 2026 · View on GitHub
核心设计:压缩检查点自动落盘 + 低频维护提醒制
压缩检查点:node:fs 直写(v1.2.0)
宿主插件由 cordis 原生 import() 加载,运行在真实 Node 环境——ctx.fs 的沙箱(workspace-write)只约束会话工具层,插件自身可用 node:fs 直接写 ~/.dsh,无需审批:
- 压缩事件 → 插件直写摘要到
sessions/今日.md(先读全量再追加,带compactionId标记幂等); - 不依赖模型回合:压缩后会话立即结束/无后续消息,摘要也已落盘,不丢记忆(v1.1.0 及以前靠提醒制,此场景会丢);
- 直写失败(罕见)才回退提醒制(注入 system-reminder,模型执行 + 升级审批)。
低频维护:提醒制保留
备份(7 天)、sessions 轮转(30 天)、超限整理仍走提醒制——插件 ctx.fs 走部署默认沙箱(workspace-write),写这些文件会被拒且无审批通道(审批只在会话工具层);频率极低,审批成本可控。
不设 DSH_PERMISSION_MODE 的原因
设置 DSH_PERMISSION_MODE 会影响所有会话且把权限模式钉死,代价大于收益。
记忆库结构
~/.dsh/memory/
├── global.md # 全局记忆:用户画像、通用偏好、跨项目经验(稳定层)
├── index.md # 记忆索引:主题 → 知识文件路径(稳定层)
├── knowledge/ # 知识库(21 个 MEMORY-*.md,自包含)
├── projects/ # 项目层记忆(半稳定层)
├── sessions/ # 会话摘要(动态层),30 天轮转到 archive/
└── tools/ # 自研工具/脚本登记
注入架构
- 注入时机:仅
agent/session-start(会话开始快照),compact 不重发; - 稳定层:global.md + index.md,注入前剥离时间戳行(stabilize)——保持前缀缓存稳定;
- 动态层:最近 7 天内最新一份 sessions/ 摘要,放稳定层之后注入(缓存失效范围最小);
- 维护提醒:同实例只注入第一个顶层会话(防并发整理);
- 超限注记:注入截断处加
[注:原文 X 字符已截断],避免模型误以为"记忆就这么多"。
前缀缓存纪律(成本关键)
DeepSeek 前缀缓存:命中 0.1 元 vs 未命中 1 元/百万 tokens。
前缀结构:[系统提示+工具定义] → [稳定记忆] → [最近摘要] → [用户消息...]
- 稳定层文件只在实质变化时更新,禁止为记录而更新时间戳——任何字节变化都会使该文件及其后的前缀缓存全部失效;
- 半稳定层(projects/tools)按需更新,不在会话开始注入,不影响前缀;
- 动态层每会话更新,放稳定层之后,失效范围最小。
超限治理(双轨)
| 指标 | global.md | index.md | 摘要 |
|---|---|---|---|
| 软字节阈值 SIZE_WARN(该整理了,仅告警) | 4608B | 4608B | 4096B |
| 硬字符预算 CHAR_LIMIT(注入 slice 截断点) | 3000 | 2000 | 1500 |
中文 4608B≈1500 字,远低于硬预算;双轨并存:软阈值提醒整理,硬预算保证不丢内容。
多会话并发安全
- 维护提醒去重:
maintenanceInjected标志,同实例只注入第一个顶层会话——避免多个主会话并发收到整理指令、并发 edit 同一文件; - 顶层判定:查
SessionHeader.origin === 'subagent'(持久化可靠标记),替代旧 rootSessionId 方案(多主会话/主会话先销毁会误判); - 压缩检查点:同样按 origin 判定,多主会话的压缩摘要都归档(旧逻辑只认第一个)。
compact 记忆刷新
compact 是上下文重置点,但记忆注入仍是会话开始快照(session-start 只触发一次)。因此:
- compact 时追加一条轻提示:"记忆可能已更新,用 memory_search 实时读盘;写入前先读全量避免覆盖他会话改动";
- 不做全量重注入:文件多数时候没变,全量重注入浪费 token 且破坏前缀缓存;轻提示成本≈0。
重入修复(重要事故)
- 症状:
[dsh-memory] session/event 处理异常: session append cannot reenter while another append is being published,压缩检查点提醒+记忆刷新提示注入失败(archivedCompactionIds 已标记 → 摘要永久丢失)。 - 根因:compaction/summary 的 session.append 在发布窗口(appending=true)内同步派发 session/event;而 agent.inject 经 Inbox.mutate 同步 append 'agent/inbox/spliced'(dsh-agent/types/inbox.js:149)→ 触发重入冲突抛异常。
- 修复:处理器同步段只做只读校验/组装,所有 agent.inject 推迟到
setTimeout(0)(发布窗口之外)执行。
调试历史与事故教训
- patch 注册必须用
insert语义(applyEntryPatches:id 不存在会被 warn 跳过),- id:覆盖写法不生效; - agent.inject() 消息必须含 id+role+content+source(管线 turn 启动读 msg.source.kind,缺字段崩溃);source = { kind: "plugin", plugin: "dsh-memory" };
- 宿主沙箱无 crypto/randomUUID(HOST_BUILTIN_INSPECTION 仅 ctx/harness/console/btoa/atob/TextEncoder/TextDecoder);UUID 用 hex(时间戳+随机数)替代;
- 事故:早期「压缩即归档」开发中局部读取尾部+整体覆写,把当日摘要截断为 16 行;靠 DSH 会话日志(多帧 zstd 分帧+JSONL)回放 7 次写入逐字重建。教训:写记忆文件前必须读全量或用 edit 锚定,禁止局部读取+整体覆写。
版本史
v1.2.0(2026-08-18 · 压缩检查点自动落盘)
- 宿主插件
node:fs直写压缩检查点摘要(真实 Node 环境,绕过 ctx.fs 沙箱); - 不依赖模型回合,压缩后会话立即结束也不丢摘要;
- 带
compactionId标记幂等去重;提醒制降级为 fallback; - 零上下文成本:直写成功不注入摘要文本,不增加输入量、不破坏前缀缓存。
v1.1.0(2026-08-18 · 检索质量升级)
- memory_search 全面升级(参考 MiMo-Code 记忆模块移植):
- Unicode 分词 + OR 匹配(中英文混合词拆词);
- tools/ 记忆文件收录修复 + 中文文件名支持;
- TF 加权评分 + 相关度排序 + 相对/绝对分数下限 + 多命中 top 8;
- 精确文件名返回全文、0 结果升级引导、过期复核提醒(>180 天)。
v1.0.0(2026-08-18 · 首个发布)
- 会话开始注入记忆(稳定层 + 最近摘要);
- memory_search 检索工具(主题索引 + 全文关键词搜索);
- 压缩检查点归档、备份 / 轮转提醒(当时为插件零写入,全部提醒制;v1.2.0 起压缩检查点改为 node:fs 直写);
- 双轨超限治理(软字节阈值 + 硬字符预算);
- 多会话去重、compact 记忆刷新、重入修复;
- 路径可移植:homedir() + 环境变量(DSH_HOME / DSH_MEMORY_ROOT / DSH_MEMORY_BACKUP_ROOT)。