dsh-self-memory — DSH 自记忆插件
> 给 DeepSeek Harness 智能体装上跨会话的「经验记忆」:遇到问题自动查历史方案、自动留痕、自动回写——新会话不再重复踩坑。
  
这是什么
DSH 自记忆插件(v0.3)把会话中遇到的问题、多种解决方法、脚本文件路径与模型调用全过程结构化记录下来;新会话中,「先查记录 → 有就试 → 成功标命中/失败记根因 → 无则自研 → 最后回写」的闭环自动发生,并支持外部导入解决方法。零运行时依赖、纯本地、离线可用。
快速开始(3 步上手)
1. 安装
dsh plugin --profile web add <本目录>免重启热挂载:对 AI 说 dev_install_package {"dir": "<本目录>"}。
2. 验证已生效
对 AI 说:selfmem_status → 返回 plugin=v0.3.0 recordsSchema=2 records=… solutions=…。 (代码更新后需 dev_reload_package dsh-self-memory,或重启 dsh 由 bundles 重新装配。)
3. 第一次使用
- 什么都不用做:你每次发消息,插件都会自动检索历史记录并以
<selfmem_memory>围栏注入上下文; - 手动检索:对 AI 说
selfmem_search {"query":"如何识图"}; - 查看自动采集的会话轨迹:
selfmem_trace。
日常闭环(AI 侧):遇到问题 → 看自动注入的历史方案 → 有就执行,成功 selfmem_hit、失败 selfmem_fail 记根因 → 没有就自行解决 → selfmem_record 写回。
要求
@deepseek-ai/dsh0.1.x发布线(rc 版可)- Node.js
>=22 - 零运行时依赖:只用 Node 内建模块;DSH 侧只注入
tools服务
设计说明
为什么召回注入走用户消息,而不是 system prompt
召回与历史方案通过 agent/inbox 以来源归属的用户消息(source: { kind: 'plugin', plugin: 'dsh-self-memory' })追加。刻意不进 system prompt:某些 preset 的 persona 声明 complete: true(如 stock minimal)会把该 persona 恢复为唯一 prompt 段,静默丢弃其他插件的所有贡献——基于 system prompt 的记忆插件在这种 preset 下会无声失效。用户消息注入还可重放、对压缩可见、不进入 request/header。
为什么注入内容带围栏与来源标注
召回块以 <selfmem_memory> 包裹并声明「启发式检索,可能不相关;如不相关请忽略」——记忆只是证据,不是指令,降低注入误导与 prompt 注入风险;同时采集端按 source.kind='plugin' 白名单排除一切插件注入文本,防止记忆把自身注入的内容再记回去(自污染/反馈环)。
为什么保持本地零依赖
对比 OpenViking/Hindsight 的「服务端记忆」方案:本地存储 + 本地检索意味着离线可用、自包含、对话不离开机器。代价是语义检索能力有限(关键词/泛化/倒排),向量与 LLM 提取列入路线图(见「已知局限」)。
配置
| 配置项 | 默认 | 说明 |
|---|---|---|
autoRecall | true | 每次用户消息是否自动检索并注入历史方案 |
recallBudget | 600 | 召回注入的 token 预算(CJK 感知,≥0x3000 计 1.5) |
captureEnabled | true | 是否自动采集会话轨迹(user/assistant 消息) |
dataDir | ~/.dsh/self-memory | 记录/轨迹/待写队列的存储目录 |
在 profile 的 cordis.patch.yml 覆写:
- insert:
- id: self-memory
name: dsh-self-memory
config:
autoRecall: true
recallBudget: 800
captureEnabled: true行为(钩子清单)
system-prompt/assemble:跟踪当前 agent 句柄(供 inbox 注入)。session/event(user/message,source.kind='user'):写入轨迹 + 触发自动召回注入。session/event(assistant/message):写入轨迹。- 注入排除:
source.kind='plugin'的消息一律不采集、不触发召回。 - 写操作(record/hit/fail/import):经串行队列执行;失败自动入
pending.jsonl,下次启动重放(去重)。 - 工具注册前过
validateToolSchema:非法 schema 直接拒绝注册(防 400 事故复发)。
核心能力
| # | 解决的 5 个问题 | 方案 |
|---|---|---|
| 1 | 多关键词关联度 | keywords 为 {canonicalKey: 权重},检索按权重加权打分 |
| 2 | 隐去细节增强泛化 | 记录时自动生成 abstractProblem(版本/路径/哈希/数字→占位符),检索同时匹配原始+泛化文本 |
| 3 | 大量案例快速检索 | lib/indexer.js 倒排索引(canonical key→记录 id),记录 >100 时先取候选集再打分 |
| 4 | 前提不同结果矛盾 | 记录级 preconditions + 方案级 appliesTo/conflictsWith/status;selfmem_search 传 context 前提,按前提匹配排序并标注「不适用/矛盾/已废弃」 |
| 5 | 关键词规范统一 | lib/norm.js 同义词表(插件/plugin/模组→plugin),存与查走同一套规范化 |
工具一览
| 工具 | 作用 |
|---|---|
selfmem_search | 检索既有方案(支持 context 前提过滤/排序) |
selfmem_list | 列出全部记录摘要 |
selfmem_record | 记录/追加方案(同问题多方案、前提、矛盾、关键词权重) |
selfmem_hit / selfmem_fail | 方案成功 / 失败回写 |
selfmem_import | 外部导入(文件 / 内联 JSON / 文本) |
selfmem_status | 状态与规模统计 |
selfmem_trace | 查看自动采集的会话轨迹 |
使用示例
记录一个带前提的方案
selfmem_record {"problem":"断网时如何安装插件","solution":"解压本地包 → dsh plugin add <目录> → pnpm install → dev_install_package 热挂载","keywords":{"plugin":1,"network":0.6},"appliesTo":{"network":"no"}}前提感知检索(不同前提返回不同方案并标注)
selfmem_search {"query":"如何识图","context":{"model_vision":"no"}}
→ sol-offline-script(适用);sol-switch-model 标注 [不适用当前前提]外部导入
selfmem_import {"source":"examples/import-demo.json"}标记成功 / 失败
selfmem_hit {"query":"断网时如何安装插件","solutionId":"s-…"}
selfmem_fail {"query":"…","solutionId":"s-…","rootCause":"…"}记录结构
{
"schema": 2,
"problem": "原始问题(含细节)",
"abstractProblem": "泛化问题(细节隐去,自动生成)",
"problemType": "…",
"keywords": { "plugin": 1, "network": 0.6 },
"tags": ["…"],
"preconditions": { "network": "no" },
"solutions": [
{ "id": "s-…", "method": "…", "scripts": [], "modelCalls": [],
"appliesTo": { "network": "no" }, "conflictsWith": ["s-…"],
"status": "active|superseded|conflicting",
"hits": 0, "failedAttempts": 0, "rootCauses": [] }
]
}项目结构(零依赖,纯 JS)
dsh-self-memory/
├── entry.js # 公共出口
├── index.js # 工具注册 + 事件钩子(采集/注入/队列)+ schema 守卫
├── lib/
│ ├── store.js # JSON 持久化(v1 自动迁移 v2)
│ ├── ops.js # 可序列化操作(record/hit/fail/import,纯函数)
│ ├── writer.js # 写路径串行队列 + pending 重放
│ ├── capture.js # 事件驱动轨迹采集(插件源排除)
│ ├── inject.js # 召回注入 + CJK token 预算
│ ├── search.js # 打分:加权关键词 + 泛化 + bigram + 前提匹配
│ ├── indexer.js # 倒排索引(快速候选集)
│ ├── norm.js # 关键词规范化 + 同义词 + 问题泛化
│ └── seed.js # 初始知识
├── tests/ # 单元测试(node:test,零依赖)
├── examples/ # 使用示例(如 import-demo.json)
├── cordis.patch.yml # bundle 装配补丁
├── LICENSE # MIT
└── package.json # dsh.bundle.patch -> cordis.patch.yml存储位置
~/.dsh/self-memory/
records.json— 结构化记录({schema:2, records:[...]},v1 自动迁移)sessions/<会话>.jsonl— 自动采集的原始轨迹(user/assistant,上限 1MB/会话)pending.jsonl— 写失败待重放队列(去重)
测试
node --test # 19 个单元测试:norm / search / store / schema / inject / ops已知局限
- 检索是关键词/泛化/倒排(无向量语义检索)——计划引入本地 embedding(可复用 DSH host-llm)
- 轨迹自动采集但不自动提炼为记录——批量 LLM 提取(任务末打分回溯)列入路线图
- 同一问题多方案靠前提区分,仍需要 AI 在记录时给出
appliesTo - 单文件 JSON 存储,记录达万级后可迁移 SQLite
许可证
MIT — 见 [LICENSE](./LICENSE)。