Zum Hauptinhalt springen
T

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-memory

README

Full-Life Memory

Full-Life Memory

不存原文,只存线索。
像人一样遗忘,也像人一样回忆。

version license node zero dependencies DSH bundle XERJ Corpus Hub contributor

@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 Memory0只留一行提示线索 → 模型自己重建✅(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 模式
limit5单次回忆最多带几条线索(上限 20)
maxPromptChars1800超出截断并标注"更早的线索已淡忘"
autoForgettrue关掉后只剩手动回忆,历史不再被切
forgetThresholdTokens8000用 ctx.tokenMeter 的口径;没有该服务时按字符粗估
forgetTurns2一次折叠几轮
keepCompletedTurns4保护最近对话:已完成轮数 ≥ 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 秒版)

  1. 测压:agent/pre-step(waterfall)里用 ctx.tokenMeter.measure(session) 读当前请求压力。
  2. 选段:只挑已完成轮次(turn/end 之后),天然避开半个 tool-call;surface 第 0 个 system 节点永不参与。
  3. 蒸馏:正则抽 keywords 与 seed_tokens,连同 session_id 写进 memory_clues 表。
  4. 切除:session.append('user/message', checkpoint, { surfaceOp: { op:'replace', startSeq, endSeq } })——和 DSH 自己的 dsh-compaction-basic 逐字同一条路径。
  5. 留痕:外面套 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