E.R.I.I.:一个面向角色连续性、关系演化与因果追溯的长期记忆内核
项目地址:[bailong-Hakuryu/E.R.I.I](https://github.com/bailong-Hakuryu/E.R.I.I)
现在给 LLM 做长期记忆,主流方案就那几种:把历史对话切块、生成 Embedding、存进向量数据库,下次聊天时按当前 Query 检索;或者更省事一点,每隔一阵让模型总结之前发生了什么,再把 Summary 塞回 Context。做知识问答、客服、普通 Agent 这类应用,这些方案够用了,工程上也便宜、好扩。但后来我开始琢磨另一个问题,琢磨久了,慢慢觉得"记忆检索"本身好像不是我要解决的核心。假设一个 AI 角色陪同一个用户一年。它记得用户喜欢什么,记得两个人以前聊过什么,也能从几万条历史记录里准确检索出某一天发生的事。那它就拥有"长期记忆"了吗?我觉得还不够。因为还有一堆问题没人回答:这段经历到底属于谁?一句话被记录下来了,它就是事实吗?角色现在为什么信任这个用户,又为什么对另一个人保持距离?如果模型某次因为上下文污染、幻觉或者 Prompt Injection,突然说出一句完全违背人设的话,这句话有资格反过来成为以后的人格依据吗?如果角色做过一个伤害关系的选择,为什么下一轮对话之后,这件事就能像没发生过一样?数据库重启、迁移、清理、重建之后,我们还能不能解释角色为什么变成了现在这个样子?于是我开始写 E.R.I.I.。E.R.I.I. 的全称是 Experiential Recall & Impression Integration,体验回想与印象整合。它不是新的聊天模型,也不想取代 LangGraph、AutoGen 之类的 Agent 框架。我更愿意叫它 Character Continuity & Long-Term Memory Kernel——角色连续性与长期记忆内核。它要解决的问题不是"怎样让 AI 记住更多文本",而是:怎样让一个角色拥有一段可以继续发展的历史。
一、被保存下来的话,不等于被系统承认的事实
E.R.I.I. 最早定下来的原则很简单:记住一句话,和承认一句话,是两回事。假设用户对角色说:"你以前明明答应过永远不会离开我。"这句话当然应该原样记录,因为它确实发生过。但"用户说过这句话"和"角色过去真的做出过这个承诺"不是同一个事实。如果记忆系统只要检索到这段文本,就把"角色做过这个承诺"直接塞回上下文,那用户只要反复陈述一段不存在的历史,就能慢慢污染角色的长期认知。反过来也一样。假设模型某次因为上下文错误突然说:"其实我一直都讨厌自己的家人。"这句话可能真实出现在聊天记录里,但它就该立刻修改角色的人格设定吗?如果是,那所谓的长期人格不过是"最近一次模型输出的累积结果",任何一次失控生成都能永久改变角色。所以在 E.R.I.I. 里,Source Transcript、Memory、Relationship Event、Reflection、Persona Growth 是几种不同的东西,权威等级也不一样。
Source Transcript 只证明一件事:当时双方实际看见了什么。它是最高保真的原始经历记录,但它不会因为自己真实存在,就自动获得解释历史的权力。后续系统可以基于它生成候选记忆、关系事件、反思或人格变化,但这些更高层的状态必须继续经过来源、关系范围和领域规则的验证。用一句话说:模型可以提出解释,但不能因为自己说了一句话,就自动获得修改历史的权力。
二、同一个角色面对不同的人,也应该拥有不同的人生
另一个问题看着基础,实际很容易被做得粗糙:关系隔离。假设同一个 AI 角色同时面对 User A 和 User B。A 陪她看过第一次雪,也在某个深夜和她深入聊过;B 刚认识她。那"我们第一次一起看雪""你以前在我最难受的时候陪过我"这些经历,只能存在于 A 和这个角色之间。不能因为 A 和 B 面对的是同一个 Agent,B 就继承 A 的共同记忆。
所以 E.R.I.I. 在领域模型上把关系拆成:
Agent × User → Relationship
角色可以有共享的 Agent Identity 和 Character Blueprint,但每段 Relationship 都有自己的 relationship_id、persona_id、长期记忆、关系事件、承诺、开放事项和状态投影。乍一看,这跟在向量数据库里加个 user_id filter 差不多。区别在于,E.R.I.I. 不只在 Recall 阶段过滤,而是让 Relationship Scope 贯穿记忆形成、事件验证、Persona 演进、MemoryPack 导入导出、删除、重建以及后续的因果引用。换句话说,"这段记忆属于谁"不是查询时临时附加的条件,而是一条领域不变量。这里也得说清楚一个边界:E.R.I.I. 说的 Agent × User 隔离,是数据语义和领域完整性意义上的隔离,不是生产 SaaS 里完整的租户安全系统。身份认证、对象级授权、TLS、KMS、传输安全、速率限制这些,依然该由宿主应用负责。我希望项目在这种地方保持诚实,而不是因为有个 relationship_id 就宣称自己做到了"企业级多租户安全"。
三、我不想给角色做一个"好感度 82"的系统
很多游戏、虚拟伴侣或者角色扮演系统建模关系,最后都会得到一根数轴:
好感度:82
送礼物 +5,陪伴 +3,吵架 -10。
好懂是好懂,但我一直觉得它表达不了真实的人际关系。一个人可能跟你很熟、有过很亲密的经历,但并不信任你;也可能依然信任你,却因为刚发生的一件事处于高冲突状态;还可能对你很有安全感,但亲密关系并不深。所以 E.R.I.I. 现在把 Relationship State 拆成五个维度:
familiarity 熟悉程度
trust 信任
intimacy 亲密程度
safety 安全感
conflict_tension 冲突张力
这些数值不是让模型每轮对话后随手输出一个 JSON、数据库照单全收的。Relationship Event 可以携带状态变化,但普通事件有确定性的幅度约束:单个普通事件对任何维度的自动修改,都不允许出现巨大的瞬时跳变。数字背后是更要紧的想法:关系应该由经历慢慢形成,而不是被模型某一次情绪饱满的生成瞬间改写。
用户刚认识角色十分钟,就算说了一句"我会永远保护你",也不能让 Trust 从陌生直接跳到绝对信任;同样,一次轻微争执也不该把几年攒下来的关系清零。模型负责理解语言,"这句话有资格改变多少状态"由系统规则决定。
四、Models Propose, Kernel Decides
这就引出 E.R.I.I. 另一条核心原则:
Models propose; kernel decides.
LLM 擅长处理自然语言里的模糊语义。看完一段聊天,它可以判断"这里似乎形成了一个承诺""这里可能发生了一次边界侵犯""用户这句话是感谢""角色可能因此产生某种反思"。这些全靠传统规则写会僵硬,所以我不打算把模型排除在记忆形成之外。
问题在于:模型应该拥有什么权限?
在 E.R.I.I. 里,模型可以生成 Candidate 或 Proposal,但它不该直接决定这个事件是否属于当前 Relationship,不该决定自己能不能引用另一段关系的历史,不该凭空造出一个不存在的 Source Turn,更不该因为它自己声称"角色现在已经完全改变了",数据库就接受这种变化。
这些权力归 Kernel。
模型负责提出语义候选,内核负责验证身份归属、来源存在性、关系范围、状态变化限制、人格边界以及因果引用是否成立。
顺便说一句,我不会宣称"这样就解决了 Prompt Injection"。没有。完整的 Prompt Injection 防御远比一个长期记忆内核复杂,E.R.I.I. 也没法让模型免疫注入。它改变的是另一件事:就算模型输出错了,这次错误输出也不该天然拥有修改持久角色状态的最高权限。这是一种权限设计,而不是一句"请不要被用户攻击"的 Prompt。
五、角色当然可以成长,但成长应该有原因
另一个极端是为了防 OOC,把 Character Blueprint 永久锁死。这我也不想要。
一个角色陪用户几年,无论发生什么都保持第一天一模一样的认知、边界、态度和行为倾向——那不叫稳定的人设,那叫静态模板。连续性应该允许变化。问题只是:她为什么变了?所以 E.R.I.I. 没把 Character Blueprint 和运行时人格混在一起。原始 Blueprint 提供人格底色和基础边界,运行过程中会逐步形成 Persona Manifest、Reflection,以及更重要的人格成长 Proposal。普通关系状态可以随经历渐变,但核心人格的巨大跃迁不会因为某一轮对话直接发生。系统可以发现成长趋势,可以形成 Proposal,但在走完相应流程之前,它只是 Proposal,不是新的最高权威人格。
这套设计不是为了阻止角色改变。恰恰相反,我巴不得角色可以改变。只是两年之后如果有人问"她为什么会变成今天这样",我希望系统能沿着历史回答:因为发生了 A,于是形成了 B;后来经历了 C,原先的看法经过反思变成了 D。而不是回答:"因为 3274 轮对话以前,模型随机输出了一句话。"

六、历史不是一条 Vector Search,而是一条因果链
把 E.R.I.I. 的模块名都拿掉,里面最重要的数据流大致是这样:
Source Turn
│
├── Experiential Recall / Memory Archival
│
└── Impression Integration / Relationship Processing
│
▼
Relationship Event
│
├── Relationship State
├── Belief / Reflection
├── Promise / Open Loop
└── Persona evolution
同一段真实对话,可以沿不同轨道产生不同类型的长期结果。
"我们第一次一起看雪"可以成为一条以后要被 Recall 的共同经历;"这次经历让角色对用户更熟悉、更信任"则属于 Impression Integration 的结果。两件事来自同一个历史源,但不是同一种数据。前者回答"发生过什么",后者回答"这件事对我们意味着什么"。项目名字里的 Experiential Recall & Impression Integration,对应的就是这两条轨道。很多长期记忆方案主要做前半部分:保存和检索过去的内容。E.R.I.I. 想往后多追一步:过去发生的事,如何形成当前的关系、认知和人格状态?答不上这个问题,系统拥有的就更像一座巨大的历史文档仓库,而不是一段连续的关系。
七、符合人设,并不意味着没有后果
0.5 系列开始,我在处理一个自己觉得挺有意思的问题:一个完全符合人设的选择,也可能伤害当前关系。
不少 AI 角色系统把 Character Consistency 和 Relationship Outcome 混为一谈:用户因为角色的行为不舒服,就是角色 OOC;只要行为符合人设,就仿佛不该对关系产生任何负面后果。现实显然不是这样。两件事完全可以同时成立——这个选择符合角色,以及,这个选择确实伤害了我们之间的关系。比如一个边界感强的角色明确拒绝了用户,这符合她的 Blueprint,但双方照样可能起冲突。再比如一个理性的角色在关键时刻坚持自己的判断,从一致性角度看毫无问题,却可能让另一方失望。
所以 0.5 引入了 Relationship Consequence 和 Narrative Tension。一个有充分连续性来源的行为产生持久关系后果后,这个 Consequence 会被记录下来;之后的交流不会覆盖原记录,而是通过新的 Link 描述它如何继续演化。Narrative Tension 目前可以投影成这些状态:
unaddressed
addressed_unresolved
mutually_reconciled
boundary_stabilized
relationship_ended
superseded
关键在于:当前状态不是模型重新总结出来的一句话,而是由历史 Consequence 和后续 append-only Link 确定性投影出来的。所以"发生过伤害"和"双方后来和解"并不冲突——原始 Consequence 还在,只是追加了一条新的关系事实:双方完成了修复。系统也不强制所有冲突都走向 mutually_reconciled。用户说"我原谅你",不代表角色必须重建亲密关系;角色表达边界,不代表系统该把她判成异常。双方完全可以停在 boundary_stabilized,甚至 relationship_ended。
我挺喜欢这个设计,它避开了 AI 伴侣系统里一个常见倾向:不管发生什么,最后都得重新变得温柔、亲密、讨好。如果真把 AI 角色当成有稳定人格的长期主体,那拒绝、冷淡、修复、原谅、保持距离、结束关系,都应该是允许存在的结果。

八、私有记忆:角色知道,不代表用户必须知道
长期角色系统里还有一类特殊信息:角色自己应该知道,但不该直接展示给用户。
比如角色因为某次事件形成了一种没公开表达的担忧,或者系统内部需要保留某个 Narrative Tension 作为后续决策依据。这类信息如果每次 Recall 都拼进面向用户的生成上下文,模型很容易把本该是"内心活动"的东西直接说出来。所以 E.R.I.I. 在召回层面区分了不同 Audience,其中包括 AGENT_PRIVATE。它的意义不是简单地"藏起一段文本",而是让系统能表达:这是一条角色拥有的认知,但不是当前用户理应直接获知的事实。做角色连续性,这事迟早要面对:Memory 并不天然意味着 Disclosure。"记得什么"和"应该说什么",是两个问题。
九、MemoryPack:我想迁移的是一段关系,而不是一堆聊天记录
长期记忆真要持续几年,数据迁移迟早会来:换数据库、换机器、重新部署、恢复备份、升级 Schema,甚至从一个宿主程序迁到另一个。
传统聊天系统做导出很简单,Message 列表导成 JSON 就行。但如果角色的人格和关系已经由多年历史形成,只迁 Message 够吗?文本全保住了,事件来源、关系状态演进、Persona 版本、Promise、审批信息、某个当前认知依赖哪些历史事实却丢了,那迁移之后得到的只是"拥有同一批聊天记录的新角色",未必还是原来那个连续主体。
所以 E.R.I.I. 提供了 MemoryPack(.erii)。它保存的不是数据库内部实现细节,而是一段 Relationship 所需的持久语义。导入时系统会继续检查关系范围、日志顺序、因果引用和依赖闭包。如果数据有冲突、缺依赖、会形成悬空引用,我的倾向一直是:宁可导入失败,也不要静默丢一部分数据,然后假装恢复成功。
项目里的 Golden Continuity Demo 验证的就是一条完整的链路:
User A 与角色第一次看雪
↓
关闭 Engine
↓
重新启动
↓
A 仍然能够召回这段经历
↓
User B 无法获得 A 的关系记忆
↓
导出 A 的 Relationship 为 .erii
↓
导入新的 SQLite 环境
↓
再次重启
↓
继续验证 Relationship / Persona / Memory / Provenance
还有个我自己挺喜欢的细节:MemoryPack 不会假装自己带了原环境的所有运行时证据。某些 archival receipt 不会完整复制,而是通过 Tombstone 和内容无关的指纹承诺保留必要语义。所以导入新环境后,如果某条 Recall 的原始来源已经不完整,系统会诚实地标成 partial_source 之类的状态,而不是告诉调用者"放心,证据全都还在"。
做长期记忆基础设施,我觉得就得有这个态度:知道多少就说多少,证据缺了一部分,就把"缺了"本身变成状态。
十、删除也属于连续性的一部分
长期记忆系统谈得多的是"怎么记住",谈得少的是"怎么真正忘掉"。
但如果系统在不断形成新的 Memory、Relationship Event、Persona Reflection 和 Consequence,删一条数据就不再只是:
DELETE FROM memories WHERE id = ?
后来的状态可能依赖早期事件,Persona Reflection 可能引用某段经历,Narrative Tension 可能来自更早的 Relationship Consequence。直接删掉中间节点,很容易造出一条还在数据库里、实际已失去来源的"幽灵因果链"。
所以在 E.R.I.I. 的设计里,删除、归档、Tombstone、重建和 Provenance 都是长期连续性的一部分。我在意的不是"数据库里还有没有这一行",而是:删除之后,系统是否仍然诚实地描述自己还知道什么。
从这个角度看,Deletion 不是 Memory 的反面,它本身就是 Memory System 的一部分。
十一、为什么没有直接把 E.R.I.I. 做成一个完整 Agent 框架
我从一开始就比较明确的一个选择:尽量不让 E.R.I.I. 和某个模型厂商、向量数据库或者 Agent 框架深度耦合。它应该能被不同宿主接入——宿主可以用 OpenAI 也可以用别的模型,可以自己管 Web Server 也可以纯本地跑,可以接向量检索也可以不接。界面是网页、游戏、桌面应用还是 NPC Runtime,不该由 Memory Kernel 决定。
大致的边界是这样:
Host
├── Model Provider
├── UI
├── Authentication
├── Network / Security
├── Generation
└── Product LogicE.R.I.I.
├── Turn lifecycle
├── Source provenance
├── Memory
├── Relationship
├── Persona continuity
├── Consequence
├── Narrative tension
├── Recall authority
└── MemoryPack
这里也纠正一个早期宣传语容易误导的地方:E.R.I.I. 现在并不是字面意义上的"Python Zero Dependency",核心运行时有 Pydantic 依赖。更准确的说法是:它不强制依赖远程模型 API、向量数据库、Agent 框架或 Web Server。FastAPI Server、Vector Retrieval、模型 Provider 都是可选的扩展,不是 Core 跑起来的前提。我更关心的是,核心领域模型能不能在没有网络、没有 API Key、没有某家模型服务的情况下,独立完成确定性的状态验证和 Continuity Test。
十二、这不是一个"让 AI 更像人"的项目
E.R.I.I. 用了 Persona、Relationship、Reflection、Trust、Intimacy、Consequence 这些看起来很"人格化"的词,所以容易被理解成一个试图证明 AI 有真实心理活动的项目。不是。我没兴趣证明模型有没有意识。E.R.I.I. 做的是个更工程化的问题:如果一个产品决定长期维护一个角色的身份、经历、关系和行为连续性,这些东西该怎么建模,才不用全指望模型临场编造?
换句话说,Persona 在这里首先是一种软件状态,Relationship 是一种领域状态,Reflection 是一种带来源的推导记录,Narrative Tension 是一种可重放的状态投影。它们可以用来构建人格表现很强的角色,也可以放进 RPG NPC、长期个人助理,或者其他需要状态连续性的 Agent。我想做的从来不是宣布"AI 现在真的有感情了",而是:如果开发者想构建一个看起来拥有连续人生的角色,至少该给这种连续性一个比 Prompt 更可靠的工程基础。

十三、E.R.I.I. 和普通 RAG 是什么关系
我不觉得 RAG 是个需要被"打败"的东西,E.R.I.I. 完全可以和向量检索一起工作。两者解决的是不同问题。RAG 擅长回答"哪些历史内容和当前问题最相关";E.R.I.I. 关心的是"这些内容属于谁、从哪来、有什么权威、形成了什么状态,以及为什么能影响现在"。
完整系统里两者可以这样组合:
Current Turn
│
▼
E.R.I.I. determines scope / authority / continuity
│
▼
Retriever selects useful memories
│
▼
Context Assembly
│
▼
LLM Generation
Vector Search 能帮角色从大量历史里快速找到可能相关的经历,但它不该独自决定一条找到的文本是不是事实、是不是当前关系的记忆、是不是角色已经接受的人格结论。所以我不太喜欢把 E.R.I.I. 叫成"更高级的 RAG"。它不是 RAG 的替代品,是在 RAG 前后加了一层连续性与权威语义。
十四、项目目前做到哪了
0.4.x 是稳定维护线,主分支已经进入 0.5.0a3 的 Alpha 阶段。0.5 的主要新方向就是前面说的 Relationship Consequence 和 Narrative Tension——从"角色能记住过去",走到"过去的事可以产生持久后果"。
没做完的还很多:更复杂的 Character Deliberation、关系修复决策、更高层的长周期叙事、正式产品环境需要的完整安全边界,都有大量空间。我不打算把 E.R.I.I. 说成已经解决"AI 长期人格"的最终答案,它更像一块正在不断收紧定义的基础设施。

十五、最后:AI 角色真正缺的不是记忆,而是历史
最开始写 E.R.I.I. 时,我想的是"怎么让角色记得更久"。写到后来,我发现这个问题本身就问偏了。一个角色可以有几百万 Token 的聊天记录,可以有一个巨大的向量数据库,但它依然可以没有"历史"。History 不只是过去信息的集合。历史意味着:某件事先发生了,于是产生了影响;后来又发生了一件事,之前形成的状态被强化、修正或者推翻。今天的角色之所以是今天的样子,是因为昨天发生过一些事,而那些事不能被下一轮生成随手覆盖。
要用一句话概括我现在对 E.R.I.I. 的理解,大概是这样:
Memory 告诉角色过去发生过什么。History 解释角色为什么成为现在的自己。
E.R.I.I. 想做的,就是把两者之间缺的那部分——来源、关系、印象、后果、人格变化、因果连续性——变成可以执行、验证、迁移、重放的软件状态。它不保证角色一定温柔,不保证关系一定和解,也不保证模型永远不出错。它只想守住一件事:如果这个角色要一直活下去,她至少不该失去自己曾经经历过的人生。


Comments NOTHING