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.mdindex.md摘要
软字节阈值 SIZE_WARN(该整理了,仅告警)4608B4608B4096B
硬字符预算 CHAR_LIMIT(注入 slice 截断点)300020001500

中文 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)。