- Startseite
- Plugins
- Gedächtnis
- dsh-full-life-memory
dsh-full-life-memory
toctsu/dsh-full-life-memory
Biomimetisches Gedächtnis-Plugin für DSH: verdichtet Kontext zu Token-Hinweisen und rekonstruiert Erinnerungen bei Bedarf.
Installation
dsh plugin --profile web add github:toctsu/dsh-full-life-memoryREADME
Full-Life Memory
不存原文,只存线索。
像人一样遗忘,也像人一样回忆。
@tuhengyu/dsh-full-life-memory
一个给 DeepSeek Harness 用的「仿生终身记忆」插件:
上下文快满时,它把旧对话蒸馏成几个关键词,从历史里物理删除;
当你再提起那件事,它顺着线索把记忆重新长出来。
上下文不是硬盘。 人不会记住三个月前的每一句话,只留下几个名字、半句原话、一种「我们好像聊过」的感觉。 需要的时候,再顺着这些线索,把当时的场景重新拼出来。
✨ 三个核心卖点
| 🧠 仿生记忆 不存原文,只存 Token 线索 | 落盘的只有 keywords、seed_tokens、一句摘要——没有 message 正文,没有向量,没有 embedding。回忆时用线索组装一段"想起"的提示交给模型,而不是把数据摊开给它看。 |
| ⚡ 零 Token 成本 正则蒸馏,不调模型 | 遗忘不经过任何 LLM:引号里的原话、文件路径、标识符、中文短语,用规则抽出来就完事。一次折叠 0 次模型调用、0 token 计费、0 延迟。 |
| ✂️ 真正释放上下文 物理切除,不是"请忽略" | 走 DSH 的 surface 节点替换,被切掉的 6 个节点直接从下一轮请求里消失。不是 system prompt 里那句"请忽略更早的内容",是真的不发了——KV cache / 显存跟着一起降。 |
🔁 它是怎么运转的
agent/pre-step
│
▼
tokenMeter.measure(session)
│
< 8000 ───┴─── ≥ 8000
│ │
keep going ▼
keep the last 4 turns, fold the oldest 2
│
▼
regex distill → keywords + seed_tokens (0 LLM calls)
│
┌─────────┴─────────┐
▼ ▼
SQLite memory_clues surface replace
(only file on disk) 6 nodes → 1 checkpoint
│ │
│ ▼
│ 【🧠 记忆已折叠】…
▼ │
recall_from_clues ◄───────┘
rebuilds the scene
🆚 和别的做法比
| 方案 | 每轮额外成本 | 上下文占用 | 旧事怎么找回来 | 真的移除了吗 |
|---|---|---|---|---|
| 全量保留 | 0 | 线性增长,迟早爆窗 | 原文还在 | ❌ |
| LLM 摘要压缩 | 每次压缩 1 次模型调用 | 压缩后仍占位 | 读那段摘要 | ✅(摘要节点) |
| 向量 RAG | 嵌入调用 + 每次检索 | 检索片段临时塞入 | 召回原文分块 | ❌ 原文还在库里 |
| Full-Life Memory | 0 | 只留一行提示 | 线索 → 模型自己重建 | ✅(surface 替换) |
一句话区别:向量库是「查」,线索是「想」。 检索把原文递给你,回忆让模型自己长出来——后者更像人,也更省。
🚀 安装
plugin_manager { action: "install_bundle", target: "<本目录的绝对路径>" }
装完重启一次 DSH。之后每个新会话都会带上:
recall_from_clues工具(Phase 1)- 自动遗忘钩子(Phase 2,默认开启)
兼容性:
@deepseek-ai/dsh-agent/dsh-tools声明为>=0.2.1-rc.2 <0.3.0。 DSH 会把这两个 peer 依赖当作兼容版本要求;钉死小版本会在 DSH 升级后报“不兼容”, 所以这里用区间,0.2.x 的补丁与 RC 升级都能直接通过。
本插件是 link 安装:改完代码不用重装,但要重启 Host 才会重新加载模块。
⚙️ 配置
- insert:
- id: full-life-memory
name: '@tuhengyu/dsh-full-life-memory'
config:
dbPath: '~/.dsh/full-life-memory/memory.db' # 线索库
limit: 5 # 一次回忆取回几条线索
maxPromptChars: 1800 # 回忆提示的字符上限
autoForget: true # Phase 2 总开关
forgetThresholdTokens: 8000 # 超过多少 token 开始遗忘
forgetTurns: 2 # 每次切掉几轮已完成对话
keepCompletedTurns: 4 # 最近这么多轮永远保留
sceneOverrides: {} # 按场景覆盖,见下一节
| 字段 | 默认 | 说明 |
|---|---|---|
dbPath | ~/.dsh/full-life-memory/memory.db | 目录自动创建,WAL 模式 |
limit | 5 | 单次回忆最多带几条线索(上限 20) |
maxPromptChars | 1800 | 超出截断并标注"更早的线索已淡忘" |
autoForget | true | 关掉后只剩手动回忆,历史不再被切 |
forgetThresholdTokens | 8000 | 用 ctx.tokenMeter 的口径;没有该服务时按字符粗估 |
forgetTurns | 2 | 一次折叠几轮 |
keepCompletedTurns | 4 | 保护最近对话:已完成轮数 ≥ forgetTurns + keepCompletedTurns 才动手 |
sceneOverrides | {} | 按场景覆盖,键是会话 header.agentPreset |
🌐 多场景覆盖
桌面端、QQ 群、QQ 私聊、微信……同一个模型在不同入口的"记忆压力"完全不同,所以除了全局默认,还可以按场景覆盖:
autoForget: true
forgetThresholdTokens: 8000 # 全局默认(桌面端)
sceneOverrides:
qqbot:
enabled: true
forgetThresholdTokens: 4000 # 群聊碎片多,早点折叠
keepCompletedTurns: 1 # 只保最近 1 轮
wechat:
enabled: false # 微信交给别的插件,本插件直接让路
场景标识 = 会话的 header.agentPreset(小写)。DSH 的 SessionHeader 没有单独的"渠道"字段,QQ/微信这类接入插件通常用自己的 agent 预设建会话,所以拿它当场景键最稳;没有预设时退回 header.origin,再退回 default。桌面端的默认预设通常是 standard,想给桌面单独一套规则就写 standard:。
生效规则:
- 场景里没写的字段继承全局
enabled: false直接跳过折叠(连日志都不写)- 场景可以反向打开全局关闭的折叠——全局
autoForget: false只影响没有场景覆盖的会话
设置页的「场景」标签页可以增删改这些覆盖,不用手写 YAML。
🛠 设置界面
两个入口,同一张表单:
- Settings → 终身记忆(独立设置页)
- Plugins → 已安装插件 → Full-Life Memory(插件详情页的配置区,DSH 的插件配置约定位置)
选项用大白话,不再出现「阈值 / 折叠轮数」这类实现词:
| 控件 | 含义 |
|---|---|
| 遗忘触发条件 | 对话超过多少 token 时开始遗忘(调大记得更久) |
| 遗忘会话轮数 | 一次遗忘几轮已经聊完的对话 |
| 最近记忆保留轮数 | 最近这么多轮永远保留,方便接着聊 |
| 自动遗忘 | 总开关 |
分为两个标签页:
- 全局默认:上面那四项
- 场景:每个场景一张卡片(启用开关 + 三个数值 + 删除),底部输入场景标识即可新增
下方实时算出「什么时候会触发」:例如 至少要有 6 轮聊完的对话(遗忘 2 轮 + 保留 4 轮)才会开始,改数字时同步刷新。
🌍 多语言
表单右上角有语言下拉框,选项来自 DSH 的语言服务(不是插件自己写死的列表),切换时调用 ctx.locale.setLocale(id)——也就是跟随全局偏好,插件不维护自己的语言状态。
只带 zh / en 两套文案,加语言只要两步:
// client.js
const DICTS = { zh, en, ja } // ① 补一份字典
// locale/ja.json(插件管理器里显示的名字)
{ "meta": { "title": "フルライフ記憶", "description": "…" } }
不需要改 UI:下拉框会自己出现 ja(前提是宿主语言列表里有它,可用 ctx.locale.addLanguage({ id, label, fallback }) 注册)。缺键会走宿主的 fallback 链,不会显示 undefined——client-smoke 里有一条断言专门检查所有字典键集合一致。
🔄 检查更新 / 一键更新
表单上方有一行版本信息:v0.3.5 [检查更新],最右侧是语言下拉框。
| 状态 | 表现 |
|---|---|
| 点「检查更新」 | 请求 registry.npmjs.org/<包名>/latest,与运行中的版本比对 |
| 有新版本 | 显示 → v0.3.2 和「立即更新」 |
| 已是最新 | 顶部轻提示「当前已是最新版本」,2.6 秒后自动消失 |
| 点「立即更新」 | 调用 DSH 的 pluginManager.installBundle,由 Host 执行 pnpm 下载并校验 |
| 更新完成 | 弹出模态框:「更新已下载,请重启 DSH 以应用新版本」 |
为什么不自己 child_process 跑 pnpm:pluginManager 已经处理了 profile 写入锁、依赖脚本审批、peer 兼容性检查、失败回滚、registry 回退和「是否需要重启」的判定。自己 spawn 会绕过这些,还可能把 profile 的 package.json 写坏。
版本探测优先走浏览器直连 registry;如果部署的 CSP/CORS 拦住了跨域请求,会退回 Host 的
pluginManager.inspect再查一次。
这些字段标了 .volatile():保存后下一轮就生效,不用重启 Host;已经折叠掉的历史不会被恢复。
👀 你会看到什么
对话流里多一行系统提示:
【🧠 记忆已折叠】为了腾出空间,我已淡忘更早的 2 轮对话,
留下的线索:E:/Fishwork/app.ts、recall_from_clues、node:sqlite。
需要时我会用 recall_from_clues 自己回忆。
日志里多一行账:
full-life-memory: folded 2 turn(s) (~9123 tokens) from surface seq 2..9
as compaction 6f1c…; clues: E:/Fishwork/app.ts, recall_from_clues, node:sqlite
然后你随口问一句「我们之前那个 sqlite 的事呢?」——模型会自己调 recall_from_clues,把线索接成回忆。
🧩 工作原理(30 秒版)
- 测压:
agent/pre-step(waterfall)里用ctx.tokenMeter.measure(session)读当前请求压力。 - 选段:只挑已完成轮次(
turn/end之后),天然避开半个 tool-call;surface第 0 个 system 节点永不参与。 - 蒸馏:正则抽
keywords与seed_tokens,连同session_id写进memory_clues表。 - 切除:
session.append('user/message', checkpoint, { surfaceOp: { op:'replace', startSeq, endSeq } })——和 DSH 自己的dsh-compaction-basic逐字同一条路径。 - 留痕:外面套
compaction/start → summary → replace → end,Chat 因此把它渲染成原生的「记忆折叠」行,而不是一条用户消息。
安全边界都是显式校验过的:区间必须工具配对平衡、没有未闭合的 compaction/start、同一轮最多切一次、最近轮次受保护。任何一步失败都只是"这次不遗忘",绝不留半截事务。
❓ 常见问题
忘掉的东西还能找回来吗? 能找回"上下文",找不回"原文"——这正是设计目标。线索足够模型重建当时在聊什么、做到哪一步;逐字复述不重要。
真的不花 token 吗? 遗忘过程 0 token(纯正则)。只有你主动回忆时才会花几百 token——这笔钱花在真正需要的那一刻,而不是每一轮都替全部历史付费。
中文能匹配吗?
能。线索库用 LIKE 模糊匹配而不用 FTS5:中文不分词,线索本来就短,LIKE 反而更稳;% 和 _ 已转义。
会不会和 DSH 自带的自动压缩打架?
不会。检测到未闭合的 compaction/start 就跳过;自己开事务时严格成对闭合,不会把锁留给别人。
会不会把系统提示或工具配对切坏?
不会。system 头永不切;只切 turn/end 之后的范围;区间前后都做 tool-call/result 平衡校验。
数据在哪?会不会上传?
只有一个本地 SQLite 文件,默认 ~/.dsh/full-life-memory/memory.db。没有网络请求,没有遥测。
能只用手动回忆吗?
autoForget: false。历史不再被切,recall_from_clues 照常可用。
🗂 项目结构
dsh-full-life-memory/
├── package.json # bundle 清单 + dsh.bundle.patch + dsh.client
├── cordis.patch.yml # 插入 Host 插件行 + 默认 config
├── index.ts # Host 源码(类型完整,改这里)
├── index.js # Host 运行时 = index.ts 编译产物
├── client.js # 浏览器半边:Plugins 页里的设置界面
├── locale/{en,zh}.json # 插件管理器里的标题与描述
├── icon.svg
└── test/
├── smoke.mjs # Phase 1:命中 / 未命中 / 截断 / 转义 / 连接回收
├── auto-forget.mjs # Phase 2:阈值 / 切除区间 / 配对校验 / 落库 / 每轮一次
└── client-smoke.mjs # 设置页:用假 slots/configForms 跑一遍渲染
🧪 测试
node test/smoke.mjs
node test/auto-forget.mjs
node test/client-smoke.mjs
auto-forget.mjs 用一个仿 Session 契约的假会话跑完整链路,并额外做一次 index.ts 与 index.js 的行为对拍:同一个场景分别执行,逐字段比较追加事件、被切的 surface 区间、落库的线索行,确保"编译产物"和源码不漂移。
覆盖:阈值以下不动、到阈值切最旧两轮、replace 区间精确、system 头保留、后续轮次完好、线索落库、同轮不重复切、最近轮次受保护、工具配对不平衡拒绝、未闭合 compaction 拒绝、落库失败不留半截事务。
🧭 设计取舍
规则蒸馏不如语义摘要细腻——它抓的是"名字、路径、术语、原话",不是"结论"。这是刻意的:遗忘不该再花钱,也不该再等一次模型响应。真想要语义摘要,只需替换 extractClue() 一个函数,返回结构不变,其余逻辑(选段、切除、落库)完全复用。
🛣 Roadmap
-
extractClue的可选 LLM 后端(默认仍是正则) - 遗忘曲线权重:越久远的线索越淡,命中时排在后面
- 按项目 / workspace 分区的线索库
-
/forget手动命令与「回忆命中率」统计 - 线索之间的弱关联图,支持"顺着一条线索想起另一条"
📄 License
MIT © tuhengyu · 完整文本见 LICENSE
Ähnliche Plugins
hindsight (coding-agents)
vectorize-io/hindsight
OpenViking (dsh-memory-plugin)
volcengine/openviking
MemOS (memos-local-plugin)
memtensor/memos
ReMe (dsh)
agentscope-ai/reme