Agent 记忆系统与上下文压缩——被摘要掉的那条约束

T3-6模块 3 · Agent 开发面试权重 更新于 2026-08-19
关联题目Q3-12Q3-13Q3-14

这篇学完你能回答什么

  1. Agent 的记忆系统怎么设计?短期、长期记忆分别放哪?
  2. 上下文快满了:滑动窗口 / 摘要压缩 / 重要性过滤怎么选?压缩丢了关键决定怎么办?
  3. 跨会话记忆怎么做?记忆检索和 RAG 是一回事吗?

从一个真实故障讲起

一个数据库迁移 Agent,任务是把 200 多张表的 DDL 从 MySQL 方言改写到 PostgreSQL。跑了两个多小时,前 90 分钟一切正常。

然后它开始把已经改好的表又改回去

拉 trace,转折点非常清晰:第 118 分钟触发了一次上下文压缩。压缩前的上下文里有这么一段早期对话——

用户:DECIMAL 统一映射成 NUMERIC,不要用 DOUBLE PRECISION,我们财务系统对精度有要求。 Agent:明白,已记录。

压缩后的摘要是这样写的:

「用户要求将 MySQL DDL 迁移到 PostgreSQL,已完成 87 张表的类型映射与索引改写,进展顺利。」

那条约束被"进展顺利"四个字吃掉了。 摘要模型的目标函数是"保住任务连续性",而"用户三小时前定过的一个类型映射规则"在它眼里既不是当前子目标、也不是最近内容——于是它被优化掉了。之后 Agent 按默认知识把 DECIMAL 映射成了 DOUBLE PRECISION,还顺手"修正"了之前几张已经正确的表。

更值得警惕的是这个故障的一般形式。学术界 2026 年已经开始专门讨论这件事:压缩是一个治理层面的决策,而它一直是按"任务准确率"这个目标被优化的。当 Agent 的行为约束(组织策略、禁止事项、审批要求)以"上下文里的一段文字"形式存在时,一次以任务连续性为目标的压缩,完全没有动机保住它。被压掉的如果不是类型映射规则,而是"禁止直接修改生产配置",后果就不是返工了。

这篇的核心结论提前给出:记忆系统的难点从来不是"怎么记住",而是"什么必须保真"。

那条约束,被「进展顺利」四个字吃了
数据库迁移 Agent 跑了两个多小时都正常,压缩一次之后开始把改好的表改回去

  1. DDL 迁移跑了两小时

    200 多张表,MySQL 方言改到 PostgreSQL

  2. 第 118 分钟触发压缩

    前 90 分钟一切正常

  3. 约束被四个字吃掉断点

    摘要只写「进展顺利」,DECIMAL 规则没了

  4. 按默认知识映射

    DECIMAL 映射成了 DOUBLE PRECISION

  5. 把改对的表又改回去

    顺手「修正」了之前几张已经正确的表

用户三小时前定的规则DECIMAL 统一映射成 NUMERIC摘要之后一个字都没剩
摘要模型的目标函数保住任务连续性旧约束既不是当前子目标也不是最近内容
已完成的 87 张表类型映射与索引改写都对其中几张被「修正」成错的
换一种被压掉的内容如果是「禁止直接修改生产配置」后果就不是返工了
更值得警惕的是这个故障的一般形式。压缩是治理层面的决策,却一直按「任务准确率」这个目标在被优化。当行为约束只以「上下文里的一段文字」形式存在时,一次以任务连续性为目标的压缩,完全没有动机保住它。

核心概念:先打比方,再给定义

比喻:上下文窗口是内存,外部存储是磁盘,Agent 需要一个"操作系统"在两者之间搬运数据。

这个类比来自 MemGPT 的工作,它把这套机制叫做 virtual context management(虚拟上下文管理)——模型的物理上下文有限,但通过主动换入换出,可以对外表现出"记得很多"的效果。今天几乎所有记忆方案,本质都是在做这件事。

