这篇学完你能回答什么
- Agent 的记忆系统怎么设计?短期、长期记忆分别放哪?
- 上下文快满了:滑动窗口 / 摘要压缩 / 重要性过滤怎么选?压缩丢了关键决定怎么办?
- 跨会话记忆怎么做?记忆检索和 RAG 是一回事吗?
从一个真实故障讲起
一个数据库迁移 Agent,任务是把 200 多张表的 DDL 从 MySQL 方言改写到 PostgreSQL。跑了两个多小时,前 90 分钟一切正常。
然后它开始把已经改好的表又改回去。
拉 trace,转折点非常清晰:第 118 分钟触发了一次上下文压缩。压缩前的上下文里有这么一段早期对话——
用户:
DECIMAL统一映射成NUMERIC,不要用DOUBLE PRECISION,我们财务系统对精度有要求。 Agent:明白,已记录。
压缩后的摘要是这样写的:
「用户要求将 MySQL DDL 迁移到 PostgreSQL,已完成 87 张表的类型映射与索引改写,进展顺利。」
那条约束被"进展顺利"四个字吃掉了。 摘要模型的目标函数是"保住任务连续性",而"用户三小时前定过的一个类型映射规则"在它眼里既不是当前子目标、也不是最近内容——于是它被优化掉了。之后 Agent 按默认知识把 DECIMAL 映射成了 DOUBLE PRECISION,还顺手"修正"了之前几张已经正确的表。
更值得警惕的是这个故障的一般形式。学术界 2026 年已经开始专门讨论这件事:压缩是一个治理层面的决策,而它一直是按"任务准确率"这个目标被优化的。当 Agent 的行为约束(组织策略、禁止事项、审批要求)以"上下文里的一段文字"形式存在时,一次以任务连续性为目标的压缩,完全没有动机保住它。被压掉的如果不是类型映射规则,而是"禁止直接修改生产配置",后果就不是返工了。
这篇的核心结论提前给出:记忆系统的难点从来不是"怎么记住",而是"什么必须保真"。
DDL 迁移跑了两小时
200 多张表,MySQL 方言改到 PostgreSQL
第 118 分钟触发压缩
前 90 分钟一切正常
约束被四个字吃掉断点
摘要只写「进展顺利」,
DECIMAL规则没了按默认知识映射
DECIMAL映射成了DOUBLE PRECISION把改对的表又改回去
顺手「修正」了之前几张已经正确的表
DECIMAL 统一映射成 NUMERIC摘要之后一个字都没剩核心概念:先打比方,再给定义
比喻:上下文窗口是内存,外部存储是磁盘,Agent 需要一个"操作系统"在两者之间搬运数据。
这个类比来自 MemGPT 的工作,它把这套机制叫做 virtual context management(虚拟上下文管理)——模型的物理上下文有限,但通过主动换入换出,可以对外表现出"记得很多"的效果。今天几乎所有记忆方案,本质都是在做这件事。
四层记忆(这是回答 Q3-12 最清晰的框架):
| 层 | 中英对照 | 存什么 | 存在哪 | 生命周期 |
|---|---|---|---|---|
| 工作记忆 | working memory | 当前会话的对话与工具结果 | 上下文窗口 | 单次任务 |
| 情节记忆 | episodic memory | 发生过什么(历史交互、决定、事故) | 外部库(向量/图/关系型) | 跨会话 |
| 语义记忆 | semantic memory | 稳定事实与知识(用户偏好、领域知识) | 外部库 | 长期,需更新 |
| 程序记忆 | procedural memory | 怎么做(SOP、技能、规范) | 文件 / Skills / 规则库 | 长期,版本化 |
面试里只答"短期记忆放上下文、长期记忆放向量库"是及格线。用这张表,并指出四层的成本特征和失效模式完全不同,才是有深度的答案。
手边摊开的东西,随手就能拿
有限、昂贵、越长越钝 —— 装不下就得往外搬
当前会话的对话与工具结果
就存在上下文窗口里,生命周期只有单次任务
想用得先知道去哪一格取
取错、取旧、取到三条互相矛盾的,都有可能
情节 / 语义 / 程序,跨会话活着
情节记发生过什么,语义记稳定事实,程序记怎么做(SOP、Skills、规则库)
原理拆解
state 对象,每次压缩后从 state 重新渲染,而不是指望摘要器把它们留下来read_file / memory / task 这类关键工具结果不清| 手段 | 怎么做 | 省得多吗 | 失败模式 |
|---|---|---|---|
| 滑动窗口 | 只保留最近 N 轮 | 中 | 早期约束和决定直接消失,最粗暴 |
| 摘要压缩 compaction | 让模型把历史压成叙述 | 高(常见 60–80%) | 语义丢失,且丢什么不可控;本身要花一次模型调用 |
| 重要性过滤 / 结构裁剪 | 按类型清理旧工具结果、旧 thinking | 中高 | 规则靠人写,长尾覆盖不全 |
生产系统基本不会只用一种。成熟的组合是:结构裁剪打底(先清工具结果这种大块低价值内容)+ 摘要压缩兜底(还不够再摘要)+ 关键信息置顶保真(规则层与任务层单独维护,不进摘要器)。
触发时机也是选择题:反应式压缩次数最少,但等到上下文已被污染很久才动手;周期式可预测,却可能在子目标进行到一半时下刀;轨迹感知在子任务边界切,切口最干净,代价是实现复杂。
上下文预算要分层,不能当成一根随便拼接的字符串
┌─ 上下文窗口(有限、昂贵、越长越钝)─────────────────────────┐ │ [规则层] 系统指令 / 硬约束 / 组织策略 ← 绝不可压缩 │ │ [记忆层] 跨会话稳定信息(用户偏好等) ← 可换出,需检索 │ │ [任务层] 当前目标 / 已做决定 / 待办清单 ← 必须保真更新 │ │ [工具层] 最近的工具调用与返回 ← 最先被清理 │ │ [审计层] 完整 transcript ← 不进上下文,落盘 │ └──────────────────────────────────────────────────────────┘
分层的价值在于每层的生命周期不同:工具返回几轮后就过期,规则层要活到任务结束,审计层根本不该占上下文(它归 trace 系统,见 T3-11)。开篇的故障,本质就是把"规则层"的内容当成"对话历史"一起送进了摘要器。
三种压缩手段,各自的失败模式
| 手段 | 怎么做 | 省得多吗 | 失败模式 |
|---|---|---|---|
| 滑动窗口 | 只保留最近 N 轮 | 中 | 早期约束和决定直接消失,最粗暴 |
| 摘要压缩(compaction) | 让模型把历史压成叙述 | 高(常见 60–80%) | 语义丢失,且丢什么不可控;压缩本身要花一次模型调用 |
| 重要性过滤 / 结构裁剪 | 按类型清理(旧工具结果、旧 thinking),保留关键类型 | 中高 | 规则靠人写,长尾覆盖不全 |
生产系统里基本不会只用一种。成熟的组合是:结构裁剪打底(先清工具结果这种大块低价值内容)+ 摘要压缩兜底(还不够再摘要)+ 关键信息置顶保真(规则层与任务层单独维护,不进摘要器)。
一个可直接抄的策略骨架(阈值需按自己的模型和任务校准):
def manage_context(messages, state):
if tokens(messages) < THRESHOLD: # 未到阈值不动
return messages
# ① 结构裁剪:优先清理大块低价值内容
messages = drop_old_tool_results(
messages, keep_recent_groups=5,
protect_tools={"read_file", "memory", "task"}, # 关键工具结果不清
min_free=5000)
if tokens(messages) < THRESHOLD:
return messages
# ② 摘要压缩:但只摘要「对话与探索过程」这一段
head, mid, tail = split(messages, keep_tail=10)
summary = summarize(mid, must_preserve=MUST_PRESERVE_CHECKLIST)
# ③ 关键状态不走摘要器,从结构化 state 重新渲染
return [system(RULES), render(state), summary] + tail第 ③ 步是重点:把"当前任务目标、已确定的决定、硬约束、未完成 TODO、关键 ID/句柄"维护成一份结构化的 state 对象,每次压缩后从 state 重新渲染进上下文,而不是指望摘要器把它们留下来。 这是开篇故障最直接的修法。
触发时机:三种策略
反应式(reactive):快满了才压 ├ 优点:压缩次数最少 └ 缺点:等到上下文已经被陈旧/错误内容污染很久了才动手(context rot 已经发生) 周期式(periodic):每 k 轮 / 每 k token 压一次 ├ 优点:可预测 └ 缺点:可能在一个子目标进行到一半时下刀,切掉模型正需要的东西 轨迹感知(trajectory-aware):在子任务边界压 ├ 优点:切口干净 └ 缺点:要能识别"子任务结束了",实现复杂
截至 2026-08 的实践共识是:不要等到上下文满了才压。 业界建议的触发点比很多人想象的早得多——轻量负载在 5k–20k token 量级就可以开始压,复杂长任务在 50k–100k 量级;作为参照,Claude Code 的自动压缩大约在有效窗口 98% 处兜底,但那是最后一道闸,不是常规策略。
理由是 context rot(上下文腐化,见 T1-12):上下文越长模型越钝,陈旧的失败尝试、过期的工具输出会持续干扰注意力。压缩的收益不只是省钱,更是恢复注意力质量。
压缩的隐藏成本:缓存失效
一个经常被漏掉、但面试里说出来很加分的点:压缩会打穿 prompt 缓存。
前缀变了,之前累积的 KV 缓存全部作废,压缩后的第一次调用是完整的冷读。所以压缩不能太频繁——它省的是后续每轮的重复 token,付的是一次摘要调用 + 一次缓存重建。压缩频率过高时,总成本可能不降反升。 这也是把稳定内容(规则层)放在上下文最前面的原因之一(见 T1-4)。
工程实践(截至 2026-08)
MUST-PRESERVE 清单(压缩前必须固化的东西)
写压缩逻辑时,把下面这份清单变成显式的结构化字段,而不是交给摘要器自由发挥:
- 原始任务目标(用户的原话,不是转述)
- 硬约束与禁止事项(组织策略、"不要用 X"、审批要求)——优先级最高
- 已做出的决定及其理由("选了方案 B,因为 A 在并发下有问题")
- 未完成的 TODO 与当前所处步骤
- 关键标识符:文件路径、ID、句柄、分支名、会话 token
- 已经失败过的路径(否则压缩后会重蹈覆辙,这是很常见的返工来源)
跨会话记忆(Q3-14):和 RAG 是一回事吗?
表面是一回事,本质不是。 两者都是"从外部存储检索内容塞进上下文",检索技术栈可以完全复用(分块、embedding、混合检索、rerank,见模块 2)。差别在四个地方:
| RAG 的语料 | Agent 的记忆 | |
|---|---|---|
| 写入方 | 外部权威(文档、知识库) | Agent 自己写的,质量由自己负责 |
| 正确性 | 假定为真,只读 | 可能一开始就记错,会传播 |
| 时效冲突 | 少见,靠版本管理 | 常态:"用户住北京"和"用户搬到上海了"必须覆盖而不是并存 |
| 遗忘 | 不需要 | 必需:错误的、过期的、一次性的信息要能删 |
所以记忆系统比 RAG 多出三个必须回答的问题:写什么(不是什么都值得记)、怎么更新与冲突消解、什么时候忘掉。回答 Q3-14 时,把"记忆需要写入策略和遗忘机制,而 RAG 语料不需要"讲清楚,就足以区分你和背概念的人。
一个务实的写入门槛:只有"跨会话仍然成立、且未来大概率会被用到"的信息才写长期记忆。会话内的中间结论不写;用户随口一提的偏好要打上置信度和时间戳;相互矛盾的记忆按时间戳覆盖,并保留一条变更痕迹便于排查。
生态(截至 2026-08,仅作了解,选型请自测)
- Letta(MemGPT 团队):把上下文显式分成 in-context core memory 与 out-of-context 的 recall / archival memory,操作系统味道最重。
- Mem0:可插拔的长期记忆引擎,负责抽取、去重、实体链接与检索。
- Zep / Graphiti:时间感知知识图谱,天然处理"事实有有效期"的问题。
- LangGraph / LangMem:把记忆和状态机、checkpoint 绑在一起,和框架的 durable execution 一体。
- 框架侧的压缩能力也在内建化(如可配置压缩间隔与重叠窗口、自定义摘要模型),自己写之前先看看框架是否已经给了。
进阶技术三段式:摘要压缩(compaction)
- 解决什么:突破上下文物理上限,让长任务能一直跑;同时清掉陈旧内容,缓解 context rot,恢复注意力质量。
- 代价是什么:① 信息不可逆丢失,且丢什么不可控;② 每次压缩要额外花一次模型调用(长上下文进、结构化出,不便宜);③ 打穿 prompt 缓存,后续成本短期抬升;④ 通常是同步的——压缩时 Agent 得停下来等;⑤ 治理风险:以任务连续性为目标的摘要会悄悄抹掉安全约束。
- 什么时候不该用:任务本来就短(几轮就结束,压缩纯亏);上下文里的内容需要逐字保真(合同条款、代码 diff、审计要求)——这类应该外置到文件系统并按需读取,而不是摘要;以及在一个子目标进行到一半时,宁可晚一点压。
避坑清单
- 别把规则层送进摘要器。系统指令、硬约束应从固定模板重新渲染,永不参与压缩。
- 别只靠摘要,先做结构裁剪。工具返回往往占了大头且价值衰减最快。
- 压缩后做一次自检:让模型基于新上下文复述任务目标和当前约束,不一致就回滚或补注入。这一步很便宜,能拦住开篇那类事故。
- 把状态外置到文件。长任务的中间产物写文件、上下文里只留路径,比什么压缩策略都有效。
- 记忆不是越多越好。低质量记忆是污染源——检索出三条互相矛盾的"用户偏好",比没有记忆更糟。
- 给记忆写入也做评测。记了什么、召回准不准,要有指标,否则你永远不知道它在帮忙还是添乱。
| 差在哪 | RAG 的语料 | Agent 的记忆 |
|---|---|---|
| 写入方 | 外部权威(文档、知识库) | Agent 自己写的,质量由自己负责 |
| 正确性 | 假定为真,只读 | 可能一开始就记错,而且会传播 |
| 时效冲突最容易漏 | 少见,靠版本管理 | 常态:「用户住北京」和「用户搬到上海了」必须覆盖而不是并存 |
| 遗忘 | 不需要 | 必需:错误的、过期的、一次性的信息要能删 |
- 压缩前必须固化
- ① 原始任务目标(用户原话)② 硬约束与禁止事项,优先级最高 ③ 已做的决定及理由
- 清单后半段
- ④ 未完成 TODO 与当前步骤 ⑤ 路径 / ID / 句柄 / 分支名 ⑥ 已经失败过的路径
- 触发要提前
- 轻量负载 5k–20k token 就可以开始压,复杂长任务 50k–100k;别等满了才动手
- 最后一道闸
- Claude Code 的自动压缩大约在有效窗口 98% 处兜底,那是兜底,不是常规策略
- 压缩后自检
- 让模型基于新上下文复述任务目标和当前约束,不一致就回滚或补注入 —— 这步很便宜
- 生态(仅作了解)
- Letta(MemGPT 团队)、Mem0、Zep / Graphiti(时间感知图谱)、LangGraph / LangMem
面试视角
- 先给四层框架并强调四层的生命周期和失效模式不同
- 讲压缩先讲分层预算再讲三种手段的组合,别一上来就说「我们用摘要」
- 主动抛 MUST-PRESERVE关键状态从结构化 state 重渲染,不靠摘要器
- 补一句成本视角「压缩会打穿 prompt 缓存,所以不敢压太频繁」
- 有故事就讲故事压缩后行为改变的事故,是最好的素材
- 只说「短期记忆放上下文,长期记忆放向量库」,再无细节
- 认为压缩就是「让模型总结一下」
- 说「上下文满了就压缩」,不知道该提前压
- 认为记忆检索和 RAG 完全一样
- 从没考虑过压缩会丢东西,更没想过丢的可能是约束
- 给出分层上下文预算,并说清哪一层绝不可压
- 结构裁剪优先于摘要,说得出保留最近几组工具结果这类具体策略
- 知道触发点比想象中早,并提到压缩打穿 prompt 缓存的成本影响
- 讲得出写入策略、冲突覆盖、遗忘机制这三件 RAG 不需要的事
- 有 MUST-PRESERVE 清单,尤其提到「已经失败过的路径」要保留
面试官怎么问
答题结构建议
- 先给四层框架,并强调四层的生命周期和失效模式不同。
- 讲压缩时先讲分层预算,再讲三种手段的组合,不要一上来就说"我们用摘要"。
- 主动抛出 MUST-PRESERVE 清单,并说明关键状态是从结构化 state 重渲染而不是靠摘要器保留。
- 加一句成本视角:"压缩会打穿 prompt 缓存,所以我们不敢压太频繁,阈值是实测出来的。"
- 有故事就讲故事——开篇那类"压缩后行为改变"的事故是最好的素材。
分水岭信号
只读过资料的回答:
- 只说"短期记忆放上下文,长期记忆放向量库",再无细节。
- 认为压缩就是"让模型总结一下"。
- 说"上下文满了就压缩",不知道该提前压。
- 认为记忆检索和 RAG 完全一样。
- 从没考虑过压缩会丢东西,更没想过丢的可能是约束。
真做过的回答:
- 给出分层上下文预算,并说清哪层绝不可压。
- 提到结构裁剪优先于摘要,能说出保留最近几组工具结果这类具体策略。
- 有 MUST-PRESERVE 清单,尤其提到"已经失败过的路径"要保留。
- 提到压缩打穿 prompt 缓存的成本影响。
- 提到压缩后自检 / 复述校验这种验证手段。
- 讲记忆时能说出写入策略、冲突覆盖、遗忘机制——这三点是记忆区别于 RAG 的核心。
- 提到把状态外置到文件系统,而不是死磕上下文。
小结与延伸
- 记忆分四层:工作 / 情节 / 语义 / 程序,生命周期与失效模式各不相同。
- 上下文要按层做预算:规则层不可压,工具层最先清,审计层根本不进上下文。
- 压缩三手段组合使用;触发要提前,不要等满;压缩会打穿 prompt 缓存。
- 关键状态从结构化 state 重渲染,不要交给摘要器——这是"丢了关键决定"的根治法。
- 跨会话记忆比 RAG 多三件事:写什么、怎么更新冲突、什么时候忘。
延伸:压缩抹掉安全约束的问题,在架构上的正解是把约束放进护栏层而不是上下文,见 T3-10。上下文越长越钝的机制见 T1-12,prompt 缓存机制见 T1-4。多 Agent 场景下"用子 Agent 做上下文隔离"其实是另一种记忆管理手段,见 T3-8。
继续深入
本篇归属第 3 章「Agent 开发」,去做这一章的题。