Skip to content

dsh-memory-tree

Verified

dsh-memory-tree · v0.4.1 · MIT

AST soft-decision-tree long-term memory for DeepSeek Harness (DSH): SQLite + embeddings, project-scoped, cross-session recall.

Install

dsh plugin add dsh-memory-tree

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Creators

Readme

dsh-memory-tree

给 DeepSeek Harness(DSH)的跨会话长期记忆:AST 结构化索引 + 分层 SQLite + 向量召回。

license node tests npm ci dsh PRs

English | 中文


Agent 会忘事。任务结束,上下文清空,压缩摘要随会话消失;同一个项目里新开会话, 一切从零讲起。一个月前存下的笔记,也不会在新想法出现时自己浮上来。

这个插件把记忆从"上下文里的消耗品"变成磁盘上的资产:

  • 捕获:每条 user/assistant 消息在后台落盘,按"一轮对话"合并;
  • 索引:切成可寻址的 AST 节点(标题/小节/段落/要点),带 embedding 和软相似边;
  • 召回:每轮开始前检索,注入一段有界的 <memory-recall>,不是塞全部历史;
  • 分层:按年龄把数据分到 hot / warm / cold 三个 SQLite 文件,活跃文件不会越用越大;
  • 工具:mem_recall / mem_store / mem_stats / mem_dream / mem_rotate。

没有外部服务、没有原生编译;整个"大脑"就是一个目录,可复制、可检查、可备份、可删除。

快速开始

# 1) 放 key(不要放进仓库)
echo '{"apiKey":"sk-..."}' > "$DSH_HOME/memory-tree/credentials.json"

# 2) 装进应用实际加载的 profile,然后重启 DSH
#    桌面版 → ~/.dsh/profiles/desktop    dsh web → ~/.dsh/profiles/web
node scripts/install.mjs --profile "$DSH_HOME/profiles/desktop" --copy-to

# 3) 在 DSH 里让 Agent 调一次 mem_stats 验证

聊两轮,然后新开会话问第一轮里的内容——召回块会出现。

换机器、npm/tarball、纯离线部署见 docs/DEPLOY.zh.md。

用 Git 安装

git clone https://github.com/qinghuaju/dsh-memory-tree.git
cd dsh-memory-tree

# 选应用实际加载的 profile(桌面版 / dsh web 不同)
node scripts/install.mjs --profile "$DSH_HOME/profiles/desktop" --copy-to

# 重启 DSH,然后让 Agent 调一次 mem_stats

安装器幂等、会备份 cordis.patch.yml,且不会碰你的密钥;不带 --profile 时它会自动选择并提示。

从插件市场安装

DSH 0.2.1-rc.3 的市场默认关闭、且没有预置索引。要发布或使用,先生成 market/index.json, 再到 设置 → 插件 → Discover 添加索引 URL:

node scripts/market-index.mjs --owner qinghuaju --commit <40位sha> --npm
node scripts/check-market.mjs

完整 schema、托管方式与“官方市场”现状见 docs/MARKET.zh.md。

兼容性

