数据截至 (上游 commit ac9c2a586ba7)
第 2 章:记忆动力学 —— 衰减、强化与反思
本章讲什么: 上一章讲了记忆"怎么进、怎么出";这一章讲记忆"活着的过程"——为什么老记忆会淡出、被用到为什么会变重要、相关记忆怎么被联想带动、以及后台怎么把零散记忆聚成"反思"。这是 OpenMemory 相对 RAG 的核心卖点。
1. 直觉:记忆不是静态的
RAG 里一条向量存进去就永远不变、每次检索机会均等。OpenMemory 借人脑的两条规律:
- 遗忘曲线: 没被用到的记忆随时间指数衰减。
- 强化: 每次"想起"一条记忆,它会被巩固,下次更容易想起。
实现载体是每条记忆的一个标量 salience(重要度/激活度,0~1)。它被三处改写:后台衰减、召回强化、联想扩散。
2. 衰减公式:分脑区、按新近度指数衰减
查询打分时用的"当 前 salience"是实时算出来的,不是数据库里那个静态值:
# 示意,非源码:calc_decay 的核心
decayed = init_salience * exp(-lambda * days_since_last_seen) # 指数遗忘
reinf = alpha_reinforce * (1 - exp(-lambda * days_since)) # 一点点回补
salience = clamp(decayed + reinf, 0, 1)
lambda来自脑区配置(SECTOR_CONFIGS[sec]["decay_lambda"]):情绪 0.02 忘得快、反思 0.001 忘得慢。- 真实实现:
calc_decay(memory/hsg.py:144),alpha_reinforce=0.08(HYBRID_PARAMS,hsg.py:36)。 - root-child 里子段还按
sqrt(seg_idx/max_seg)削减 lambda,让靠后的段落衰减更慢(hsg.py:148-150)。
3. 后台衰减任务:分冷热档 + 压缩向 量
apply_decay(memory/decay.py:111)是周期性后台作业,做三件事,由记忆的"温度档"决定力度。
3.1 三档温度
pick_tier(decay.py:42)按"最近是否被用 + 是否高激活"分档:
| 档 | 条件 | 衰减 lambda |
|---|---|---|
| hot | 近 6 天用过 且 (coactivations>5 或 salience>0.7) | 0.005(慢) |
| warm | 近 6 天用过 或 salience>0.4 | 0.02 |
| cold | 其余 | 0.05(快) |
3.2 衰减 + 向量降级(节省空间的巧思)
对每条记忆算衰减因子 f = exp(-lambda * dt/(sal+0.1))(decay.py:154),然后:
f < 0.7 ──▶ compress_vector:把向量维度池化压小(省空间)
f < 0.3 ──▶ fingerprint_mem:直接降级成 32 维哈希"指纹" + 关键词摘要
妙在哪: 越冷的记忆占的存储越小——先压缩再降级成指纹,而不是简单删掉。降级后若将来又被查到,on_query_hit 会重新嵌入还原(decay.py:195-204)。真实实现:compress_vector(decay.py:58)、fingerprint_mem(decay.py:98)、降级逻辑(decay.py:157-176)。
3.3 两道保护
apply_decay 开头会跳过:有活跃查询时(active_q > 0,decay.py:113)、以及冷却期内(60s,decay.py:118)——避免和查询争锁、避免过于频繁。它还只随机抽一个 decay_ratio(默认 3%)的批次处理(decay.py:137-142),把开销摊薄。
4. 召回强化:用到就变重要
召回闭环发生在 hsg_query 尾部与 on_query_hit:
applyRetrievalTraceReinforcementToMemory(ops/dynamics.py:38):sal + 0.18*(1-sal)——朝 1 逼近,越低的涨得越多。on_query_hit(decay.py:188):salience + 0.5(注意:206-207先写 +0.15 又被 +0.5 覆盖,实际生效 0.5)、刷新last_seen_at;若向量已被降级(≤64 维)就重嵌入还原。
5. 联想扩散:想起一个,带回一串
要解决的小问题: 人想起"公司年会",连带想起"那晚喝多了"。记忆之间该能互相带动。
思路: 命中记忆强化后,顺着它的 waypoint 出边,给邻居也按"边权 × 源 salience"加分,并带时间衰减,让"很久没碰的邻居"带动弱一些。
真实实现:propagateAssociativeReinforcementToLinkedNodes(ops/dynamics.py:41)算出邻居新分,hsg_query(hsg.py:642-654)再叠加 gamma(0.2)与 exp(-0.02*time_diff) 的上下文 boost 后回写。
命中记忆 A(强化后 salience=0.9)
│ waypoint 出边
├──weight 0.5──▶ B: +0.18*0.5*0.9 → B 回暖
└──weight 0.3──▶ C: +0.18*0.3*0.9 → C 小幅回暖
(再乘时间衰减:越久没碰的边,带动越弱)
waypoint 边本身也会被强化/剪枝:走过的路径 reinforce_waypoints(hsg.py:347)加权,弱边 prune_weak_waypoints(hsg.py:360,阈值 0.05)删除。
6. 反思:把零散记忆聚成洞察
要解决的小问题: 存了 20 条"又调了一天 bug",单看每条价值低,但"你最近一直在 debug"这个模式有价值。
思路: 后台 run_reflection(memory/reflect.py:92)周期跑:取最近 100 条记忆,按"同脑区 + 文本 Jaccard 相似>0.8"聚类(cluster,reflect.py:29),≥2 条的簇生成一条 reflective 记忆(summ,:68),把源记忆标记 consolidated 并 boost(:74-90)。
注意: 相似判定用的是词集合 Jaccard(
sim_txt,reflect.py:20),不是向量——所以"反思聚类"其实是词面聚类,和检索用的语义向量是两套逻辑。这是个可留意的实现取舍。
7. 关键常量速查
这些散落在 hsg.py / dynamics.py / decay.py 顶部,是调参入口:
| 常量 | 值 | 作用 | 位置 |
|---|---|---|---|
SCORING_WEIGHTS | sim .35 / overlap .2 / wp .15 / rec .1 / tag .2 | 复合分权重 | hsg.py:28 |
alpha_reinforce | 0.08 | 衰减回补项 | hsg.py:36 |
ETA_...TRACE_LEARNING | 0.18 | 召回/扩散强化率 | dynamics.py:12 |
gamma | 0.2 | 联想 boost 系数 | hsg.py:36 |
prune_threshold | 0.05 | 弱边剪枝阈值 | hsg.py:48 |
| lambda hot/warm/cold | .005/.02/.05 | 后台三档衰减 | decay.py:23-25 |