dsh-plugin-memory-lite
给 DSH 的本地优先跨会话记忆:每轮自动沉淀,空闲时自动整理,需要时按需注入。
数据全部存在本地 SQLite 文件里 —— 不联网、不上传、不依赖任何外部记忆服务。
它解决什么
DSH 的会话本身是有记忆边界的:换一个会话、或者一次压缩(compact)之后, 之前聊过的结论就找不回来了。你得反复重新交代"我们用两层架构""那个坑别再踩"。
这个插件在后台做三件事:
| 层 | 做什么 | 什么时候 | 成本 |
|---|---|---|---|
| 日志层 | 每轮结束把对话原样追加到本地 | 自动,每轮 | 零模型调用 |
| 长期条目层 | 把日志里的结论蒸馏成"记忆条目" | 你点「立即整理」时(也可开启空闲自动) | 每次一次模型调用 |
| 注入层 | 把你这句话相关的记忆作为独立消息追加到对话尾部 | 自动,每轮 | 零成本 |
日志层永远不进提示词 —— 只有蒸馏后的条目才会被注入。 否则上下文预算和前缀缓存都会被日志淹没。
日志层存的不是"逐字原样":落盘前会剔掉宿主注入的系统样板(<system-reminder> 里的
用户全局 AGENTS.md 等),也会去掉与前文重复的部分(实测 10 轮对话曾因此写出 804 KB,
相邻两条有 6/9 对是"后一条完整包含前一条")。没有内容的轮次干脆不记。
安装
命令行:
dsh plugin --profile <你的profile名> add github:eighteentang/dsh-plugin-memory-lite#v1.3.0
或者在界面里装(不用命令行):侧边栏 「插件」→「添加插件」,把地址贴进去。 那个输入框接受包名、Git 地址、压缩包、本地绝对路径四类写法:
| 贴什么 | 结果 |
|---|---|
https://github.com/eighteentang/dsh-plugin-memory-lite#v1.3.0 |
✅ 装指定版本(推荐) |
https://github.com/eighteentang/dsh-plugin-memory-lite |
✅ 跟随主分支 |
github:eighteentang/dsh-plugin-memory-lite#v1.3.0 |
✅ 同上两种写法都行 |
⚠ 别贴 Releases 页的地址。
…/releases/tag/v1.3.0会被直接拒绝 (提示a URL must point at a git repository or a tarball)—— 它只认仓库根地址 或压缩包链接;版本要写成#v1.3.0拼在根地址后面。同理…/tree/main这类网页地址也不行。这几条不是猜的:宿主侧的解析函数
parseInstallSpec曾被抽出来逐例跑过 (那是开发者本机的诊断脚本,不随仓库发布 —— 见下方「关于本仓库不含开发工具」)。
装完后重启客户端,设置页会出现 「记忆控制」。
#v1.3.0是版本钉子 —— 钉住版本避免"装到一半上游改了"。 去掉#…就是跟随主分支(自动拿到新改动,也自动拿到新 bug)。 全部版本见 CHANGELOG.md;升级就是先卸载再装新版 (DSH 的插件目前不支持自动更新)。
开发期用
link:装(link:D:/path/to/memory-lite)也是支持的, 但必须保留peerDependencies,否则插件的包解析会失败。
怎么用
平时不用管
- 每轮对话结束,它自动把这一轮记下来(不花模型额度)
- 每句用户消息到达时,它按关键词找相关记忆,注入到对话尾部
- 整理成条目默认要你手动点(避免"没打招呼就花额度")。
想让它自己长出来,把
autoDistill设成true—— 那样空闲 30 分钟后会自动整理 (每天最多 4 次,见下面配置)。
想手动做点什么
设置页 →「记忆控制」分成上下两块:
上面两个文本框:人设设定 / 工作规范与行为准则 想写什么就写什么 —— 没有任何格式要求(不用标题、不用 markdown)。 两块内容存在插件自己的两个文件里:
$DSH_HOME/memory-lite/persona/persona.md ← 人设设定 $DSH_HOME/memory-lite/persona/rules.md ← 工作规范与行为准则它们被注入成 system prompt 的一段(位置紧跟框架自己的人设段)—— 所以是 每一轮都生效,不像记忆库那样靠召回(规范类的东西"这轮没命中"就等于静默失效)。 保存后下一轮立刻生效,不用重启;写前自动备份到同目录的
backup/。代价:它们随插件走 —— 插件停用/卸载,人设与准则就不生效了。
下面是长期记忆列表:搜索、按状态筛选、改状态(在用 / 已完成 / 已停用)、点「立即整理」。 列表是一行一条、行行等高(
层级 · 项目 · 标题 · 敏感 · 用过N次 · 状态), 不展示正文 —— 标题/项目太长会省略号截断,鼠标悬停看全文。 「用过几次」显示成3×(悬停会说明"被自动注入过 3 次",–表示还没用过)。
想手动记一条记忆:直接在对话里说(memory_note),比在界面上敲更快、还能带上上下文。
- 对 DSH 说(这些是模型可见的工具):
- 「记住:这个项目用两层记忆架构」→
memory_note - 「搜一下我之前说过关于注入的东西」→
memory_search - 「把那条标记成已完成」→
memory_update - 「忘了那条吧」→
memory_forget(删除需要你点头,走审批)
- 「记住:这个项目用两层记忆架构」→
用户级记忆(跨工作区)
有些事跟哪个项目无关:你是谁、做什么工作、说什么语言、跨项目的工作约定。 这类东西写进某个工作区,就只有那个项目能召回 —— 所以要显式写进用户级库:
$DSH_HOME/memory-lite/global/memory.db ← 用户级(跨工作区)
<工作区>/.dsh-memory/memory.db ← 该工作区自己的(默认)
- 怎么写:对话里说「记住(跨工作区):我在深圳,做产品运营」→ 我用
memory_note带scope="user"写进用户级库。默认是当前工作区,我不会靠project字段猜。 - 怎么起作用:每轮召回时「当前工作区 + 用户级」一起搜、合并排序, 同分时工作区优先;同一条记忆只出现一次(按内容判重,不是按 id)。
- 不会拖累提示词:合并后仍受
injectMaxItems那一个名额上限管, 用户级记忆写多少条都不会让每轮注入变长。 - 不进常驻索引段:索引段只有 4096 B,容量留给工作区条目 (所以用户级条目是靠「按需召回」生效,不是像人设那样每轮都在)。
- 面板:设置页 →「记忆控制」的库下拉里会多一项「用户级(跨工作区)」, 可以像别的库一样看、搜、改状态。
还要更"永远在场"的?那是人设/工作规范两个文本框的活(它们每轮都注入)。 分工:每轮都必须遵守的规则 → 那两个框;换个项目还需要的事实 → 用户级库; 跟这个项目有关的事 → 工作区库。
三种状态
| 状态 | 含义 | 之后会怎样 |
|---|---|---|
| 在用 | 内容有效,可能被召回 | 正常参与注入与检索 |
| 已完成 | 这件事做完了(todo 完成、话题达成目标) | 内容仍然有效,仍可被搜到与召回 |
| 已停用 | 这条不再正确或不再需要(过时、被取代、写错了) | 不再被注入,但仍可搜到、可随时恢复 |
数据在哪
<工作区>/.dsh-memory/memory.db ← 记忆库(每个工作区一个)
<工作区>/.dsh-memory/archive/ ← 日志溢出后的归档(jsonl)
$DSH_HOME/memory-lite/global/ ← 用户级库(跨工作区,见上一节)
$DSH_HOME/memory-lite/persona/ ← 人设 / 工作规范两个文本框的内容
$DSH_HOME/memory-lite/workspaces.json ← 库 → 工作区 的注册表
删除记忆:三种方式,按彻底程度
- 设置页把条目改成「已停用」—— 不注入了,但还在库里(推荐,可恢复)
- 用
memory_forget工具删单条 —— 需要审批 - 直接删
.dsh-memory/目录 —— 全部记忆消失,不可恢复
配置
在 profile 的 cordis.patch.yml 里给插件加 config:
- id: memory-lite
name: dsh-plugin-memory-lite
config:
autoCapture: true # 每轮自动落日志(不花额度)
autoRecall: true # 每轮按关键词注入(不花额度)
autoDistill: false # 空闲自动整理 —— 默认关(会花模型额度)
distillIdleMinutes: 30 # 开启 autoDistill 时:多久没说话才算"空闲"
distillMinIntervalMinutes: 30 # 开启 autoDistill 时:两次整理之间的最小间隔
distillMaxPerDay: 4 # 开启 autoDistill 时:每天最多整理几次(成本硬上限)
injectMaxItems: 3 # 单次最多注入几条
injectMaxChars: 2000 # 单次注入的字符上限
crossWorkspaceSearch: true # 允许跨工作区检索
distillProvider: '' # 留空 = 用会话默认模型
distillModel: ''
关于花额度:默认 autoDistill: false —— 日志照记,但只有你点「立即整理」时才会调用模型。
想让它自己长出来就把 autoDistill 设成 true,那时上面三个 distill* 参数才生效
(空闲多久算闲 / 冷却 / 每天上限)。
设计上的几个刻意选择
这些都是踩过坑之后定下来的,改动前请先读 AGENTS.md:
- 注入只走长期层。日志层永不进提示词,否则前缀缓存失效、上下文预算被淹。
- 注入是"只追加":作为独立消息追加在本轮已领取消息之后,不改写已有历史。
- 不重复注入:同一会话里已注入过的条目不再注入;压缩后重置这个记录并补回常驻记忆。
- 敏感内容 fail-closed:
sensitive在写入时算一次并落列,注入时只读列。 注入路径上再判断,等于把 fail-closed 变成 fail-open(判断代码出错就注入了)。 - 删除走审批:
memory_forget声明为破坏性工具,转人工确认。 - 超预算的日志留到下次:不"发都没发出去却记成处理过了"——那会永久丢内容。
- 蒸馏输入先剔系统样板,再取头尾:日志一条 = 一整轮,里面夹着宿主注入的
<system-reminder>(用户全局AGENTS.md等)。实测 10 条真实日志里有 7 条的 "前 1500 字"逐字相同 —— 不剔样板的话,模型看到的不是"这一轮发生了什么", 而是同一段样板。剔完再按 头 40% + 尾 60% 取样(结论在尾部)。 - 常驻索引段每条只列 20 字:这段每轮都在 system prompt 里,中文 1 字 = 3 B。 实测 33 条时它已占到 4096 B 预算的 83.8%(每行 123 B)→ 只装得下约 33 条, 再多就会静默掉出提示词。截到 20 字后约能装 49 条。别指望"回填标题"来救容量 —— 回填的标题同样会被截断,额外容量是 0。
已知限制
- 检索是关键词级的,不是语义向量检索。中文查询按连续串与子串匹配,
所以换个说法可能搜不到(用
memory_search时尽量带上你当时用的词)。 - 每个工作区一个库,默认只搜当前工作区;跨工作区要显式
scope="all"。 - 日志层有容量上限(每会话约 200 条),超出会先写归档文件再删库内行(不静默丢)。
- 空闲自动整理依赖宿主进程活着 —— 客户端关着的时候不会整理。
关于本仓库不含开发工具
本仓库只有插件本身(index.js / lib/ / client/ / locale/ / 图标 / 补丁 / README / LICENSE)。
校验器(verify-*.mjs)、诊断脚本(diag-*.mjs)、迁移脚本与发布助手不在仓库里,
它们只在开发者本机(原因:那些脚本含开发机的绝对路径与个人数据位置,不该跟着插件发给别人;
这也是本项目的一贯纪律 —— 开发产物不进用户包)。
如果你想自己验证这个插件的行为,用它的运行时表现验:
装完之后在设置页看「记忆控制」,或者用 memory_stats / memory_search 这些工具直接问它。
真机行为才是判据。
许可
MIT
No comments yet. Be the first to write one.