在 DSH 0.2.1-rc.3(自带 Node 24)上测试,Windows 与 Linux CI;CLI 需要 Node ≥ 22.5。 插件没有原生编译、没有自己的运行时依赖树——@deepseek-ai/* 从 profile 解析。

为什么不直接用向量库

需求 扁平 chunk RAG 本项目
找到相似文本 ✅ ✅ embedding
定位"上次那个光遗传学方案" ❌ 匿名 chunk ✅ 唯一 AST 路径
命中段落时带出所属小节 ❌ ✅ 一跳激活扩散
解释"为什么召回这条" ❌ 只有一个余弦值 ✅ 六个具名分项 + 审计表
控制活跃库体积 ❌ 无限增长 ✅ hot/warm/cold 轮转
离线 / 不绑定厂商 看情况 ✅ Ollama,或确定性 fake embedder
人类可读、可迁移 看情况 ✅ SQLite 旁边有 Markdown 镜像

原理、取舍、以及"哪些坑是实测踩出来的":docs/PRINCIPLES.zh.md。

工作原理

session/event ──▶ 捕获缓冲(按会话,按轮次合并)
                    │
                    ▼
              AST 解析 ──▶ 节点(path / kind / title / content)
                    │
                    ▼
              embedding ──▶ float32 向量 + 相似边(cos ≥ 0.72)
                    │
                    ▼
   memory.db(热,<30天) ──轮转──▶ memory-warm.db(30–180天) ──▶ memory-cold.db(>180天)
                    │
   agent/pre-step ──▶ 查询 ──▶ 向量 ∪ FTS 候选 ──▶ 混合打分 ──▶ <memory-recall>

打分公式(每一项为什么存在见 PRINCIPLES):

base = (0.55·余弦 + 0.20·关键词 + 0.25·两路一致性) × 层级权重
     + 一跳:父节点 ×0.55,相似邻居 ×0.55·相似度
     + 先验:0.06·重要度 + 0.05·时效(45天半衰) + 0.03·使用频次

实测数据

node scripts/bench.mjs --sizes 1000,10000,30000(本机 Windows / Node 24 / fake embedder, 测的是存储与检索路径,不含网络):

节点数 库大小 召回 p50 召回 p95 轮转 轮转后热层文件
1,001 5.4 MB 11.6 ms 15.2 ms 137 文档 / 0.5 s 0.36 MB
10,003 54.1 MB 55.9 ms 68.4 ms 1,370 文档 / 6.0 s 2.48 MB
30,002 162.6 MB 191.2 ms 258.3 ms 4,109 文档 / 25.6 s 7.23 MB
  • 约 5.4 KB / 节点(其中 1024 维向量约占 4 KB);
  • 热层只保留保留期内的内容——这就是分层的意义;
  • 轮转是"移动整文档",compact() 回收已释放页;
  • 真实使用还要加一次 embedding 往返;自动召回受 recall.timeoutMs(1.5 秒)约束, 超时就静默跳过,不拖慢这一轮。

分层存储

层 文件 默认年龄 向量 检索权重
热 memory.db < 30 天 保留 1.00
温 memory-warm.db 30–180 天 保留 0.90
冷 memory-cold.db > 180 天 默认保留;dropColdVectors: true 可丢 0.78

轮转由定时器(6 小时)、启动后的延迟任务、mem_rotate 工具三处触发;搬的是整份文档 (nodes + vectors + 路径 + 时间戳),不删除内容。丢向量的冷层仍可关键词命中,体积小一个量级。

想要旧的单文件行为:storage.tiered: false。

配置

- insert:
    - id: dsh-memory-tree
      name: 'dsh-memory-tree'
      config:
        embedding:
          preset: pollinations     # pollinations | dashscope | openai | ollama | custom
          dimensions: 1024
        capture:
          enabled: true
        recall:
          autoInject: true
          maxNodes: 6
          timeoutMs: 1500
        storage:
          tiered: true
          hotDays: 30
          warmDays: 180

完整配置项与默认值见 docs/PHASE1.md。

同一份配置也可以写进 $DSH_HOME/memory-tree/config.json(CLI 与插件共用;patch 里的配置优先级更高)。 环境变量:MEMORY_TREE_API_KEY、MEMORY_TREE_DATA_DIR、MEMORY_TREE_EMBED_PRESET。

工具

工具 作用
mem_recall 检索项目 + 全局记忆,可按 AST 路径过滤
mem_store 写入 durable 笔记(跨项目偏好用 scope: global)
mem_stats 各层文档/节点/向量、体积、待嵌入数
mem_dream 去重节点、重建软边
mem_rotate 立即执行一次分层轮转

参数表、返回结构、引擎 API 与 CLI 说明:docs/TOOLS.zh.md。

默认就是安全的

  • 进程隔离:记忆引擎(SQLite、embedding、轮转)跑在独立子进程里;它崩了不会带走对话, 客户端会自动重启,并把现场写进 $DSH_HOME/memory-tree/plugin.log;
  • 只存本地;唯一出网的是送去算 embedding 的文本;
  • 召回内容按不可信资料框定,帧标记会被转义,防止内容越狱出块;
  • 注入消息带来源标记,捕获时跳过——记忆不会记自己;
  • key 只从 env / credentials.json 读,不进日志、不进 prompt、不进 patch (scripts/check-secrets.mjs 在 CI 里强制检查);
  • 不会发帖、评论、发邮件,也不会请求任意 URL(见 SECURITY.md)。

CLI

同一个库可以在 DSH 外直接用:

node scripts/demo.mjs index ./notes --scope project
node scripts/demo.mjs recall "光遗传学 艺术装置" --limit 5
node scripts/demo.mjs stats
node scripts/demo.mjs rotate

常见问题

记忆多了会变慢吗? 召回是对活跃空间的扫描:本机 1k 节点 12ms、10k 56ms、30k 191ms。分层让热层文件保持很小; 超过约 5 万节点时,可以在 store.listVectors 这个接口后面接 sqlite-vec。

可以完全不用云端 key 吗? 可以。embedding.preset: ollama + bge-m3 全本地;测试/演示用 embedding.fake: true。

换 embedding 模型怎么办? 不同模型的向量不可比。换 dataDir(或删掉分层文件)让它重建索引;原文在 Markdown 镜像里。

两台机器能共用吗? 可以把 $DSH_HOME/memory-tree/ 拷过去,但绝不能让两台机同时写同一个 SQLite 文件。 只做单向同步,或只对 Markdown 镜像用 git。多写入者的 CRDT 同步在路线图里。

它替代 AGENTS.md / CLAUDE.md 吗? 不替代。那是你维护的"指令",它是 Agent 自己积累的"历史",两者互补。

对话内容会被上传吗? 只有被捕获的文本片段会发给你配置的 embedding endpoint。没有 telemetry,也不会上传数据库。

路线图

  • Phase 1 — AST + embedding + SQLite、捕获、自动召回、工具、分层文件
  • Phase 2 — 离线巩固:摘要、合并、冲突检测(supersedes)、BM25
  • Phase 3 — 参数化记忆:从召回/决策日志训练可回滚的 LoRA 适配器
  • Phase 4 — 给非 DSH 客户端用的 MCP server、CRDT 多机同步、3D 记忆图谱

细节见 docs/PHASE1.md。

参与贡献

欢迎 issue 与 PR,先读 CONTRIBUTING.md:引擎必须零依赖、行为改动必须带离线测试、 check-secrets 必须保持干净。如果它帮你省下过一次会话,点个 star 能让更多 DSH 用户找到它。

License

MIT