数据截至 (上游 commit 2d38dafede67)
记忆的生命周期:演化、巩固、遗忘
本章讲什么: 记忆不是只进不出的日志。agentmemory 给记忆装了「演化」——新版本取代旧版本、零散观察巩固成知识、不用的记忆衰减消失。这章讲三件事:取代、巩固、遗忘。
1. 两种记忆先分清
项目里有两种东西容易混,先分清:
| 观察(Observation) | 记忆(Memory) | |
|---|---|---|
| 来源 | hook 自动捕获工具调用 | agent 主动调 memory_save |
| 类型 | CompressedObservation | Memory |
| 存哪 | KV.observations(sessionId)(按会话分 scope) | KV.memories(全局一个 scope) |
| 粒度 | 一次工具调用 | 一条提炼出的决定/模式/偏好 |
| 定义 | types.ts:46-64 | types.ts:83-105 |
本章主角是记忆——它有版本、有取代、有衰减;观察 相对更「流水账」。
2. 取代:新记忆怎么替掉旧记忆
要解决的小问题: agent 上周存了「用 yarn 装依赖」,这周改主意存「用 pnpm」。不能两条都留着自相矛盾——新的该取代旧的。
思路: 存新记忆时,扫一遍现有记忆,如果内容足够相似(Jaccard > 0.7)就判定为「同一件事的新版本」,把旧的标记为非最新、新的版本号 +1。
这段逻辑在 mem::remember(src/functions/remember.ts:23,符号 registerRememberFunction),包在 withKeyedLock("mem:remember") 里串行执行。核心比较(remember.ts:125-141):
// remember.ts:77 —— Jaccard 相似度判定取代(真实源码)
const similarity = jaccardSimilarity(lowerContent, existing.content.toLowerCase());
if (similarity > 0.7) {
supersededId = existing.id;
supersededVersion = existing.version ?? 1;
supersededMemory = existing;
break;
}
jaccardSimilarity(src/state/schema.ts:132)很朴素:把两段文本切成 >2 字符的词集合,算交集/并集。
命中取代后(remember.ts:152-190):
- 新
Memory的version = 旧版本 + 1,parentId = 旧id,supersedes = [旧id]。 - 旧记忆
isLatest = false写回(不删!保留历史)。 - 触发
mem::cascade-update(异步 void),让引用旧记忆的关系跟着更新。
两个保护边界:
- 项目隔离(
remember.ts:122):两条记忆都有明确project且不同时,绝不跨项目取代。无project的旧记忆当通配,不让历史数据被孤立。 isLatest === false的记忆跳过比较(remember.ts:117):只和「当前最新」比,不和已被取代的历史比。
搜索时也只索引最新版本——重建 索引用的 indexRecords 里 if (memory.isLatest === false) continue(src/functions/search.ts:285)。
3. 巩固:4 层记忆
要解决的小问题: 一堆零散观察和会话摘要,怎么沉淀成「我知道的事」和「我会做的事」?
agentmemory 借用人脑记忆巩固的比喻(README 说「不亚于睡眠巩固」),分 4 层(README.md:920-925):
| 层 | 是什么 | 类比 |
|---|---|---|
| 工作记忆(Working) | 工具调用的原始观察 | 短期记忆 |
| 情景记忆(Episodic) | 压缩后的会话摘要 | 「发生了什么」 |
| 语义记忆(Semantic) | 抽取的事实和模式 | 「我知道什么」 |
| 程序记忆(Procedural) | 工作流和决策模式 | 「怎么做」 |
巩固由 mem::consolidate-pipeline(src/functions/consolidation-pipeline.ts,符号 registerConsolidationPipelineFunction)驱动,可定时跑(src/index.ts:606-614,默认每 2 小时),也可手动触发。
它是 opt-in 的且要 LLM。 没开 CONSOLIDATION_ENABLED 或没配 LLM provider 就直接 skip(consolidation-pipeline.ts:51-53)——因为语义合并和程序抽取都要调模型。
语义层做的事(consolidation-pipeline.ts:62-72):攒够 5 个会话摘要,取最近 20 个,喂给 LLM 做语义合并(SEMANTIC_MERGE_SYSTEM prompt),沉淀成 SemanticMemory。程序层类似,从摘要里抽工作流。