人格接力 · dsh-identity-relay
零依赖 · 纯文本 · 281 项测试通过 · MIT
v0.4.0 · 换会话不是换鱼——满的是记性,不是她。
30 秒看懂
问题:上下文是易失的——满了会裁剪、清了会归零、压缩会改写。把「她是谁」只放在上下文里,等于把灵魂放在冰上。
做法:把灵魂搬到磁盘。人格卡、交接书、长期记忆是三个纯文本文件,每个新会话开工自动读档,上下文快满时提前提醒写交接。
结果:换会话、清上下文、压缩、重启 DSH——她还是她。记忆不在模型里,在你自己的文件夹里。
dsh plugin add github:wumingzhiren-lab/dsh-identity-relay#main
想看它是怎么被讲出来的:B 站《记忆的户口》(2 分钟,讲清楚「为什么记忆要上户口」)
为什么不会消失
| 磁盘文件 | 作用 | 对应「小鲸工作法」 |
|---|---|---|
persona.md |
人格卡:我是谁、性格、说话方式、红线 | 每次会话自动加载的 AGENTS.md |
handover.md |
交接书:上一世在做什么、接下来做什么、答应过什么 | 交接.md |
facts.jsonl |
长期记忆:一行一条 {time, content, source, status} |
works.json / 记忆库 |
每次新会话开工(新建、恢复、清上下文、压缩之后),插件自动把「人格卡 + 交接书 + 最近记忆」注入第一轮上下文——这就是「换会话不是换鱼」的通用化。
上下文快满时,插件会在下一步之前插入预警,提醒她先用 relay_handover 写交接书。就算下一秒上下文被清空,下一世读档回来:知道自己在做什么、答应过什么、还是那个性格。
会话进行中也一直盯着记忆库:别的会话(或主人亲手编辑)改了 persona.md / handover.md / facts.jsonl,下一步之前会自动插一句「档案更新,先 relay_load 重读」。她不会拿着一小时前的旧记忆干活——跨会话不是靠自觉,是靠它盯着。
写覆盖自带历史:relay_handover / relay_persona 每次覆盖前,旧版自动留进 history/ 目录(各保留最近 10 份)。写坏了、想回看上一版,直接去翻文件。
记忆的户口(v0.3 的核心)
每条记忆带来源(谁说的、怎么来的)和状态(active / superseded / archived):
- 来源五档:
owner主人说的 >self自己说过/答应的 >evidence有据观察 >tool工具台账 >inferred推断垫底;老数据一律unknown,不猜。 - 作废 = 盖章,不删行(Katsuno-Mendelzon 最小变更:只作废该作废的;Darwiche-Pearl:使用只看当前态,历史留档)。检索默认只出 active,被作废的要用
includeSuperseded才能看见。 - 同键(
subject)新记忆自动取代旧记忆:旧条标superseded、supersededBy指向新条——这是规则,不是模型猜。 - 关联:一条记忆可以挂
related(最多 5 个 id)。回忆命中它时,关联的记忆会顺手带出来(最多 3 条,标「关联」)——写的时候就把关系钉死,不靠检索时猜。 - 归档 = 退休:
status: archived的行不再进读档和检索,但行还在,永远翻得出来。收工整理脚本~/.dsh/identity-memory/tools/consolidate.mjs只归档老于 N 天的过程流水(tool/inferred/evidence 且无 subject),owner/self永不归档。
安装
方式一:GitHub 直装(推荐)
dsh plugin add github:wumingzhiren-lab/dsh-identity-relay#main
或 DSH 插件管理器 → 市场/安装 → 填 github:wumingzhiren-lab/dsh-identity-relay。
仓库地址:https://github.com/wumingzhiren-lab/dsh-identity-relay
方式二:本地目录安装
dsh plugin add <本目录绝对路径>
安装后无需重启(HMR 生效)。记忆库默认建在 ~/.dsh/identity-memory,三个文件会自动生成模板——注意「生成模板」不等于「配好了」,见下面的装完三步。
装完 60 秒(别跳过,跳过等于没装)
- 看一眼现状:让她调
relay_status。如果人格卡/交接书还是初始模板,它会直接标 ⚠️ 提醒你。 - 填人格卡:把「她是谁」写进
persona.md(或让她用relay_persona写)。模板是空的,不填的话下一世读到的是一张空白卡——插件不会替你编人格。 - 写第一份交接书:用
relay_handover写「我是谁、正在做什么、接下来做什么、答应过什么」。 - 验货:问她一件「只有她知道」的小事(比如你们上次聊到哪、你答应过什么)。答得上,说明读档链路通了;答不上,先
relay_load看人格卡是不是空的。
记忆不会自己长出来:插件负责读,不负责采。你不
relay_remember,它就永远是空的。
配置(可选,写在 cordis.patch.yml 的 config 里)
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关,设 false 整个插件不加载任何行为 |
memoryDir |
~/.dsh/identity-memory |
记忆库目录,支持 ~ 和绝对路径 |
injectOnCreated |
true |
新会话自动注入读档块 |
injectOnResume |
true |
恢复会话注入精简续接块 |
injectOnSubagents |
true |
子代理也注入人格 |
alwaysOnSection |
true |
系统提示常驻锚段落 |
contextWindow |
65536 |
容量预警窗口(tokens),仅在拿不到模型真实上报值时兜底 |
warnRatio |
0.7 |
占用达到该比例时提醒写交接。范围 0.5–0.99,越界会被静默夹紧(设 0.3 实际是 0.5) |
reserveTokens |
40000 |
窗口剩余低于该值也提醒(与比例无关的第二道保险) |
nudgeMinutes |
10 |
同一会话两次预警最小间隔 |
maxFacts |
2000 |
facts.jsonl 最多保留条数。有 100 条硬下限:设比 100 小的值会被抬到 100(防呆,避免配错把记忆裁光) |
recallLimit |
20 |
读档注入的最近记忆条数。范围 1–100,越界会被静默夹紧(设 200 实际是 100) |
pinSources |
["owner","self"] |
保底名额给哪些来源单独占位,不让最近的流水把它们挤出去。设成 [] 等于关掉保底 |
pinWindow |
300 |
保底名额只在最近 N 条 active 里挑,不会翻出几百条前的旧话 |
pinMax |
0 |
保底名额上限;0 = 自动取 ceil(recallLimit/3)(20 条时是 7 个) |
maxInjectChars |
6000 |
注入的人格卡最大字符数(超出会截断并提示用 relay_load 看全文) |
maxHandoverChars |
4000 |
注入的交接书最大字符数 |
injectSelect |
"relevance" |
读档挑哪些记忆。"relevance" = 拿交接书当话题,按「相关 × 新鲜 × 来源」挑 N 条(挑不出话题自动退回 recent);"recent" = 最近 N 条 + 保底名额(v0.4 旧行为,想退回去设这个值即可)。0.5 起默认 "relevance":真库比对同样 20 个名额,owner+self 覆盖 15/23 → 19/23 |
maxFactsChars |
8000 |
记忆段的字符预算(0.5)。单条上限 4000 字符 × 20 条最坏能到 8 万字符,小窗口直接吃掉半个上下文。超预算按挑选顺序从尾部丢,至少留 1 条,并在块尾写明「另 N 条超出预算,用 relay_recall 捞」;0 = 不设限 |
八个工具(bot 自己会用)
relay_load— 重读人格卡 + 交接书 + 最近记忆(失忆自救)relay_remember— 记一条长期记忆(身份、偏好、承诺、教训)。带source(owner/self/evidence/tool/inferred,默认 inferred 垫底);带subject键则同键新压旧,旧记忆自动盖章作废;带related可挂到已有记忆上(填 id 或内容片段,最多 5 个);带validFrom/validTo(ISO 日期)则到点后退出生效区、单独列进「已过期」;带importance(0–10,这事有多戳人)则读档时优先被挑中——不填就按来源和信号词自动估relay_recall— 按关键词回忆。最多 20 条(硬上限),排序先看来源可信度再按时间;命中条目的关联记忆会带出(最多 3 条);默认只出 active,includeSuperseded: true翻案看历史relay_retract— 把一条记忆盖章作废:按内容片段定位,标superseded并记原因。作废不删行,历史保留relay_conflicts— 列出可能互相矛盾的记忆对(纯规则:否定词差异 + 正反义词,不调模型),每对带稳定编号#xxxxxxxx和「建议动作」(如「建议盖旧的」,旧的是主人原话时一律建议人工判断)。默认只报告,不动数据;要执行得显式传pairId+which+apply:true(走 relay_retract 盖章、不删行),且主人原话不会从这个口子被盖。同subject的已被同键替换处理,不参与relay_handover— 读/写交接书relay_persona— 读/更新人格卡relay_status— 记忆库状态(active/已作废/已归档计数)+ 历史备份数 + 当前上下文压力
测试
全部测试全在临时目录跑,不碰真实记忆库:
node test/smoke.mjs # 12 项:记忆库层冒烟(建库/追加/去重/检索/统计)
node test/step4.mjs # 26 项:来源五档、作废盖章、同键替换、凭据拦截
node test/step5.mjs # 18 项:关联记忆、软归档、检索上限与来源优先、跨进程写锁
node test/step6.mjs # 13 项:读档注入保底名额(主人原话不被流水挤掉)
node test/step7.mjs # 25 项:记忆矛盾检测(否定差异 / 极性概念组 / 过期与未生效不参与)
node test/step8.mjs # 36 项:记忆有效期(validFrom/validTo 一起判、同键补结束时间、新值过期拒绝、只写日期按本地、非日期拒绝)
node test/step9.mjs # 44 项:注入安全(换行压平、伪造边界清洗、凭据补全、锁自愈)+ 增量注入
node test/step10.mjs # 45 项:pre-step 接线(指纹对比 / 基线更新 / 行渲染 / 提示文本 / 全链路不丢)
node test/step11.mjs # 26 项:来源动态信任(按人工判错率降权,上限一半)+ 年龄标记 + 时区排序
node test/step12.mjs # 26 项:检索升级(切词 + 双字组覆盖率,多词「与」匹配,子串回归对照)
node test/step13.mjs # 17 项:并发写压测(进程内 20 并发 / 跨进程 2x10 与 3x6,都不丢行)
node test/step14.mjs # 16 项:读档按相关性挑记忆(相关×新鲜×来源,保底名额不丢,空话题退回最近)
node test/step15.mjs # 14 项:接线层实测(真实 buildInjection 认不认 injectSelect,recent/relevance/resume 三路 + 调用点漏传回归)
node test/step16.mjs # 17 项:注入体积预算(字符预算截断、至少留 1 条、0=不设限、漏传走默认)
node test/step17.mjs # 21 项:记忆重要度(显式打分 / 启发式兜底 / 落盘 / 注入排序用它)
node test/step18.mjs # 21 项:冲突处理(稳定编号 / 建议动作 / 显式执行 / 主人原话不在此被盖)
node test/benchmark.mjs # 49 项:基准场景集 v1(A 同键 / B 作废与翻案 / C 来源 / D 凭据 / E 边界与并发)
node test/fuzz.mjs 200 # 随机压力:随机操作序列 + 极端输入,只测不变量(每轮 300+ 断言)
426/426 通过(2026-11-03 实测),外加 test/fuzz.mjs 随机压力测试三轮全过(332 / 338 / 343 项随机断言)。
fuzz 关注的不变量:每一行永远合法 JSON、无重复 id、同 subject 同时最多 1 条 active、行数不超硬上限、写入能原样读回、注入行不泄漏伪造的数据边界、指令识别对极端输入不抛异常。
它真的抓到过东西:
maxFacts有 100 条硬下限(Math.max(100, maxFacts)),传更小的值会被静默抬到 100——配置与行为不一致,属于防呆保护,已记为已知边界。2026-11-02 起 store 层新增跨进程写锁(facts.lock,与桌宠 relay_store.py 同协议):E08的进程内串行锁之外,跨进程写同一memoryDir会排队串行、不再互相覆盖(陈旧锁 30s 自动抢占;见 Q8)。
功能解答(FAQ)
Q0:它会自动把聊天内容记下来吗?
不会。 记忆只在有人(你或 bot)调用 relay_remember 时写入。插件负责「存、读、注入、维护」,不负责采集——它不会偷偷摘要对话,也不会自己去猜什么值得记。想让某件事留下,得明确说一句「记住这个」。
这个误解比「作废=删除」更常见:装完之后发现「她什么都不记得」,十有八九是根本没人写过。
Q1:这个插件到底解决什么? 别的记忆插件解决「记住」(存人格、存记忆、注入上下文)。人格接力解决「记住的东西还算不算数」:记忆会过期、会写错、会和别处撞车——它给每条记忆上户口(来源)、给作废盖钢印(状态),让 AI 拿出来的每一句话都知道自己凭什么信它。
Q2:为什么记忆要带来源? 同一条信息,主人亲口说的和 AI 自己推断的,可信度天差地别。来源五档(owner/self/evidence/tool/inferred)就是在写的时候把「谁说的」钉死,读的时候按信任排序。没标来源的(inferred)永远垫底——宁可保守,不把猜测当事实。
Q3:作废和删除有什么区别?
删除 = 烧档案,错了找不回来。作废 = 盖钢印,行还在、历史还在、随时能翻案(relay_recall 带 includeSuperseded: true)。这是 Katsuno-Mendelzon「最小变更」和 Darwiche-Pearl「使用只看当前态」的落地:日常检索自动跳过作废条,历史查询永远查得到。
Q4:同键(subject)是什么? 给同一件事一个固定标识(作品名、办件号、项目名)。同一个键再写新记忆,旧记忆自动作废、并指向新条——用规则替换,不用模型猜。共指冲突(「那个视频」和「CTX1」是不是一个东西)在写入时就消灭,不留给读取时猜。
Q5:为什么「作废/冲突」不搞全自动? 开放世界的常识冲突(「上午有课」vs「上午坐高铁」)没有封闭解,自动判死误删真记忆的代价远大于漏判。所以确定性冲突走规则(同键替换),疑似冲突靠人终审——这正是 2026 年行业前沿(STALE / Kymata 白皮书)停在「标注 + 人」的原因,小规模系统天然适合这条路。
Q6:和同类插件的区别? DSH 生态里的记忆插件(dsh-mem-weave 忆织、dsh-identity、dsh-memory 等)多数解决「记住」和「注入」。本插件的差异化只有一条链:来源五档 + 同键规则替换 + 永久盖章翻案。三者同时具备的,2026-10 调研的 8 个竞品里没有第二家。它刻意保持零依赖、纯文本、42~200 条量级够用——不为规模过度设计。
Q7:密钥/密码能写进记忆吗?
不能。relay_remember 写入前扫描明文凭据(sk- 前缀、api_key=、password:、长 token、PEM 私钥等),命中直接拒绝写入——宁拦错,不放行。密钥只进钥匙串,记忆只存指针。
Q8:两个 DSH 实例能共用同一个记忆库吗?
能(2026-11-02 起)。写入是「读-改-写」且带两层锁:进程内串行锁(防并发丢行,基准 E08 实测通过)+ 跨进程写锁 facts.lock(独占创建、30 秒陈旧自动抢占、5 秒等待上限)——两个进程同时写同一个 memoryDir 会排队串行,不再互相覆盖;写盘走临时文件 + 原子替换,读方永远看不到半截文件。桌宠(dsh-pet)侧 pet/chat/relay_store.py 用同一份协议。锁等待超时 = 放弃本次修改(不丢别人已写的数据)。
Q9:会不会动我的人格和情绪? 不会。插件只碰记忆层:记什么、凭什么信、还算不算数。人格卡和情绪表达是 owner 自己的事,插件只会原样搬运、原样注入。
给已上线 bot 的迁移三步(趁上下文还没满)
- 装好插件,让她
relay_status确认记忆库位置; - 把现在提示词里的人设原样贴进
persona.md(owner 直接编辑文件即可,改完下一世生效); - 让她
relay_handover写第一份交接书。
从此:上下文是身体,会换;磁盘是灵魂,不灭。
原理(给开发者)
- 零依赖 Host 插件,
apply(ctx, config)形式,服务走ctx(tools/systemPrompt/tokenMeter),文件走node:fs; - 开工读档:
agent/created(serial)里读文件 →agent.inject(...)(进入 next-step, 不唤醒),注入消息带 producer-owned 来源plugin:dsh-identity-relay; - 容量预警:
agent/pre-step瀑布里用tokenMeter.measure(session)估算占用,超线后{...decision, messages: [...decision.messages, nudge]},只追加不改判; - 常驻锚:
systemPrompt.section(),order 紧随DEPLOYMENT_PERSONA_SUFFIX; - 记忆库是纯文本:任何语言、任何平台都能读,不被 DSH 绑架。
相关文档
- TUTORIAL.md — 一步步装好、跑通、写第一份交接书
- CHANGELOG.md — 每个版本改了什么、测试什么数
- docs/tech-article-memory-maintenance.zh.md — 技术文章《AI 伙伴记忆维护》:为什么记忆需要来源、作废和关联
License
MIT · 作者 @wumingzhiren-lab
No comments yet. Be the first to write one.