三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Agent 智能体的核心技术不是工具——而是上下文管理

Agent 智能体的核心技术不是工具——而是上下文管理

为什么压缩是 Agent 系统的"元机制",而工具只是确定性的基础设施


一、一个被误导的行业焦点

过去两年,Agent 框架的竞赛似乎围绕着一个指标展开:谁集成了更多工具。从代码解释器到浏览器控制,从数据库查询到图像生成,工具数量成了衡量 Agent "能力"的显性标准。

但这种视角忽略了一个根本事实:工具调用本身是确定性的read_file读第 35 行就是第 35 行,grep匹配到的结果就是那些结果,执行bash命令的返回码不会撒谎。工具是基础设施,是手脚,是感官——它们本身不产生智能。

真正让 Agent 从"单次问答"进化为"长期工作"的,是上下文管理。而在上下文管理的所有技术中,压缩(Compaction)是唯一的元机制——它决定 Agent 记得什么、忘记什么,从而决定 Agent 能工作多久、工作质量如何。


二、上下文管理:Agent 的"操作系统"

如果把 Agent 比作计算机,上下文窗口就是 RAM——有限、昂贵、高速。文件系统和向量数据库是磁盘——无限、廉价、慢速。Agent 的长期工作能力,不取决于它有多少工具,而取决于它的"操作系统"如何管理这块 RAM。

2.1 没有上下文管理,Agent 是金鱼

一个 128K 上下文的 Agent,面对一个 10MB 的代码仓库,如果不做上下文管理,它只能:

  • 读几个文件就触顶
  • 忘记用户最初的约束
  • 重复读取同一个函数
  • 在循环中耗尽 token 预算

工具再多也没用——它根本"记不住"自己用过什么工具。

2.2 上下文管理的七层技术栈

业界处理上下文超限的方法可以归纳为七层:

层级技术作用域确定性
L1硬截断(Truncate)工具返回时100% 确定
L2分页读取(Pagination)工具设计时100% 确定
L3去重(Deduplication)工具返回时100% 确定
L4引用替换(Reference)轮次内100% 确定
L5外化记忆(Offload)跨轮次100% 确定
L6结构化压缩(Compaction)跨轮次/轮次内概率性
L7上下文重置(Reset)跨轮次100% 确定

L1-L5 和 L7 都是工程手段,是确定性的基础设施。只有 L6——结构化压缩——是智能行为,因为它需要判断"什么重要、什么可以丢"。


三、压缩:唯一的"元机制"

3.1 为什么压缩是"元"的

工具调用修改的是外部世界(文件系统、数据库、API),压缩修改的是 LLM 自己的"大脑状态"。

  • 工具调用错了→ 系统返回错误,LLM 收到反馈,下一轮回正。这是可逆的。
  • 压缩错了→ 关键信息被静默丢弃,LLM 真诚地相信自己记得的是对的,然后基于错误的记忆继续走向深渊。这是不可逆的。

压缩是 Agent 系统中唯一一个 LLM 修改自己未来输入上下文的环节。它像人脑的海马体——在睡眠时决定什么进入长期记忆,什么被遗忘。海马体坏了,再聪明的大脑也会变成痴呆。

3.2 压缩失败的复利效应

工具调用的错误是单点失败:

read_file 报错 → 重试 → 成功 → 继续

压缩错误是复利失败:

Round 1: 压缩时丢了"用户说不要用全局变量" Round 2: Agent 用了全局变量(约束已丢) Round 3: 压缩时丢了"全局变量已被使用" Round 4: Agent 在全局变量上继续堆代码 ... Round N: 代码变成 spaghetti,Agent 不知道为什么会这样

每一轮压缩都在前一轮的"失真记忆"上继续失真。最终,上下文与真实世界严重脱节,而 LLM 对此毫无察觉。


四、架构分界:自主域 vs 控制域

理解 Agent 系统,必须划清一条界线:

LLM 自主域:工具选什么、参数怎么填、先读后改还是先 grep——这是 LLM 的"思维自由",系统只提示,不控制。
算法控制域:压缩怎么做、什么能丢什么不能丢、丢完后对不对——这是系统的"工程责任",必须算法级保障。

4.1 工具调用属于自主域

LLM 决定调用read_file还是grep,决定offset=35还是offset=230,这是它的 reasoning 能力。系统不应该干预——即使它选错了,错误也是显式的:

LLM: read_file("auth.ts", offset=99999) → 越界 系统: 返回错误 "offset out of range" LLM: 重新计算,read_file("auth.ts", offset=1)

自主域允许试错,因为错误可被感知、被反馈、被修正。

4.2 压缩必须属于控制域

压缩不能交给 LLM 自由发挥,因为它的失败是静默的、不可逆的

