一个 Agent好不好用,常常不取决于它「会不会」,而取决于它此刻脑子里「装着什么」——也就是上下文(Context)。
上下文是 Agent 唯一的工作记忆:每一次决策,都建立在当下这一整段上下文之上——系统指令、工具定义、工具返回、检索到的资料,以及一轮轮累积的对话与轨迹。而这段记忆既有限又花钱,把它管好本身就是一门工程,业界称之为上下文工程(Context Engineering)。下面几个日常场景,都是它要面对的问题:
- 场景 1:web search / file read 返回的内容太长,系统在工具返回后做一次截断,避免 LLM 请求窗口被撑爆。
- 场景 2:agent 写完一个 python 脚本后,系统自动做语法检查(LSP),有错就把错误信息拼回工具结果,让 agent 自行修改。
- 场景 3:系统在后台统计当前会话的上下文长度,超过一半时提醒 agent 采取轻度思考模式,超过 90% 时自动触发压缩以腾出窗口。
这三个场景面对的问题、用的手段各不相同,但共同的本质,都是在管理Agent输入给 LLM 的上下文。
5.1 上下文窗口的物理性质
我们首先要清楚上下文窗口本身受哪些物理约束所影响:
长度上限:每个模型支持的 token 输入输出总和都有硬上限,从最早的 1024\8196 到现在的 256K、1M,虽然上下文窗口已经增长到了一个客观的量级,但任务的复杂程度增长当然超过了上下文窗口的增幅。一旦输入过长,轻则输出被截断导致输出无效,重则报错导致流程崩溃。
计算复杂度:由于在 Transfomer 中 self-attention 机制让每个 token 都与其它 token 做注意力计算,因此 Attention 是 O(n2) 的计算复杂度。这意味着:
- 当输入序列长度翻倍时,计算量 x4
- 上下文窗口越长,推理越慢越贵(有工程手段可以优化这个问题,但不治本,后文再讲)
上下文衰减(Context Rot): 一篇来自 Anthropic 的 context engineering 文章中阐述了这个现象:
as the number of tokens in the context window increases, the model's ability to accurately recall information from that context decreases. from Anthropic · context engineering
通俗来讲,就是塞得下不等于看得清。即使没超过上下文窗口硬上限,大模型也会变笨。通常表现为:
- 长 prompt 中间的信息被忽略
- 后半段反复幻觉
- 工具名叫错、漏参数
Attention Sink:Agent 训练数据中的 prompt 开头通常带 system prompt,模型学到把开头的 token 注意力锁定。这意味着 prompt 开头位置是黄金注意力区。而实际上,一段较长的上下文中,开头和结尾往往对输出的约束力更强,中间的部分则较弱。这个问题在长上下文场景下尤为突出。
把上下文窗口想成一张办公桌面,也就是 Agent 的“工作记忆”:
所以“塞得下”从来不是目标——桌面再大,堆满了照样找不到东西。Harness 要做的,就是帮 Agent 随手把桌面收拾干净。
5.2 记忆与压缩
上下文工程是管理Agent送给LLM的的上下文内容的专业范畴,凡事涉及到上下文的组织、编排、CRUD等等的工程都是上下文工程要面对的问题。我们以当前一个非常热门的Agent框架 hermes-agent,看看它内部是如何实现上下文工程中最常见的两个技术环节:记忆(memory)与上下文压缩(compression)。它最值得学的一点是:记忆和压缩不是两件事,而是同一条流水线的两端。
先看它怎么组织上下文
在讲记忆和压缩之前,先看 hermes 每次调用时把上下文排成什么样。它的载荷分两块:一个 messages 数组 + 一个顶层 tools 参数。
<memory-context> 就挂这里),绝不改 system 以保前缀缓存;工具走原生 tools 字段不塞文本。配合 system_and_3 的 4 个缓存断点,静态前缀反复命中、只有新增尾部走全价。记忆 Memory
- 文件优先,外部可插拔。内置记忆就是两个 Markdown 文件——
MEMORY.md(agent 自己的经验)与USER.md(用户画像),没有数据库、没有向量检索。需要语义检索时,才挂一个外部 provider(Honcho / Mem0 / Hindsight…),且同一时刻只允许挂一个,以免工具定义把上下文撑大。 - 写,靠模型自主。模型通过一个
memory工具自己决定「记什么」(add / replace / remove),而非规则硬塞;每轮结束还会在后台线程异步把这一轮同步给外部 provider,不阻塞主流程。 - 读,靠「围栏注入」而非检索拼接。召回的记忆文本被包进一个
<memory-context>权威块,只在发起 API 调用的那一刻追加到当前用户消息上——不改写原始对话、不做持久化,还附一句「这是召回的记忆、不是新的用户输入」,专门防提示注入。
压缩 Compression
- 客户端自建的「保头 + 保尾 + 中段摘要」。不调用厂商的服务端压缩接口,全在本地做:上下文超过阈值(约「有效窗口」的 50%,已扣掉输出预留)就触发——先无损预清理(旧工具返回换成一行摘要、同一文件的重复读取只留最新),再只把中间段交给一个(可以更小的)摘要模型压成结构化快照(目标 / 已完成动作 / 活动状态 / 关键决策 / 相关文件…),而开头(system)与最近约 20% 窗口逐字保留,且绝不切断成对的 tool_call / tool_result。
- 压缩不是「丢弃」,是「软归档」。被压掉的旧对话不会真删:在 SQLite 里标记为
active=0,仍可全文检索(FTS)、仍可恢复;摘要作为新的active=1消息插入,会话 id 原地不变。 - 压缩与记忆共用一条流水线。压缩真正落地前,会先触发一个
on_pre_compress钩子把要点抽进记忆,然后才归档旧对话、写入摘要。所以一次压缩 = 抽记忆 + 归档 + 摘要 三合一——这正是「上下文工程」最精炼的一幕。
<memory-context>
围栏块注入、绝不改写原始消息;一旦上下文触阈值,压缩沿「预清理 → 抽记忆 → 中段摘要 → 软归档」四步走完,旧对话软归档进
SQLite(可搜可恢复)而非丢弃——记忆与压缩共用这同一条流水线。技能(skills)是可以自策展的记忆。
/learn 让 agent 自己写 SKILL.md 沉淀方法论;一个 Curator 每隔约 7 天用辅助模型 fork 出一个 agent 在后台整理技能(合并 / 归档 /
置顶,永不删除、只归档),全程不碰主会话的 prompt cache。
5.3 小结与思考
- 上下文是 Agent 的工作记忆——每一次决策都建立在当下这段上下文之上;它有限又花钱,把它管好本身就是上下文工程。
- 注意力是 O(n²)——序列长度翻倍、计算量翻四倍,窗口越长推理越慢越贵。
- Context Rot——还没撞到窗口硬上限,召回准确率就已一路下滑:塞得下 ≠ 看得清。
- Attention Sink——开头(system,黄金区)与结尾权重最高,重要的东西别埋在中间。
- 记忆与压缩是同一条流水线——hermes-agent 的一次压缩 = 抽记忆 + 软归档 + 中段摘要三合一,共用一套 SessionDB。
- 记忆:文件优先、围栏注入——内置就是 MEMORY.md / USER.md,召回内容以
<memory-context>只在调用时注入、绝不改写原始消息。 - 压缩:保头 + 保尾 + 中段摘要,且软归档不丢——本地约 50% 阈值触发;旧对话
active=0仍可检索、可恢复。
- 有哪些方式可以实现 Agent 记忆(至少设计 6 种以上的机制)
- 有哪些方式可以实现 Agent 上下文压缩
- 理解 SummarizationDrift,有哪些方法可以缓解该现象