四层记忆(这是回答 Q3-12 最清晰的框架):

中英对照 存什么 存在哪 生命周期
工作记忆 working memory 当前会话的对话与工具结果 上下文窗口 单次任务
情节记忆 episodic memory 发生过什么(历史交互、决定、事故) 外部库(向量/图/关系型) 跨会话
语义记忆 semantic memory 稳定事实与知识(用户偏好、领域知识) 外部库 长期,需更新
程序记忆 procedural memory 怎么做(SOP、技能、规范) 文件 / Skills / 规则库 长期,版本化

面试里只答"短期记忆放上下文、长期记忆放向量库"是及格线。用这张表,并指出四层的成本特征和失效模式完全不同,才是有深度的答案。

「短期长期」两栏,抹平了四层差别
MemGPT 管这叫 virtual context management:主动换入换出,装作记得很多

内存 · 快,但小

手边摊开的东西,随手就能拿

有限、昂贵、越长越钝 —— 装不下就得往外搬

对应
工作记忆 working memory

当前会话的对话与工具结果

就存在上下文窗口里,生命周期只有单次任务

上下文窗口单次任务
磁盘 · 大,但要找

想用得先知道去哪一格取

取错、取旧、取到三条互相矛盾的,都有可能

对应
另外三层 · 都在外部

情节 / 语义 / 程序,跨会话活着

情节记发生过什么,语义记稳定事实,程序记怎么做(SOP、Skills、规则库)

episodicsemanticprocedural
工作记忆会被压缩吃掉,情节与语义记忆可能一开始就记错并且继续传播,程序记忆得版本化。四层的成本特征和失效模式完全不同,答成两栏,这些差别就全没了。

原理拆解

上下文不是一根字符串,是五层预算
每层的生命周期不同:工具返回几轮就过期,规则层要活到任务结束

规则层系统指令 / 硬约束 / 组织策略 —— 绝不可压缩从固定模板重新渲染,永不参与压缩。开篇的故障,本质就是这一层被当成对话历史送进了摘要器
记忆层跨会话稳定信息(用户偏好等)—— 可换出,需检索写入要有门槛:只有「跨会话仍然成立、且未来大概率会被用到」的才写长期记忆
任务层当前目标 / 已做决定 / 待办清单 —— 必须保真更新维护成结构化 state 对象,每次压缩后从 state 重新渲染,而不是指望摘要器把它们留下来
工具层最近的工具调用与返回 —— 最先被清理占大头且价值衰减最快:保留最近几组,read_file / memory / task 这类关键工具结果不清
审计层完整 transcript —— 不进上下文,落盘它归 trace 系统(T3-11),根本不该占上下文;需要逐字保真的内容也该外置到文件按需读
三种压缩手段,各自的失败模式
手段怎么做省得多吗失败模式
滑动窗口只保留最近 N 轮早期约束和决定直接消失,最粗暴
摘要压缩 compaction让模型把历史压成叙述高(常见 60–80%)语义丢失,且丢什么不可控;本身要花一次模型调用
重要性过滤 / 结构裁剪按类型清理旧工具结果、旧 thinking中高规则靠人写,长尾覆盖不全

生产系统基本不会只用一种。成熟的组合是:结构裁剪打底(先清工具结果这种大块低价值内容)+ 摘要压缩兜底(还不够再摘要)+ 关键信息置顶保真(规则层与任务层单独维护,不进摘要器)。

触发时机也是选择题:反应式压缩次数最少,但等到上下文已被污染很久才动手;周期式可预测,却可能在子目标进行到一半时下刀;轨迹感知在子任务边界切,切口最干净,代价是实现复杂。

前缀一变,之前累积的 KV 缓存全部作废 —— 压缩后的第一次调用是完整的冷读。它省的是后续每轮的重复 token,付的是一次摘要调用加一次缓存重建;压得太频繁,总成本可能不降反升。