一个正确的压缩系统应该这样工作:

  1. 算法决定丢什么:系统按硬规则分类消息(不可丢 / 可摘要 / 可丢弃)
  2. LLM 只负责填充:按给定的 Schema,把"可摘要"的消息提炼成结构化摘要
  3. 算法校验结果:检查不可丢项是否还在、修改过的文件是否保留、未解决的错误是否丢失
  4. 校验失败则阻断:不是让 LLM “重新试试”,而是回退到 checkpoint 或拒绝压缩

五、压缩的工程实现:三层防线

5.1 第一层:硬规则(系统决定,LLM 不可违背)

COMPACTION_RULES={"immutable":[# 绝对不可丢弃"user_constraints_last_5_turns","files_modified_in_this_session","unresolved_errors","unresolved_contradictions"],"summarizable":[# 可摘要,但路径/行号必须保留"files_read_but_not_modified","tool_outputs_already_consumed"],"droppable":[# 可直接丢弃"file_unchanged_records","superseded_code_versions"]}

这些规则不是 prompt 里的"建议",而是系统对消息数组的直接操作。LLM 的权限边界非常清晰:它可以决定key_findings怎么写,但不能决定files_modified是否保留。

5.2 第二层:Schema 填充(LLM 执行,结构约束)

LLM 的任务不是"自由写摘要",而是按算法给定的输入,生成结构化输出

{"preserved":[{"type":"file_modified","path":"src/auth.ts","lines":[35,50]},{"type":"user_constraint","text":"不要用全局变量"}],"summarized":{"files_read":[{"path":"src/auth.ts","key_findings":"实现了登录逻辑","lines":[35,230]}]}}

Schema 强制保留定位信息(路径、行号),只压缩语义内容(代码细节)。这避免了"摘要写得很通顺,但丢了关键行号"的问题。

5.3 第三层:一致性校验(算法验证,不经过 LLM)

defverify_compaction(original,compressed):# 修改过的文件不能丢modified=get_modified_files(original)assertall(fincompressed.files_writtenforfinmodified)# 未解决的错误不能丢unresolved=get_unresolved_errors(original)assertall(eincompressed.issues.errorsforeinunresolved)# 不能产生幻觉(新信息必须能在原文中找到)forfindingincompressed.summarized.files_read:assertfinding.pathinoriginal_file_paths

校验失败的处理不是"让 LLM 重新压缩",而是回退到更保守的策略拒绝压缩直接报错。因为压缩是单向门,一旦错了就没有挽回余地。


六、主流项目的佐证

这个论点不是理论推演,各主流项目的架构选择都在印证它:

Claude Code:压缩是黑盒,但行为暴露硬规则

Claude Code 的自动 compaction 不可定制,但从其可观测行为可以反推出明确的优先级:

  • P0(绝不丢弃)CLAUDE.md、最近修改的文件路径、未解决的错误
  • P1(尽量保留):最近 3-5 轮对话、用户明确约束
  • P2(可丢弃):已消费的 Read 结果、File unchanged记录
  • P3(优先丢弃):早期探索路径、已解决的错误、被覆盖的代码版本

Anthropic 把压缩策略硬编码,说明他们认为这太重要,不能交给 LLM 随意决定。

CoordClaw:绕过压缩,用外化替代

CoordClaw 的选择更极端——如果压缩太危险,那就避免需要压缩

  • 一轮生命周期极短(T1-T5 标准动作),自然不需要轮次内压缩
  • 跨轮次不保留对话历史,只保留工作日志
  • 工作日志是确定性的结构化输出,不是 LLM 生成的摘要

这相当于把"压缩"这个概率性问题,转化成了"结构化输出"这个确定性问题。

OpenCode:把压缩交给社区探索

OpenCode 提供experimental.session.compacting钩子,但默认不实现。这暴露了一个事实:Even 核心团队也认为"最佳压缩策略"还没有共识。社区插件(DCP、mnemosyne)各自探索不同的压缩方案,恰恰说明压缩是 Agent 系统中最开放、最关键的问题域。


七、给架构师的结论

设计 Agent 系统时,请遵循这条公理:

凡是不可逆的操作,必须落在算法控制域;凡是可逆的操作,可以交给 LLM 自主域。

操作可逆性所属域
工具调用(读/写/搜)✅ 可逆(可重试/可回滚)自主域
代码编辑✅ 可逆(git 可恢复)自主域
任务拆分✅ 可逆(可重新规划)自主域
上下文压缩不可逆(原始信息已丢)控制域
记忆外化不可逆(写入即事实)控制域

工具设计是确定性的基础设施——它解决"能不能做"的问题。工具调用是 LLM 的自主行为——它解决"怎么做"的问题。但压缩是 Agent 的元认知机制——它解决"还记得什么"的问题。

一个 Agent 可以只有三个工具(读、写、搜),只要它的压缩策略正确,就能维护百万行代码。反之,一个 Agent 有三十个工具,如果压缩策略错误,读五个文件就会忘记用户是谁。

Prompt 指导思维,算法保障底线。压缩就是底线。

← 返回列表