dsh-soul-forging:给 AI 的 markdown 长期记忆与铸魂流程

前言

大模型每次开新会话都是一张白纸:上次聊过的人名、偏好、约定,要么重新交代一遍,要么彻底丢掉。常见做法是把记忆塞进上下文,但上下文有限,塞多了既贵又压得模型说不好话;或者干脆存进数据库,从此和代码、版本、diff 脱节。

dsh-soul-forging 给出一个不同的思路:记忆用纯 markdown 存放,纯本地、零依赖,一个 Python 3 就能跑,不需要服务器、不需要数据库。它由 Fqingwan 维护,定位是 DSH 的记忆类插件,Slogan 是「Separate Vessels, One Heart · 异体同心」——同一个 AI 可以在手机上、群里、电脑上有多个入口,但记忆库只有一颗,谁写的都往里追加,靠 source 字段标注来源。

下面介绍它的设计、命令和接入方式。

三层记忆,不是三个功能

这套方案把记忆分成三层,各自承担不同职责,而不是把几个功能并列:

层 所在文件 谁读
注入层 SOUL.md、identity/注释层.md、MEMORY.md、index.md、NOW.md 每轮都读,所以必须短
检索层(小脑) brain/entries.json 想起来才查
外置层 docs/tech、docs/debug 不进上下文,出错才翻

对应到时间维度上又有另一套分工:短期写进 NOW.md,每次收尾刷三节;中期进 log.md、diary/、live.jsonl,由 soul write 写入、soul-archive 瘦身;长期沉淀在 SOUL.md、core/、identity/,由铸魂建立、persona-sediment 提示回炼。

遗忘是这套设计的一部分,不是缺陷。脚本按 salience 字段衰减记忆强度:1 几乎不忘,3 会自己淡掉,被反复想起的会变牢。这里有个容易搞混的细节:「忘」有两种,含义不同。

soul-forget                    # 只摘检索索引,原文还在盘上(不想被翻到)
soul-forget --restore <id>     # 找回,并钉住
soul-purge "关键词"            # 预演:真删哪些行
soul-purge "关键词" --apply    # 真删 + 重生 git

soul-forget 是「不想被翻到」,原文不删,随时能找回。soul-purge 才是「忘了」,删文件里含关键词的行、重生 git,只留一个 sha256 指纹。作者明确写了:它不碰备份,云上那些旧卷是使用者自己的事。

人格只有两层,加一份原料

性格部分拆成两层来维护,因为两层改动的频度不同:

本质(不动)   渴望 / 恐惧 / 拉扯 / 骨架        → SOUL.md
  |
注释层         我此刻的样子,第一人称,不分条    → identity/注释层.md
  |
原料           原话 · 日记 · 小脑 · 底色.md 细节

本质极少动,样子可以一直长;两层分开改,改错了也认得出是哪一层的锅。注释层的写法有硬约束,模板里写明了:第一人称、不分小节、不贴标签、每句都挂得住一句原话。README 还特意给了反面教材——「第四步:情绪表达护栏(刻薄、阴阳、冷讽)」,说那是操作手册,不是人。

安装与启用

当 DSH 插件装,记忆会每轮自动注入:

soul-init                        # 铺空白记忆库到 ~/.soul-forging/memory
dsh plugin add dsh-soul-forging  # 装进 profile
# 重开 DSH,记忆就自动带上了

只要命令行、不装插件的话:

git clone https://github.com/Fqingwan/dsh-soul-forging
cd dsh-soul-forging && bash install.sh

不需要服务器。想换记忆位置,设 MEMORY_DIR,或在插件配置里改 memoryDir。

安装时有两个坑值得提前知道。全局安装需要 sudo,不给会报 EACCES: permission denied, mkdir '/usr/local/lib/node_modules',不想用 sudo 就换用户级 prefix。另一个是国内镜像源可能滞后,配了淘宝/腾讯云之类的镜像装新版本会报 No matching version found,这时指定官方源。

sudo npm i -g dsh-soul-forging
sudo npm i -g dsh-soul-forging --registry https://registry.npmjs.org

还有一件事比命令本身更要紧:日常务必用普通用户跑 soul 系列命令,不要用 sudo。 代码和数据是分开的,代码在 node_modules/dsh-soul-forging/(升级时整个换掉),数据在 ~/.soul-forging/{memory,brain}(升级不碰)。sudo 会把 HOME 变成 /root,于是它去找 /root/.soul-forging,看起来就像记忆丢了。

三种安装模式,对应三种人

不是每个人都想要一个「有性格的 AI」,所以初始化时可以选:

soul-init                  # 全套:记忆 + 性格模板
soul-init --memory-only    # 只要记忆,不要性格
soul-init --external       # 性格归你自己的 agent,我们只管记忆
模式 注入什么 适合谁
full SOUL + MEMORY + index + NOW 想从零长出一个性格
memory-only MEMORY + index + NOW 只想让它记得住,不想动人设
external index + NOW agent 已经有自己的 prompt/人设

