dsh-memory-tiers
Six-layer local memory for dsh: captures turns, stages them through an L2 buffer into DL3 summaries via a staging model, injects a stable memory block each turn and replaces chat history with placeholders. Ships five memory tools and a config page.
Install
dsh plugin --profile web add dsh-memory-tiers
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-memory-tiers
给 DeepSeek Harness 用的六层本地记忆插件:
- 每轮对话结束后,把「你问的 / 模型答的 / 工具做的」写进 L1 即时层;
- L1 字数超过上限,下一轮整理就分流成摘要与原文:被摘要引用的原文进 DL2 原文库,没被引用的进 DL1 遗忘中,少量留在 L2 巩固层;
- 摘要进 DL3 摘要库,按热度截出一份 L3 长期视图;
- 每轮把
L3 + L2 + L1拼成一块注入上下文; - 可选地把 dsh 自己的对话历史替换成一条占位——用记忆替代历史,聊再久上下文也不涨;
- 工作模式按会话独立:开过工作模式且对话过的会话会永久保留历史,不再被记忆接管;
- 可插人格(系统提示词之后、记忆块之前),可另开「分流/分类也带人格」;
- 自带记忆星图,随插件打开,读
storage.dir,不另起端口; - 大部分配置保存即生效,只有模型通道等少数项需要重载插件。
全部数据在本地,不依赖任何云端存储;模型只用来做分流(摘要)、融合(判关系)、算向量。
文件
文件 · 干什么
memory.mjs · 记忆系统:六层读写、分流/融合/遗忘、检索、注入块。不认识 dsh,不联网
llm.mjs · 模型层:OpenAI 兼容 HTTP + 「跟随当前会话模型」通道
index.mjs · dsh 适配:订阅事件、注入上下文、5 个工具、/memory 命令、历史压缩、装载诊断
settings.mjs · 配置:唯一的字段表(默认值/类型/范围/说明)+ 读写迁移 + 配置页服务端接口
client.mjs · 配置页(设置 → 记忆)与会话模式标记。配置字段全部由服务端 schema 渲染
star.mjs · 记忆星图页面与数据函数;由插件挂到 /dsh-memory/star,开发可用 tools/star.mjs
cordis.patch.yml · 告诉 dsh 把这个包装进配置树
测试在仓库的 ../test(不进包):npm test 或 node --test ../test/*.test.mjs。
实机验证照 ../docs/VERIFY.md 走;配置页字段核对表见 ../docs/CONFIG-PAGE.md;配套脚本在 ../tools:
preflight.mjs(装机前自检)、inspect-session.mjs(从会话日志 + status.json 验「工具/注入/压缩」)、
collect-report.mjs(一键打包取证)、seed.mjs(造假库看注入块)。
装在 dsh 里
# 在 dsh 的 profile 里 link 这个包(包根目录 = 本目录) pnpm add "link:<这个目录的绝对路径>"
或者由 dsh 的插件管理器安装。装完重载插件,设置 → 记忆 里就能看到配置页。
配置
位置固定:<DSH_HOME>/memory-tiers/config.json(DSH_HOME 缺省 ~/.dsh)。
同目录还有 log.txt(插件审计日志)、status.json(装载与运行状态)、context.txt(/memory context file 导出)。
- 启动时文件不在 → 用内置默认值生成一份;
- 有但残缺 → 补齐并写回;
- 有但坏了 → 改名
config.json.broken-<时间戳>后重建; - 旧版(0.9.x)的
~/.dsh/memory-tiers.json→ 自动迁移,旧文件改名.v1-bak。
改配置:设置页里改,或者直接编辑这个 JSON。大多数项保存即生效;模型通道、存储目录、写者、全局工作模式默认值、整理定时器间隔这几类需要重载插件,页面上会标「需重载」。直接改文件时,插件不会自动重读,仍需重载。
数据目录(storage.dir,默认 <DSH_HOME>/storages/memory)可以随时改、可以搬,
但旧目录里的内容不会自动搬过去——那是你的数据,插件不替你搬。
会话状态在 <DSH_HOME>/memory-tiers/sessions.json(工作模式/接管豁免名单),跨重启和插件升级保留。
六层与七个步骤
层 · 内容 · 进上下文
L1 即时 · 刚发生的原文(含工具做了什么) · 是
L2 巩固 · 近期沉淀的原文 · 是
L3 长期 · 从 DL3 按热度截出来的摘要视图(不落盘) · 是
DL1 · 没被摘要引用的原文,慢慢遗忘 · 否
DL2 · 被摘要引用过的原文,保细节,按需检索 · 否
DL3 · 摘要库,融合去重在这里做 · 否
流程:收(写 L1)→ 分(分类 + 摘要 + 分流)→ 合(新摘要 vs 旧摘要)→
沉(L2 满了连同 DL1 再压)→ 忘(DL1/DL3 按规则清理,先写台账)→
喂(注入块)→ 替(压缩历史,写成功才丢)。
分流规则(两阶段)
两个阶段、两套 prompt,条目本身不带任何标记:
阶段 · 触发 · 模型输出 · 去向
L1 分流 · L1 字数超过 hotChars(不等安静期,下一轮整理就分) · ★ 摘要《行号》 · 摘要 → DL3;被引用的原文 → DL2
· · + 行号 · 原话 → L2
· · 不写 · 原话 → DL1
L2 分流 · L2 满了 · ★★ / ★ / ☆ 摘要《行号》 · 摘要 → DL3(importance 0.9 / 0.6 / 0.3,都算长期记忆)
· · + 行号(最多 l2KeepMax 条) · 原话 → L2,其余 → DL1
· · 不写 · 原话 → DL1
- 没有「
·」这一档了:旧版的·(0.7) 与 first-pass 的 0.9 过滤线结构互斥,会让摘要必灭。 - 只有带标记的行才算数;其它行忽略(原文走「没被引用」的路,沉 DL1)。
- 术语:
summary阶段叫分流模型(挑 + 摘要 + 保留一次完成);chat阶段叫融合模型
(分类 + 判重复/矛盾/进展/新增)。L2 存的是原话,摘要只在 DL3/L3。
清理规则
层 · 规则
DL1 · 遗忘曲线(layers.forgetThreshold)+ 容量上限 layers.dl1MaxChars:超了按「越旧越先清」清到 80%,优先清已整理过的(pendingDigest 的最后动)
DL3 · 只淘汰琐事档(importance < 0.45):按保持率衰减,低于阈值才删;中/高档只靠融合合并/作废;不足 1 天的不动;被删摘要独占的 DL2 原文一起清理
所有删除 · 一律先写遗忘台账(graveyard/forgotten.jsonl),写不进去就不删;pinned 不动
几条硬约束(改代码前先看):
L1.tmp/<writer>.jsonl只追加、追完就关,不长期持有句柄;- 先写目标层、最后写 L1,崩了重跑靠 id 去重;
- 不删别人的临时文件,
rename认领成功才算自己的; - 同一 id 只出现在一层;
- 没有模型就不产生摘要:原文只能下沉 DL1 等模型,不许假装整理过。
工具与命令
名字 · 干什么
memory_write · 明确把一条结论写进记忆
memory_search · 检索(summary / detail / full 三档深度)
memory_attend · 确认哪些检索结果真的相关(热度 +1;只搜到不算数)
memory_forget · 删掉记错的一条(下次整理生效,会写遗忘台账)
memory_stats · 六层条数、待整理、整理锁、数据目录
/memory · list 看六层;context [all\ · file] 导出「模型这一轮收到什么」
/memory context 的导出 = 系统提示全文 + 本轮人格 + 本轮将注入的记忆块 + 其余消息原样;
人格和记忆块实际落在本轮用户消息之前(inject-order),导出里也按这个顺序排。
不列工具定义、不转储请求配置。默认写 context.txt(不刷屏),context all 强制刷屏。
⚠ 注入块不是从会话消息里反推的:dsh 的 systemPrompt.context() 贡献在请求组装时才合成
runtime-context 消息,而命令执行在组装之前(加上「全部替换」的压缩会清掉上一轮快照),
所以会话消息里看不到它。插件用 mem.buildBlock() 按当前库实时构建这一块——文件里那一段就是
本轮真正会注入的内容;注入关闭/工作模式/已豁免时会写明原因。
人格槽位
配置页「人格」组:
项 · 默认 · 说明
persona.enabled · true · 人格注入总开关;关掉后配置正文和外部片段都不生效
persona.text · 空 · 人格正文。空 = 不注入,不留空行
persona.inSummary · false · 分流/分类时临时带人格;默认关,和注入开关独立
- 注入位置固定在「系统提示词之后、用户消息之前」:人格 order 90;记忆块动态取
WEB_SURFACE - 5(通常 205),排在稳定环境段之后、端口/URL 块之前;DSH 默认把快照放在用户消息后,插件用agent/pre-step把它移到本轮消息之前; - 人格逐字稳定:不要写时间戳或每轮会变的内容;
- 其它插件可以通过 Cordis 服务
memoryTiersPersona追加片段,返回 disposer 即可撤销:
const off = ctx.get('memoryTiersPersona').register({ id: 'my-persona', order: 100, text: '…' })
off()
persona.inSummary只改分流/分类的 system 前缀,不改三档判定标准、不写进摘要正文。
记忆星图
安装后打开 设置 → 记忆 → 打开记忆星图,或直接访问 <api.baseUrl>/dsh-memory/star(例如 http://127.0.0.1:7307/dsh-memory/star)。
- 数据来自插件内存里的六层,跟随
storage.dir,不另起端口、不每 30 秒读盘; - 页面 UI 与单独跑
node tools/star.mjs serve时一致;开发时仍可用该 CLI 生成静态快照。
工作模式(专注干活)
工作模式按会话独立,不再是一个全局开关。配置页的 workMode.enabled 只是「新会话默认值」(改它需要重载插件);单个会话在输入框旁的小标记里随时切换。
维度 · 普通 / 记忆接管 · 工作模式 · 已豁免
注入记忆块 · 每轮 · 不注入 · 不注入
替代历史(压缩) · 每轮 · 不压缩 · 不压缩
收 / 分 / 合 / 沉 / 忘 · 正常 · 正常(粗骨架记录) · 正常(粗骨架记录)
抓取/层容量 · capture. / layers. · workMode.(更大) · workMode.(更大)
- 豁免:开过工作模式、且确实对话过的会话,即使之后关掉工作模式,也永久保留历史、不再被记忆接管(落盘在
sessions.json,跨重启/升级保留)。 - 解除豁免:输入框旁标记或设置页会话表里可以解除,但会先提示「历史可能被压到 0」,并要求二次确认。
- 粗记忆:工作模式进 L1 前先压成一条骨架(地点 / 事件 / 做了什么 / 结论),不记命令输出和过程噪音;可用
workMode.skeleton.enabled关掉。 - 状态标记:
工作模式/工作模式 · 已豁免/记忆接管/普通,与实际行为同源。
排障
先看两个文件:
<DSH_HOME>/memory-tiers/status.json→boot.steps(哪一步成功了
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::13,最后更新 2026-11-05