Agent 记忆会"满"吗?重要性×时效性×白名单,给记忆排个淘汰优先级
本文是「Agent 应用开发工程师」系列第⑤篇 · 上下文是预算,缓存是第一刀,记忆淘汰是第二刀——把最值得记住的留下。
⚡ TL;DR · 先看结论
- Agent 记忆不是无限仓库,上下文窗口满了就得淘汰,淘汰不能靠"先进先出"——重要记忆刚说完就被扔了。
- 本文给出一个三维评分公式:score = importance × recency × whitelist,用 Redis 有序集合(Sorted Set)做优先级淘汰。
- 白名单机制保护关键记忆不被误杀;TTL + 冷热分层防止"刚记住就被扔"。
- 所有权重/阈值都是骨架,得你自己按场景调参,别写死"最佳值"。
前两篇我们讲了"预算"(Context Engineering)和"缓存"(Prompt Caching)。这一篇接着讲预算的另一半——记忆淘汰。
Cache 管的是"系统提示和工具定义反复算",Memory 管的是"用户说了什么、Agent 发现过什么"。但窗口就那么大,满了之后扔什么,比"用什么方式存"更值得想清楚。
这篇不讲"怎么用向量库存住所有记忆"——那是存储层的事。我们讲满了之后怎么扔:三维评分公式、Redis 排序集实现、白名单保护、冷热分层,以及调参不该踩的坑。
一、先立标准:为什么"先进先出"不适合 Agent
先进先出(FIFO)是最简单的淘汰策略——谁先来谁先走。但在 Agent 场景里,它大概率会做错事:
- 用户刚说了一句"我的项目密码是 XXXX",FIFO 可能因为前面排了太多轮对话,直接把它挤出去。下次 Agent 就忘了密码。
- Agent 刚发现了一个关键工具调用结果,FIFO 可能因为这条消息在窗口里位置靠前,优先淘汰它。
所以 Agent 记忆淘汰需要按"价值"排优先级,而不是按时间顺序。
💡 一个反直觉的事实:长对话里,最重要的信息往往出现在中间——既不是最早(开场寒暄),也不是最晚(当前问题)。FIFO 和 LRU 在这种场景下都不好用。
二、三维评分公式:重要性 × 时效性 × 白名单
一个可落地的方案是给每条记忆打一个综合分,按分淘汰——分最低的先走。
score = importance × recency_weight × whitelist_boost
| 重要性 (importance) | 这条记忆"值多少钱" | 用户明确标注的(“记住这个”)、工具返回的错误/告警、涉及安全/配置的信息 → 高分;日常闲聊 → 低分 |
| 时效性 (recency) | 这条记忆"多新鲜" | 刚发生的 → 分高;半小时前的 → 分逐渐降低。可用指数衰减 weight = e^(-λ·Δt) |
| 白名单 (whitelist) | 这条记忆"能不能碰" | 白名单内的 → 乘一个大系数(如 10),永不淘汰;不在白名单 → 乘 1 |
⚖️ 权重不说死:λ(衰减系数)、importance 分档、whitelist 乘数——全都得按你的场景调。正文给公式和思路,不给"最佳值"。
重要性分档(示例,非固定)
| 用户明确指令/配置 | 10 | “记住这个密码”“把这条规则记下来” |
| 工具返回错误/告警 | 8 | API 调用失败、权限不足 |
| 关键业务数据 | 7 | 订单号、项目 ID、配置项 |
| Agent 发现的重要结论 | 5 | 经过推理得出的事实 |
| 一般对话上下文 | 3 | 日常问答 |
| 问候/闲聊 | 1 | 开场白、寒暄 |
三、用 Redis 有序集合做优先级淘汰
Redis 的 Sorted Set(有序集合) 天然适合这个场景:ZADD 把记忆 ID 和 score 写进去,ZRANK 查排名,ZREMRANGEBYSCORE 按分淘汰——不存在"遍历所有记忆找最不重要的"。
# ⚠️ 骨架示例:Redis 排序集做记忆淘汰,以你实际 Redis 版本为准
import redis, time, json
r = redis.Redis(host="localhost", port=6379, db=0)
MEMORY_KEY = "agent:memory:priority"
def remember(memory_id: str, importance: float, is_whitelisted: bool = False):
"""写入一条记忆及其评分"""
recency = 1.0 # 刚发生的,时效性满分
whitelist = 10.0 if is_whitelisted else 1.0
score = importance * recency * whitelist
r.zadd(MEMORY_KEY, {memory_id: score})
def decay_memories(decay_rate: float = 0.95):
"""每隔一段时间给所有记忆的时效性乘一个衰减系数"""
for mem_id in r.zrange(MEMORY_KEY, 0, –1):
mem_id = mem_id.decode()
current = r.zscore(MEMORY_KEY, mem_id)
# 白名单记忆不衰减
if current and current >= 10.0:
continue
# 非白名单:时效性衰减
new_score = current * decay_rate if current else 0
r.zadd(MEMORY_KEY, {mem_id: new_score})
def evict_if_full(max_size: int = 100, evict_count: int = 10):
"""如果记忆超过上限,淘汰评分最低的 evict_count 条"""
current = r.zcard(MEMORY_KEY)
if current > max_size:
# 淘汰分最低的
to_evict = current – max_size + evict_count
# 找到最低分的记忆
lowest = r.zrange(MEMORY_KEY, 0, to_evict – 1)
# 删除它们
for mem_id in lowest:
r.zrem(MEMORY_KEY, mem_id)
print(f"Evicted {len(lowest)} memories")
🧭 为什么用 Sorted Set 而不是 List:List 只能按插入顺序"先进先出",Sorted Set 可以按分排序,取最低分、按范围删除都是 O(log N)——这在记忆量大的时候是质的区别。
三个要命的边界
四、白名单机制:有些记忆不能被淘汰
白名单是关键设计。不是所有记忆都该参与淘汰——有些是"死了也不能丢"的:
- 用户身份信息(“我是张三”)
- 系统配置/偏好(“用中文回复”)
- 安全凭证(“我的 API key 在环境变量里”)
- 长任务上下文(“我们正在处理订单 #12345”)
实现上,这些记忆的 score 被乘一个大系数(如 10),让它们永远排在 Sorted Set 的顶端。
# ⚠️ 骨架示例:白名单保护逻辑
WHITELIST_BOOST = 10.0
def remember_with_whitelist(memory_id: str, importance: float,
is_whitelisted: bool = False):
whitelist = WHITELIST_BOOST if is_whitelisted else 1.0
recency = 1.0
score = importance * recency * whitelist
# 如果是白名单,记录白名单元数据备用
if is_whitelisted:
r.hset("agent:memory:whitelist", memory_id, json.dumps({
"importance": importance,
"created_at": time.time()
}))
r.zadd(MEMORY_KEY, {memory_id: score})
⚠️ 白名单的"白"不是永久的:用户可能改主意(“不再用中文”)、任务可能结束(订单已完成)。建议提供一个"降级"接口,把白名单记忆降为普通记忆,参与后续淘汰。
五、TTL + 冷热分层:防止"刚记住就被扔"
还有一个常见问题:一条记忆刚写进去,因为初始分数低(比如普通闲聊 importance=1),下一轮淘汰就被扔了。
三个办法:
# ⚠️ 骨架示例:新记忆保护期
PROTECTION_TTL = 30 # 秒
def remember_with_protection(memory_id: str, importance: float, ...):
# … 正常写入逻辑 …
# 在保护期集合中记录
r.sadd("agent:memory:protected", memory_id)
r.expire(f"agent:memory:protect:{memory_id}", PROTECTION_TTL)
def evict_except_protected(max_size: int):
"""淘汰时跳过保护期内的记忆"""
protected = r.smembers("agent:memory:protected")
# 只淘汰不在保护期内的低分记忆
...
六、和 Context Engineering、Prompt Caching 合起来看
系列走到这里,三张"成本牌"已经齐了:
| Context Engineering | 往窗口里放哪些 token | 预算分配,L0-L5 分层 |
| Prompt Caching | 系统提示/工具定义少算 | 前缀缓存,断点 + 预热 |
| 记忆淘汰 | 用户说了什么该留 | 三维评分,Redis Sorted Set 淘汰 |
三者的关系:Context Engineering 决定"放什么",Prompt Caching 确保"放进去的少算",记忆淘汰确保**“放不下的扔得对”**。
💡 对应试/面试的启发:当面试官问"Agent 长对话怎么处理",不要只答"用 RAG 存"——把这三张牌串起来,比只答一个工具名深得多。
七、10 条面向客户/面试的表达红线
| 「用向量库存所有记忆就行」 | 存是存储层,淘汰是策略层,存得下不等于窗口放得下 |
| 「先进先出够用了」 | Agent 对话里最重要的信息居中,FIFO 正好淘汰它 |
| 「我的评分公式是通用的」 | 权重/阈值按场景调,没有万能参数 |
| 「白名单记忆永不淘汰」 | 白名单会膨胀,需要上限和"降级"机制 |
| 「Redis 做记忆淘汰很慢」 | Sorted Set 的 ZRANK/ZREMRANGEBYSCORE 是 O(log N),够快 |
| 「分数越高越重要」 | 分数高只说明现在排在前面,衰减会改变排名 |
| 「衰减系数设一个就行」 | 不同场景(短期对话 vs 长期项目)衰减速度不同 |
| 「淘汰了就不管了」 | 淘汰的记忆可以归档到冷层,需要时还能回溯 |
| 「记忆管理不需要测试」 | 模拟长对话、注入关键信息、看它是否被误杀——必须测 |
| 「我的记忆方案是生产级的」 | 如果没跑过超过窗口的对话,它只是骨架,别写成"已上线" |
八、一句话带走
记忆不是无限仓库,淘汰是必然的决策;用三维评分排优先级,用 Redis 排序集落地,用白名单保护关键记忆——然后把一切数字标成"按场景调参"。
下期预告:第⑥篇讲「Delta Channels」——LangGraph 的 checkpoint 从 O(N²) 降到 O(N) 的"实验性"功能,值得每个做复杂 Agent 的人关注。
参考:Redis Sorted Set 文档(ZADD/ZRANK/ZREMRANGEBYSCORE);MemGPT/LangMem 官方文档(以发布时版本为准);本文评分公式为骨架,权重/阈值需按场景调参。
本系列目录
- ① Java 后端不做 Agent?Spring AI + MCP 实战(已发布)
- ② MCP vs A2A:协议栈位置与选型决策树(已发布)
- ③ 别再只会写 Prompt:Context Engineering 才是 Agent 的下一个范式(已发布)
- ④ Prompt 缓存不是玄学:命中判定、断点与预热(已发布)
- ⑤ 记忆淘汰策略:重要性 × 时效性 × 白名单(本文)
- ⑥ LangGraph Delta Channels——下期预告
- 安全与可信 → 成本与跨栈 → 未完待续
网硕互联帮助中心





评论前必须登录!
注册