跳到主要内容

第 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.40.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_WEIGHTSsim .35 / overlap .2 / wp .15 / rec .1 / tag .2复合分权重hsg.py:28
alpha_reinforce0.08衰减回补项hsg.py:36
ETA_...TRACE_LEARNING0.18召回/扩散强化率dynamics.py:12
gamma0.2联想 boost 系数hsg.py:36
prune_threshold0.05弱边剪枝阈值hsg.py:48
lambda hot/warm/cold.005/.02/.05后台三档衰减decay.py:23-25