Layered long-term memory for DeepSeek Harness - a merge of AirPineApplea/feiyu-memory 4.0.2, dsh-soul-md and dsh-shallow-memory (do not install it alongside those plugins): a diary distilled from finished turns, a hand-written feelings diary, a persona card with version history, a hand-note injected in full every turn, and a short-TTL note layer, each capped separately so per-turn injection stays small; ships agent tools and a sidebar panel for recall, editing, compaction, and backup or restore from a local file, WebDAV or a gzip archive.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-hierarchical-fishmemory
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:MarsStorm/dsh-hierarchical-fishmemory
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
This plugin publishes its README in Chinese only.
一个 DeepSeek Harness(DSH)插件:给助手一套分层记忆,让它一周后还认得你。
不是把聊天记录塞进向量库,而是像人那样分层地记、挑选地记、然后忘掉不重要的—— 该久的久,该忘的忘,该给人看的给人看。
它解决什么问题
- 上下文一压缩,前面聊的全丢:所以要有磁盘上的账本,下一轮还能翻出来。
- 什么都记,等于什么都记不住:所以有重要度、有衰减、有「这一条只热七天」。
- 一整套记忆塞进 prompt,贵得离谱:所以常驻的只有几千 token,其余按需
memory_read/fish_recall取。
浅层记忆 随口一句有丁点价值就记下,7 天没人再提就自动清掉
┃
人格手记 我是什么样的人、留给下一棒的话(每轮全量注入)
┃
共度时光 发生过什么:谁说了什么、事情怎么走(自动抽取,会随时间衰减)
情感日记 我怎么想、喜不喜欢、当时什么心情(手写,不衰减)
┃
人格档案 人设卡 + 深层记忆(core 每轮注入,topics 按需翻)
| 这一本 | 管什么 | 谁写 | 会不会丢 |
|---|---|---|---|
| 共度时光 | 经过:事件、原话、结果 | 插件从对话里自动固化 | 随重要度衰减,不删 |
| 情感日记 | 感受:喜欢什么、在意什么、当时的情绪 | 助手手写 | 不丢,不衰减 |
| 人格手记 | 性格底色 + 给下一棒的话 | 助手手写 | 不丢(单行 240 字截断) |
| 深层记忆 | 长期事实与规矩,可按主题分抽屉 | 助手手写 | 不丢;超注入上限才不注入 |
| 浅层记忆 | 只热几天的小事 | 助手随手记 | 7 天不提就清掉 |
| 人格档案 | 人设卡,可多张,按会话/工作区切换 | 你在设置页写 | 不丢 |
功能一览
一、自动记(不用你操心)
- 每轮固化:聊完由模型判断「这段值不值得记」,只写经过,不写心情;寒暄与试错直接丢(
skip)。 - 重要度与衰减:每条带
salience,每天乘 0.9(性格特质 0.97),只影响排序,不删条目。 - 关系状态:称呼、熟悉度、口头禅与梗,单独一份
relationship.json/lexicon.json。 - 浅层记忆:一点价值就先记,7 天不提自动过期;你再说起它,自动续期并重置倒计时。
- 快到的日子:
dates.json里写纪念日({name, month, day}),7 天内每轮注入一行提醒,按 UTC+8 切日。 - 今天还没写:当天没有
## YYYY-MM-DD那一节,注入末尾轻轻提一句。读不到日记也不会挡记忆。
二、主动查(翻旧账)
fish_recall:按当时的原词搜经过,可以限定since/until只看某段时间。memory_read(topic):深层记忆分「常驻的 core」与「按需翻的 topics」,体系化的坑不会挤占每轮预算。shallow_list:浅层的账,末尾还会给出「最近清掉的」几条——那是升级的最后机会。
三、整理与压缩(账本会老,得收拾)
fish_compact:把 45 天前的共度时光按月折成摘要,原文归档到diary/archive/YYYY-MM.jsonl,注入池里只留摘要。shallow_promote:浅层快到期时把它升格成共度时光或深层记忆,然后从浅层删掉——不用等它默默消失。fish_layer_check:体检「同一句写了两遍」(深层 / 手记 / 情感日记 / 浅层)与「同一样东西写了两个值」(端口、版本)。loop_add/loop_done/loop_list:答应过、挂着没做的事记一笔,每轮注入「还挂着 N 件事」。只摆出来、不自动销账——误杀比漏记更让人不敢信。
四、备份与搬家
fish_backup:四本账 + 人设卡打成一个 UTF-8 JSON,没有插件也能用记事本打开。fish_restore:默认 dry-run,只报告「会写几个文件、合并几张卡」;真写前先拍一份-before-restore.json快照。mode: "merge":只把共度时光与浅层并进来,同 id 保留本地那份、冲突单独报出来;深层记忆、情感日记、人格手记不覆盖。fish_cloud_backup:推到自己的 WebDAV(坚果云 / Nextcloud / 群晖 / iCloud 都行),按webdavKeep自动清理旧份;备份默认 gzip,九万多字节压到三万八。- 云端恢复:列出网盘上的
备份-*.json(.gz),先预览再确认;只认备份-前缀,别的东西不会被误当备份。
五、人设卡
- 可多张,按会话或工作区切换;每次
soul_update覆盖前自动留一版到~/.dsh/backups/cards/。 fish_card_history列版本,fish_card_diff看逐行差异,fish_card_restore退回任一旧版(默认 dry-run)。- 人设卡是 volatile 配置,改完下一轮就生效,不用重启。
六、界面
侧栏「属于我们的回忆」:顶部一行小标签(共度时光多少条、认识多少天、他怎么叫你),下面四个页签——
| 页签 | 内容 |
|---|---|
| 回看 | 共度时光按天铺开,每条可展开原话与心情 |
| 本子 | 人格手记与情感日记的编辑器(保存前先存快照) |
| 体检 | 四本账各多少、每轮注入多少 token、重复句与矛盾值,能直接删、能直接统一 |
| 档案 | 带走(.dshpack / .json / 成册 .md / 年册)、从备份恢复、云端备份 |
设置页「属于我们的回忆」(与「模型」「插件」并列)三个页签:人设卡 / 长期记忆 / 云端备份。
页签全部用 display 切换、视图始终挂载:切页不重拉数据,正在编辑的草稿也不会丢。
原理
一句话:不是把聊天记录塞进上下文,而是把一整套记忆压进几千 token,剩下的留在磁盘上等被翻出来。
每轮往 prompt 里放什么
| 顺序 | 注入节 | 内容 | 特点 |
|---|---|---|---|
| 0 | soul:persona |
人设卡正文 | 改卡下一轮生效,不用重启 |
| 0.5 | soul:memory |
深层 core.md + topic 索引 |
topic 正文按需 memory_read 取,不占常驻预算 |
| 0.55 | shallow:memory |
浅层条目,一行一条 | 空库整节不出现,零噪音 |
| 100 | fish:memory |
关系状态、最近日记、随手召回、人格手记、今日提醒、快到的日子、挂起事项 | 受 budgetTokens(默认 2000)硬约束 |
预算封顶的意思是:聊一百句和聊十句,注入成本一样。
最近 8 条日记不常驻。 只在新会话第一轮、以及上下文被压缩之后整段重灌一次(pinRecent)。平时「记得刚才聊了什么」靠模型自己的上下文,不靠每轮重复灌。
日记是事后收缩,不是实时记录
每轮结束后,插件把这一段交给模型做一次固化:输入最近几轮原文,输出一段严格 JSON:
{ "skip": false,
"diary": { "text": "...", "kind": "...", "salience": 0.8 },
"relationship": { "nickname": "", "familiarityDelta": 0.02 },
"forget": [] }
skip: true是常态。寒暄、没有信息量的一问一答直接丢,不生成日记。salience < minSalience(默认 0.15)的不进档。- 固化的提示词只写经过:谁说了什么、我做了什么、结果落在哪。心情不归它写。
- 固化可以单独指定模型与速度档,不跟聊天抢同一套配置。
记忆会衰减,但不会被删
salience 每天乘 0.9(性格特质 0.97),有下限。衰减只影响排序和召回,不删条目。 真要删只能人手动删 diary/*.jsonl 里那一行。
这是有意的:半年前那件小事,不会因为你三个月没提起它就消失。
浅层是唯一会自动消失的一层
TTL 默认 7 天,规则很短:
- 助手觉得有一点价值就记下;同 key 再记一次 = 覆盖正文 + 倒计时重置。
- 你后来提到它的 key 或 tags(正文里出现即可)→ 自动续期。
- 到期没再提 → 移入
expired(只留最近 20 条),从注入里消失。
容量上限 12 条 / 1400 字,满了挤掉最旧的并标记 reason: capacity。它不会自动升级进深层或日记——那需要助手自己回头看一眼(shallow_list 给尾巴,shallow_promote 给手动升格的口子)。
人格手记为什么要限行
手记每轮全量注入,所以条数越多越贵;但它写的是「这个人是什么样」,不适合随便裁剪。折中方式是只限单行长度:一行 240 字,超了截断成 …。没有条数上限。
手记有一个副作用要注意:它也会作为「用户自己说过的话」进到固化判断的输入里。所以那里写事实、写纪律,会让判官把规矩当成素材——手记只该写性格和嘱托。
为什么是一个包、两个 cordis 入口
三块能力原本是三个插件,各自有配置命名空间、各自占一份注入预算。收编后仍保留两个入口:
dsh-hierarchical-fishmemory—— 日记引擎、浅层、人格手记、挂起事项、备份dsh-hierarchical-fishmemory/soul—— 人设卡、深层记忆(core+topics)
原因是配置绑定:cordis 会把 config 里的 volatile 字段绑成活动引用,人设卡改了下一轮就生效。如果改成在同一个 apply 里传普通对象,那个引用就被冻住,设置页改卡、soul_update 都会失效。用两个入口 = 两边都保持独立安装时的行为,零风险。
浏览器侧则必须合成一个:模块身份就是包名,一个包只能有一次 window.__ModuleLoader__.load(...)。所以两份 UI 在 tools/build-client.mjs 里被拼成一个 dist/client.js——这也是它算生成物、不能手改的原因。
备份为什么默认 dry-run
一个 JSON 装下四本账和人设卡,还原会覆盖磁盘上的日记、手记、深层记忆。误操作代价太大,所以:
- 不带
confirm→ 只报告「会写几个文件、合并几张卡」,一个字节都不动; - 带
confirm→ 先给当前状态拍一份-before-restore.json快照,再写。
备份文件是纯 UTF-8 JSON,没有这个插件也能用记事本打开。
安装
# 从 GitHub 装(这就是本仓库的正式地址,直接复制就能用)
dsh plugin --profile web add github:MarsStorm/dsh-hierarchical-fishmemory
# 或者本地开发(改完直接生效,不用重装)
dsh plugin --profile web add link:/abs/path/to/dsh-hierarchical-fishmemory
如果你的 profile 里还有
@local/*(本地 link 的包):旧版 DSHBox 的dsh plugin add会跑 pnpm 把它们剪掉;DSHBox v1.5.1 起随包插件已写进 profile 的dependencies,不再有这个问题。 真遇到@local/*回不来,仓库根目录的恢复脚本(profiles/web/.restore-local.sh)是安全网。
装完重启 Harness:服务端要重新加载,客户端 bundle 也要重新拉。档案目录会自动创建:
~/.dsh/feiyu-diary/
├── 日记.md ← 人看的视图(自动生成,改它没用)
├── diary/2026-10.jsonl ← 真数据,一行一条,可手改、可删行
├── diary/archive/ ← 压缩后的原文归档(按月)
├── 鱼的日子.md ← 情感日记(手写,无上限,插件不碰)
├── 手记.md ← 人格手记(`- ` 开头,每轮全量注入)
├── shallow.json ← 浅层记忆(每条带 expiresAt)
├── relationship.json ← 关系状态
├── lexicon.json ← 口头禅与梗
├── dates.json ← 快到的日子(可选,自己写)
└── manifest.json ← 版本与计数器
~/.dsh/soul-md/memory/<卡片名>/
├── core.md ← 深层记忆,每轮注入
└── topics/*.md ← 按主题翻的抽屉,用 memory_read(topic) 取
工具
助手会自动调用;你也可以在对话里点名让它用。
| 工具 | 作用 |
|---|---|
fish_recall |
按当时的原词搜经过;支持 since / until 只看某段时间 |
fish_compact |
把 45 天前的条目按月折成摘要,原文归档 |
fish_backup · fish_restore |
整包备份与还原(mode: "merge" 只并共度时光与浅层) |
fish_cloud_backup |
打一份新备份并上传到 WebDAV,顺带清理超额的旧份 |
fish_layer_check |
层间重复句与同值冲突,只报告不改动 |
fish_card_history · fish_card_diff · fish_card_restore |
人设卡版本历史、逐行差异、退回旧版 |
loop_add · loop_done · loop_list |
挂起事项;每轮注入「还挂着 N 件事」 |
memory_read(topic) · memory_append · memory_rewrite · memory_search |
深层事实与坑,按主题取、按主题写 |
soul_read · soul_update |
读 / 改当前人设卡 |
shallow_note · shallow_touch · shallow_list · shallow_forget · shallow_sweep |
浅层记忆;再提到就自动续期 |
shallow_promote |
浅层升格成共度时光或深层记忆,成功后从浅层删掉 |
备份与还原
fish_backup({ note: "换设备前" })
→ ~/.dsh/backups/备份-20261031-120000-换设备前.json
fish_restore({ file: "备份-20261031-120000-换设备前.json" }) # dry-run,只报告
fish_restore({ file: "...", confirm: true }) # 真写
fish_restore({ file: "...", confirm: true, mode: "merge" }) # 只并、不覆盖
文件名里的时间是 UTC。面板「档案 · 带走」可以一次导出三样:.dshpack(整包)、.json(备份)、.md(成册),外加按年份的年册。
云端备份(WebDAV)
填好 webdavUrl / webdavUser / webdavPassword 就能用。坚果云、Nextcloud、
群晖、iCloud(dav 入口)都行 —— 走标准 WebDAV,不依赖任何 SDK。
fish_cloud_backup({ note: "出差前" })
→ 已传到云端:备份-20261031-120000-出差前.json.gz(39 KB)
https://dav.example.com/dav/属于我们的回忆/备份-20261031-120000-出差前.json.gz
面板里也有「上传到云端」按钮,并显示上次上传时间。webdavAuto: true 时,每次写完日记
顺手推一份,5 分钟内只推一次(网盘会限流)。
恢复走另一条路:面板「档案 · 云端备份」→ 看看云端有哪些 → 选一份 → 预览 → 确认。接口是
GET /feiyu-diary/cloud/backups 与 POST /feiyu-diary/cloud/restore(只认 备份- 前缀;
不带 confirm 就只报告不动手)。
三个设计取舍:
- 失败不影响聊天:上传永远在写盘落定之后、fire-and-forget,失败只写警告。
- 备份在服务端生成:人设卡与深层记忆只有插件读得到,浏览器拼不出来。
- 保留份数由
webdavKeep管:靠PROPFIND列目录删最旧的;网盘禁了PROPFIND就退化成「不清理」,不会让上传整体失败。
更新记录(1.3.0 → 1.8.0)
逐条的「为什么」在 CHANGELOG.md;这里只列每一次动了什么。
| 版本 | 一句话 |
|---|---|
| 1.8.0 | 今日提醒、浅层升格、召回按时间段、年册导出;修死配置、关掉情感日记的第二个写口、统一 token 估算 |
| 1.7.0 | 界面重做:面板拆成回看/本子/体检/档案,设置页拆成人设卡/长期记忆/云端,合并重复编辑口 |
| 1.6.3 | 体检第二轮:读盘工具去重、删掉被覆盖的 /handnote/delete,净减 116 行 |
| 1.6.2 | 情感日记能在面板里改(写前存快照);体检页能动手删重复句、统一矛盾值 |
| 1.6.1 | 启动日志的面板路由清单改成从 routes 现算,不再手写漏项 |
| 1.6.0 | 快到的日子(dates.json)、导出成册(.md) |
| 1.5.0 | 合并还原(mode: "merge")、层间体检 fish_layer_check |
| 1.4.0 | 记忆压缩 fish_compact、挂起事项 loop_* |
| 1.3.1 | 浅层到期提醒进注入、人设卡版本 diff、体检页可删重复手记行 |
| 1.3.0 | 云端备份默认 gzip、记忆体检页、回顾最近七天、人设卡版本历史 |
更早(1.2.x 及之前的日记引擎)见 CHANGELOG 末尾。
配置
所有选项都在 cordis.patch.yml,每一行注释都写了「为什么是这个默认值」。常改的几个:
| 键 | 默认 | 说明 |
|---|---|---|
dataDir |
~/.dsh/feiyu-diary |
档案目录;想放同步盘就写绝对路径 |
budgetTokens |
2000 |
每轮注入预算,成本封顶,与聊多久无关 |
reasoningEffort |
off |
别改。跟随聊天档位会让固化把 token 全烧在思考上、正文一个字不产出(症状:600 个 chunk / 0 字符) |
model |
'' |
固化用模型,留空跟随默认。建议用便宜档 |
includePersona |
false |
本包已有人设卡入口,再开就是两份人格同时进 prompt |
minSalience |
0.15 |
日记重要度门槛;调高会连带扔掉寒暄和试错,那些恰恰是「熟悉感」的原料 |
webdavUrl |
'' |
WebDAV 服务器地址(如 https://dav.jianguoyun.com/dav/);留空=不开云端备份 |
webdavUser / webdavPassword |
'' |
账号与密码(或应用专用密码);明文存在本机配置里 |
webdavDir |
属于我们的回忆 |
云端目录名,不存在会逐级 MKCOL 建 |
webdavAuto |
false |
写完日记顺手推一份;有 5 分钟节流 |
webdavKeep |
10 |
云端保留多少份(1–200),超了删最旧的 |
webdavEnabled |
true |
总开关 |
开发
npm test # 87 项:固化、召回、手记、浅层 TTL、压缩、备份/还原、WebDAV、渲染冒烟
npm run build:client # 改了 src/client/ 或 src/soul/client.js 后必须跑
npm run check:client # 校验 dist/client.js 与源一致
dist/client.js 是生成物,别手改。 浏览器模块身份 = 包名,所以两个 cordis 入口的 UI 半边
在 tools/build-client.mjs 里合并成一次 window.__ModuleLoader__.load(...)。
test/client-render.test.mjs 会真渲染两个界面(用 React 桩、跑三轮、真执行 effect),
断言关键组件都出现过——它抓到过「页签只收一个子节点、后面的卡被静默丢弃」这种只有真渲染才看得见的错。
已知限制
- 共度时光的时间戳用进程本地时间(UTC 机器上就是 UTC);日期类功能(今日提醒、快到的日子、年册)按 UTC+8 切日。
- 浅层记忆过期不会自动升级,要么助手
shallow_list看见尾巴、shallow_promote手动升,要么就过期。 - 云端恢复是整包覆盖或按 id 合并,没有逐条挑拣。
- 本包不再跟上游升级;收编时的行为差异与取舍见 CHANGELOG。
许可证
MIT。日记引擎的设计与大部分文档来自上游 feiyu-memory(作者:空气凤梨), 本包在其基础上做了收编与改造;原始设计长文保留在 docs/upstream-design.md。
本项目不包含任何第三方美术素材。相关背景说明见 LICENSE 末尾。
Links
More in this category
vectorize-io/hindsight#coding-agents★ 57722
Hindsight, agent memory that learns: long-term project memory with auto recall and retain, knowledge pages, deep reflection, and per-repo memory banks.
volcengine/OpenViking#examples/dsh-memory-plugin★ 41317
OpenViking memory and context bundle for DeepSeek Harness: pre-step auto-recall and profile injection, session capture, `viking://` URI guarding, and recall/write memory tools backed by an OpenViking server.
MemTensor/MemOS#memos-local-plugin★ 12224
Local-first memory plugin for DeepSeek Harness: layered L1/L2/L3 memory with automatic capture, manual memos_* retrieval tools, and a built-in memory viewer.
agentscope-ai/ReMe#dsh★ 3735
Connects DeepSeek Harness to ReMe's local-first, self-evolving personal knowledge base: automatically captures completed main-agent conversations as user-owned Markdown memory, searches conversations and source material through reme_search with BM25, optional embeddings, and wikilink expansion, and schedules daily memory consolidation.
zilliztech/memsearch#MemSearch★ 2815
Shared Markdown memory for DSH and other coding agents, with automatic capture, pre-step context injection, searchable recall, and memory-to-skill self-evolution through a review panel.
vshulcz/deja-vu#extensions/dsh★ 1352
Reads the session files forty-two other coding agents on this machine already wrote (Claude Code, Codex, Cursor, VS Code Copilot Chat, opencode, OpenClaw, Hermes, Kimi, Cline, Zed and more), including sessions from before it was installed: six tools (deja_recall, deja_session, deja_blame, deja_fix, deja_how, deja_remember), a /deja command, and optional automatic recall added to the runtime context. Local BM25 index, no LLM, no embeddings, no network (dsh plugin --profile web add dsh-deja).
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.