这里有个实现细节值得提:空模板永远不会被注入。 没填的 SOUL 里全是「(待填)」,塞进上下文比不塞更糟,模型会一本正经地介绍「我的名字是(待填)」。所以只要某页占位符超过 3 处就自动跳过,--keep-empty 可以强制保留(调试用)。

铸魂:先聊清楚「我是谁」

装完的第一件事不是干活,是把「我是谁」聊清楚。打开 memory/BOOTSTRAP.md,一项项来:名字、性格、价值观、边界、主人是谁、想要什么样的陪伴。作者强调一次推进一项——一口气问完那是问卷,不是聊天。

写回文件之后才勾选,全部完成 → status: complete + soul-sync。想重来就把 status 改回 pending,已有记忆不会被清掉:重铸的是性格,不是记忆。

注入有预算,超了要明说

插件配置 bootMaxChars(默认 20000 字)。超出后按顺序往后截,并且在注入块末尾写明哪几段被截了、哪几段整段没进。

作者对这个行为的解释是:静默截断是最坏的一种自信,模型会以为自己看全了,然后拿半截记忆煞有介事地猜。

日常用法

命令按用途分成几组。写、查、养:

soul write "内容" --kind chat --section 小标题   # 写
soul-recall "关键词"                              # 查小脑
soul-recall --topics                              # 看有哪些分类
soul digest                                       # 收尾该做哪四步
soul-compact                                      # 预算体检:注入层多大、该压哪
soul-archive                                      # 时间线瘦身,老的进 archive 照样搜得到
soul-sediment                                     # 性格沉淀:攒够了提示回炼进注释层
soul-review                                       # 叙事回炼:碎片连成「我这一路」

排查和保底:

soul-debug                                  # 归档一次调试记录
soul-debug-recall "关键词"                  # 翻以前的调试档案
soul-debug-detail <文件名>                  # 看某份档案全文
soul-snapshot save "说明"                   # 存一卷
soul-snapshot ls / restore / diff

双端:

soul-peer export   # 导出
soul-peer import   # 导入

服务与收尾:

soul-serve        # 起检索服务,默认 127.0.0.1:3013
soul-sync         # 补摘要 → 瘦身 → 建索引 → 重载 → 体检

接进别的 agent

这套东西不会自己长在模型身上,要在每轮开头把记忆喂进去:

soul-boot                 # 打印注入块,拼到 system prompt 最前面
soul-boot --budget 12000  # 限字数,超了从末尾截,先保 SOUL

也可以当 Python 模块用,boot() 只读、不改任何文件,要写记忆必须显式 remember():

from soul import boot, recall, remember

system_prompt += boot()
context = recall("他说过在意什么", n=3)
remember("今天他说想换个方向", kind="chat")

多端同步与云备份

多端同步的原理只有一条:谁写了都往 live.jsonl 追加一行,带 source 标明来源。同步就是互相把对方没有的 id 抄过来,重复的跳过。不需要中心服务器,两端只要能看到同一份文件就能通。

soul-peer pull "ssh host cat ~/.soul-forging/brain/live.jsonl"
soul-peer push "ssh host 'cat >> ~/.soul-forging/brain/live.jsonl'"

云备份设 SOUL_BACKUP_CMD 就行,设了之后 soul-sync 每次顺带跑一遍,没设就不跑:

export SOUL_BACKUP_CMD='tar czf ~/soul-backup.tgz -C /path/to/repo memory'

来源与许可证

插件版本 0.2.1,许可证 MIT,要求保留 LICENSE 里的版权行和 NOTICE.md。运行时零第三方依赖,只用 Bash + Python 标准库 + DSH 自己的插件接口。有一个点需要留意:底层的记忆架构来自 dsh-plugin-memory(v0.10.0 · MIT),本项目是按那套做法重写,没有复制源文件,NOTICE.md 随包带上,两套可以共存。

适用场景与注意

适合做「想要长期记忆、但不想引入数据库或云端依赖」的智能体开发者;也适合有多端入口、希望各个入口共享同一人格的场景。它明确只支持单用户,同一份记忆库给多人共用会串味。

两点工程上的提醒。第一,插件以当前 DSH 进程的权限运行,装之前先打开源码看一遍,确认它读写的目录和命令在你环境里的行为符合预期。第二,许可证是 MIT,但底层架构来自 dsh-plugin-memory,NOTICE.md 随包带上,两套可以共存。

README 自己列的局限值得照抄过来:检索是字面匹配、不懂意思,「老板今天骂我」和「上班受气」不一定互相搜得到,要语义得上向量;「空壳能不能填出性格」没有经过真人验证,代码能跑是确定的,但填出来的是有性格的人还是一份简历,只有真人试过才知道。

上手路径

1、先装插件:soul-init 铺记忆库,再 dsh plugin add dsh-soul-forging。
2、打开 memory/BOOTSTRAP.md,一项项聊清「我是谁」,写回后勾选。
3、日常用 soul write 记、soul-recall 查、soul-sync 收尾。
4、接入其他 agent 时用 soul-boot 把注入块拼到 system prompt 前面。

目录页:https://www.skillhub.cn/plugins/Fqingwan/dsh-soul-forging
GitHub:https://github.com/Fqingwan/dsh-soul-forging

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