上下文预算要分层,不能当成一根随便拼接的字符串

原文示意
┌─ 上下文窗口(有限、昂贵、越长越钝)─────────────────────────┐
│ [规则层]  系统指令 / 硬约束 / 组织策略      ← 绝不可压缩      │
│ [记忆层]  跨会话稳定信息(用户偏好等)      ← 可换出,需检索  │
│ [任务层]  当前目标 / 已做决定 / 待办清单    ← 必须保真更新    │
│ [工具层]  最近的工具调用与返回              ← 最先被清理      │
│ [审计层]  完整 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 清单(压缩前必须固化的东西)

写压缩逻辑时,把下面这份清单变成显式的结构化字段,而不是交给摘要器自由发挥:

  1. 原始任务目标(用户的原话,不是转述)
  2. 硬约束与禁止事项(组织策略、"不要用 X"、审批要求)——优先级最高
  3. 已做出的决定及其理由("选了方案 B,因为 A 在并发下有问题")
  4. 未完成的 TODO 与当前所处步骤
  5. 关键标识符:文件路径、ID、句柄、分支名、会话 token
  6. 已经失败过的路径(否则压缩后会重蹈覆辙,这是很常见的返工来源)

跨会话记忆(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 多三件事:写什么、怎么改、何时忘
检索技术栈可以完全复用,差别落在写入方、正确性、时效冲突和遗忘这四处

跨会话记忆 vs RAG
差在哪RAG 的语料Agent 的记忆
写入方外部权威(文档、知识库)Agent 自己写的,质量由自己负责
正确性假定为真,只读可能一开始就记错,而且会传播
时效冲突最容易漏少见,靠版本管理常态:「用户住北京」和「用户搬到上海了」必须覆盖而不是并存
遗忘不需要必需:错误的、过期的、一次性的信息要能删
MUST-PRESERVE 清单与阈值
压缩前必须固化
① 原始任务目标(用户原话)② 硬约束与禁止事项,优先级最高 ③ 已做的决定及理由
清单后半段
④ 未完成 TODO 与当前步骤 ⑤ 路径 / ID / 句柄 / 分支名 ⑥ 已经失败过的路径
触发要提前
轻量负载 5k–20k token 就可以开始压,复杂长任务 50k–100k;别等满了才动手
最后一道闸
Claude Code 的自动压缩大约在有效窗口 98% 处兜底,那是兜底,不是常规策略
压缩后自检
让模型基于新上下文复述任务目标和当前约束,不一致就回滚或补注入 —— 这步很便宜
生态(仅作了解)
Letta(MemGPT 团队)、Mem0、Zep / Graphiti(时间感知图谱)、LangGraph / LangMem
长任务的中间产物写文件、上下文里只留路径,比什么压缩策略都有效。反过来,记忆也不是越多越好 —— 检索出三条互相矛盾的「用户偏好」,比没有记忆更糟。

面试视角

没被压缩坑过,想不到「丢了关键决定」
追到第五问就踩缓存:怎么设计 → 快满了 → 丢了东西 → 怎么知道丢了 → 成本影响

  1. 先给四层框架并强调四层的生命周期和失效模式不同
  2. 讲压缩先讲分层预算再讲三种手段的组合,别一上来就说「我们用摘要」
  3. 主动抛 MUST-PRESERVE关键状态从结构化 state 重渲染,不靠摘要器
  4. 补一句成本视角「压缩会打穿 prompt 缓存,所以不敢压太频繁」
  5. 有故事就讲故事压缩后行为改变的事故,是最好的素材
只读过:这些回答会暴露你
  • 只说「短期记忆放上下文,长期记忆放向量库」,再无细节
  • 认为压缩就是「让模型总结一下」
  • 说「上下文满了就压缩」,不知道该提前压
  • 认为记忆检索和 RAG 完全一样
  • 从没考虑过压缩会丢东西,更没想过丢的可能是约束
真做过:这些细节骗不了人
  • 给出分层上下文预算,并说清哪一层绝不可压
  • 结构裁剪优先于摘要,说得出保留最近几组工具结果这类具体策略
  • 知道触发点比想象中早,并提到压缩打穿 prompt 缓存的成本影响
  • 讲得出写入策略、冲突覆盖、遗忘机制这三件 RAG 不需要的事
  • 有 MUST-PRESERVE 清单,尤其提到「已经失败过的路径」要保留
Q3-13 几乎是专门用来筛「真跑过长任务」的人的。很少有人主动说的一个动作:压缩后让模型复述一遍任务目标和当前约束,不一致就回滚或补注入 —— 它很便宜,能拦住开篇那类事故。

面试官怎么问

Q3-12(记忆系统怎么设计)是一二面常规题。Q3-13(压缩怎么选、丢了关键决定怎么办)是二面的追问重灾区——它几乎是专门用来筛"真跑过长任务"的人的,因为没被压缩坑过的人根本想不到"丢了关键决定"是个问题。Q3-14(跨会话记忆 vs RAG)考的是概念辨析的清晰度。

连环追问路径:怎么设计记忆 → 上下文快满了怎么办 → 摘要丢了东西怎么办 → 那你怎么知道丢了 → 压缩对成本的影响是什么(这一问踩缓存)。

答题结构建议

  1. 先给四层框架,并强调四层的生命周期和失效模式不同。
  2. 讲压缩时先讲分层预算,再讲三种手段的组合,不要一上来就说"我们用摘要"。
  3. 主动抛出 MUST-PRESERVE 清单,并说明关键状态是从结构化 state 重渲染而不是靠摘要器保留。
  4. 加一句成本视角:"压缩会打穿 prompt 缓存,所以我们不敢压太频繁,阈值是实测出来的。"
  5. 有故事就讲故事——开篇那类"压缩后行为改变"的事故是最好的素材。

分水岭信号

只读过资料的回答

  • 只说"短期记忆放上下文,长期记忆放向量库",再无细节。
  • 认为压缩就是"让模型总结一下"。
  • 说"上下文满了就压缩",不知道该提前压。
  • 认为记忆检索和 RAG 完全一样。
  • 从没考虑过压缩会丢东西,更没想过丢的可能是约束。

真做过的回答

  • 给出分层上下文预算,并说清哪层绝不可压。
  • 提到结构裁剪优先于摘要,能说出保留最近几组工具结果这类具体策略。
  • MUST-PRESERVE 清单,尤其提到"已经失败过的路径"要保留。
  • 提到压缩打穿 prompt 缓存的成本影响。
  • 提到压缩后自检 / 复述校验这种验证手段。
  • 讲记忆时能说出写入策略、冲突覆盖、遗忘机制——这三点是记忆区别于 RAG 的核心。
  • 提到把状态外置到文件系统,而不是死磕上下文。

小结与延伸

  • 记忆分四层:工作 / 情节 / 语义 / 程序,生命周期与失效模式各不相同。
  • 上下文要按层做预算:规则层不可压,工具层最先清,审计层根本不进上下文。
  • 压缩三手段组合使用;触发要提前,不要等满;压缩会打穿 prompt 缓存。
  • 关键状态从结构化 state 重渲染,不要交给摘要器——这是"丢了关键决定"的根治法。
  • 跨会话记忆比 RAG 多三件事:写什么、怎么更新冲突、什么时候忘。

延伸:压缩抹掉安全约束的问题,在架构上的正解是把约束放进护栏层而不是上下文,见 T3-10。上下文越长越钝的机制见 T1-12,prompt 缓存机制见 T1-4。多 Agent 场景下"用子 Agent 做上下文隔离"其实是另一种记忆管理手段,见 T3-8

继续深入

本篇归属第 3 章「Agent 开发」,去做这一章的题