核心判断: 上下文窗口是 RAM,不是硬盘。窗口越大≠效果越好------研究里叫 context rot (上下文腐化):token 越多,模型对中间信息的召回精度越下降。所以主流 Agent 的工程目标不是"塞下更多",而是每一步只塞最小的高信号 token 集。这篇文档拆的就是这件事在 Claude Code、Cline、Aider、LangGraph、OpenAI 这些产品里具体怎么落地。
暂时无法在飞书文档外展示此内容
一、为什么上下文管理是 Agent 工程的第一问题
普通聊天是一问一答,上下文一两轮就清空。Agent 是循环------模型自己调工具、看结果、再调下一个,一个任务跑几十上百轮。每轮都产生新的 token:用户消息、模型推理、工具调用 JSON、工具返回结果。窗口很快就会撑满。
撑满之后不是简单的"报错",而是四种隐性失败,每一种都会让 Agent 悄悄变差:
| 失败模式 | 含义 | 典型症状 |
|---|---|---|
| Context Poisoning 上下文中毒 | 早期一次幻觉被塞进了历史,后面所有轮次都基于这个错误前提推理。 | Agent 越跑越偏,跟用户说的事实对不上。 |
| Context Distraction 上下文分心 | 窗口里塞了太多无关信息,模型注意力被稀释,抓不住重点。 | 明明给了正确线索,模型却忽略了。 |
| Context Confusion 上下文混淆 | 历史里有多条相似但不同的信息,模型混淆了哪条是当前任务要用的。 | 用了上一个任务的文件路径/参数。 |
| Context Clash 上下文冲突 | 历史里两条信息互相矛盾(用户先说 A 后说 B),模型不知道听谁的。 | 输出前后矛盾,或莫名其妙取了旧值。 |
叠加 context rot(长窗口召回精度自然下降)和成本/延迟随 token 线性上涨,结论很直接:不管窗口多大(128k、200k、1M),都必须主动做上下文管理。Anthropic 自己的说法是------"把上下文当成有限资源,而不是可以无限灌的桶"。
二、一个典型 Agent 的上下文到底由什么组成
把一次 LLM 调用的输入拆开看,按顺序通常是这样:
| 组成部分 | 内容 | 是否跨轮变化 | 典型占比 |
|---|---|---|---|
| System Prompt | 角色、行为准则、输出格式、拒绝策略、工具用法示例。 | 基本不变 | 5%~10% |
| 工具定义 | 所有可用工具的 name + JSON Schema + description。 | 基本不变(除非动态增减工具) | 5%~15% |
| 长期记忆 / 项目文件 | CLAUDE.md、rules 文件、用户画像、关键事实笔记。 | 偶尔更新 | 3%~8% |
| Message History | 对话历史:用户/助手/工具角色的消息列表。 | 每轮追加 | 20%~40% |
| Tool Results | 最近几次工具调用的返回结果(文件内容、搜索结果、SQL 输出)。 | 每轮追加 | 15%~30% |
| 当前用户消息 | 这一轮用户的输入。 | 这一轮才有 | 1%~5% |
关键观察:真正每轮变化的只是后 50%~70% 。前面那 30%~40%(system + tools + memory file)在多轮之间是固定的------这就是 Prefix Cache 的物理基础,后面会专门讲。
上下文管理的四件事------Write / Select / Compress / Isolate------本质就是在管这张表里的每一行:哪些要预加载、哪些要按需取、哪些要压掉、哪些要拆出去。
三、策略一:Write ------ 把信息写到窗口外面
核心思想:窗口里只放"这一步必须用的",其他信息写到外部存储(文件、数据库、state object),需要时再用工具读回来。
3.1 Scratchpad(草稿纸)
让 Agent 自己维护一个工作笔记,跨轮持久化。常见实现:
- 文件即笔记 :Claude Code 的 TODO list、项目根目录的
NOTES.md/progress.md。Agent 用 Edit/Write 工具更新,下一轮用 Read 读回来。 - State object 字段 :LangGraph 的 state schema 里加一个
notes字段,每轮由模型写入、系统自动持久化,不用工具调用。 - 专门的 Memory 工具:Anthropic 在 Sonnet 4.5 上线了 memory 工具(beta),就是把"存笔记/读笔记"抽象成两个 function call,模型自己决定何时写、何时读。
Anthropic 拿 Claude 玩 Pokémon 做过实验:不开记忆,跑几百步就忘自己在干嘛;开了结构化笔记,Agent 自己在笔记里维护"已探索区域地图 / 已解锁道具 / 战斗策略",跨几千步还能连贯推进。
3.2 Procedural Memory(程序性记忆):CLAUDE.md 这一类
不是事实,而是"做事的规则"。典型形式:
- Claude Code 的
CLAUDE.md(项目根目录)和用户级~/.claude/CLAUDE.md。 - Cursor 的
.cursorrules、Windsurf 的.windsurfrules。 - Cline 的 Memory Bank:在项目里建一组 markdown(projectBrief.md、techContext.md、productContext.md 等),每次启动自动加载。
这些文件每轮都被塞进上下文(属于"长期记忆"那一段),所以要克制------它们是 system prompt 的延伸,不是日志。Anthropic 的建议是"minimal but complete":够指导行为,又不冗余。
3.3 Long-term Memory(长期记忆):跨会话的用户画像
跨会话记住"用户是谁、偏好什么、之前做过什么"。专门做这一层的产品:
| 产品 | 核心机制 | 写入/召回方式 |
|---|---|---|
| Mem0 | 从对话里抽事实,自动去重、打分、存向量库。 | 用户和助手每轮对话后异步抽 memory;召回用 embedding + 时间衰减。 |
| Letta (MemGPT) | 把 LLM 当 CPU,显式划分 main context(RAM)和 external context(磁盘),模型自己决定 page in/page out。 | 通过 core_memory(窗口内)和 archival_memory(外部)两个工具手动管理。 |
| Zep | 时序知识图谱,把记忆建成图(实体 + 关系 + 时间)。 | 不是简单向量检索,而是图查询------能回答"用户上次说的那个项目用的什么技术栈"。 |
| ChatGPT 自动记忆 | 对话中自动识别值得记住的事实,存到用户级 memory。 | 召回靠 embedding;问题是会误召回(Simon Willison 举过例子:自己要求生成图片,结果模型把"他在哪个城市"也从记忆里拉进去了)。 |
工程经验:写入要异步 (不要让用户等抽 memory);召回要按用户隔离(A 的偏好不能给 B 用);过期事实要有 TTL 或被新事实覆盖。
四、策略二:Select ------ 每轮只选相关的进来
Write 解决了"存哪儿",Select 解决"每轮拿什么"。不是所有信息都该进窗口。
4.1 预加载 vs 即时加载
| 策略 | 做法 | 适用 |
|---|---|---|
| 预加载 (eager) | 启动时就把 system prompt、工具定义、CLAUDE.md、最近一次任务状态塞进窗口。 | 小项目、规则稳定、首轮延迟不敏感。 |
| 即时加载(lazy / just-in-time ) | 窗口里只放轻量标识符(文件路径、URL、query),模型用 Read/grep/HTTP 工具按需取。 | 大代码库、长文档、动态数据。Claude Code 就是这种:CLAUDE.md 预加载,其他文件靠 glob + grep + Read 现取。 |
Anthropic 把后者叫 "just-in-time context":别预计算塞进来,给模型工具让它自己导航。好处是不用维护复杂的索引,坏处是多了一轮工具往返。生产里通常是混合:稳定的规则预加载,动态的内容即时取。
4.2 RAG:按相关度检索
知识库大到塞不进窗口,就做检索。工程细节:
- Chunking:按语义边界切(代码用 AST 切函数/类,文档按段落 + overlap),不要按固定字符数硬切。
- 混合检索:embedding(语义)+ BM25(关键词)两路召回,再用 cross-encoder re-rank 取 Top-K。纯 embedding 在代码库上不可靠(Windsurf 工程博客原话)。
- 上下文拼接模板 :召回结果不是直接拼进 history,而是包成一个
<context>...块放在用户消息前面,并标注来源,方便模型区分"这是检索来的参考"还是"用户原话"。
4.3 工具 RAG:工具太多时也要选
当工具数量超过 ~20 个,工具定义本身就能吃掉 10k+ token,而且模型会因为描述相似而选错工具。做法:把所有工具描述做 embedding,每轮根据用户消息语义检索 Top-K 个相关工具,只把这几个塞进 system。LangGraph 的 BigTool 库就是干这个的,论文里说工具选择准确率提升 ~3 倍。
五、策略三:Compress ------ 窗口要爆时压掉
前面是预防,Compress 是兜底。触发时机通常是"窗口用了 80%~95%"。
5.1 Tool Result Clearing(最轻量)
最安全的压缩:不改对话,只把旧工具结果替换成占位符。比如第 5 轮那个 grep 结果,到第 30 轮已经没用了,换成:
[Tool result cleared: grep returned 12 matches in src/; see original output at turn 5]
Anthropic 在 Claude Developer Platform 上线的 "tool result clearing" 就是这个------不动模型行为,只省 token。这是第一档该做的事。
5.2 Auto-Compact(全轨迹摘要)
窗口接近满时(Claude Code 是 95%),把整个对话历史喂给模型,让它输出一段摘要,然后用"摘要 + 最近 N 轮原始消息"重启一个新窗口。
关键是摘要 prompt 里要明确保留什么、丢弃什么。Claude Code 的做法:
- 保留:架构决策、未解决的 bug、实现细节、用户偏好、当前任务目标。
- 丢弃:冗余的工具输出、重复的推理过程、已经完成的子任务细节。
- 附加:压缩后再拼上最近访问的 5 个文件,让模型有即时上下文。
工程经验:先调 recall(确保摘要不丢关键事实),再调 precision(去掉冗余)。压缩太狠会丢"现在看起来没用、后面才发现关键"的细节。
5.3 递归 / 分层摘要
超长任务(几小时的迁移、几十轮调研)不会只压一次。做法:
- 每过 N 轮做一次局部摘要(这一段的工具调用和结果)。
- 窗口再满时,把多个局部摘要再压成一个全局摘要。
- 形成"摘要的摘要"的树状结构,类似于人类的会议纪要分层。
5.4 Trimming(硬切)
最暴力的方式:直接丢掉最旧的 K 条消息。优点是零成本、确定性;缺点是可能丢掉早期重要约束(用户第一轮说的"不要用 X 库")。工程上一般配合"系统级消息保护":system prompt 和前几条关键用户消息永远不切。
六、策略四:Isolate ------ 拆窗口,不要一个 Agent 扛所有事
有些任务本身就需要探索("帮我调研一下 A、B、C 三个方案"),让一个 Agent 在一个窗口里搜三个方案,结果就是互相干扰。拆成子 Agent 是最干净的解法。
6.1 Sub-Agent(子代理)
主 Agent 只做规划和综合,子 Agent 各拿一个独立窗口去深潜:
- 每个子 Agent 有自己的 system prompt、工具集、消息历史。
- 它可以用几万 token 去搜、去读文件、去跑命令。
- 完成后只回传 1k~2k token 的摘要给主 Agent。
Anthropic 的 multi-agent research system 论文里,这种架构在复杂调研任务上明显优于单 Agent。代价是 token 消耗可能是单 Agent 的 15 倍------所以只在并行探索有价值时用。
6.2 Sandbox(沙箱隔离大对象)
CodeAgent(HuggingFace deep researcher 用的那种):模型输出一段 Python 代码,在沙箱里执行。大对象(图片、音频、CSV、中间结果)直接作为 Python 变量留在沙箱内存里,不进 LLM 上下文。回传给模型的只有 print 出来的几行结果或变量引用。这是隔离"重数据"的标准做法。
6.3 State Schema 隔离
LangGraph 式的状态对象:schema 里有很多字段,但只有 messages 这一个字段被暴露给 LLM。其他字段(工具结果缓存、中间计算、用户画像)留在 state 里,需要时由代码决定何时注入。这样 LLM 看到的永远是干净的消息流,不会被中间状态污染。
暂时无法在飞书文档外展示此内容
七、主流产品的具体实现拆解
7.1 Claude Code(Anthropic 官方 CLI)
- 预加载 :项目根目录
CLAUDE.md+ 用户级~/.claude/CLAUDE.md+ 系统 prompt。 - 即时加载 :内置工具
Read/Glob/Grep/Bash,模型自己导航文件系统。不做 codebase 索引。 - 压缩:Auto-compact 在 95% 窗口时触发,模型输出摘要 + 最近 5 个文件重启。
- 记忆工具:Sonnet 4.5 起有专门的 memory 工具(写文件到 memory 目录)。
- 子代理:可以 spawn 子 Agent,主 Agent 只拿摘要。
7.2 Cline(VS Code 编码 Agent)
- 不做 codebase 索引:官方博客专门解释------RAG 索引在大代码库上不可靠,而且会过期。让模型用 Read/Grep 工具即时探索更准。
- Memory Bank:项目里预建一组 markdown 文件(projectBrief / techContext / productContext / activeContext),每次会话启动加载,作为程序性记忆。
- 渐进式披露:工具返回大文件时不一次性塞,而是分页、截断、显示目录树,让模型自己决定要不要继续读。
7.3 Aider(终端 pair-programming)
- Repo Map:用 tree-sitter 解析整个代码库,抽出每个文件的类/函数/方法,生成一个 symbol map。
- 重要性打分:按 import 关系、被引用次数、与当前编辑文件的距离打分,只把 top-N 的符号塞进上下文(典型几百行)。
- 好处:模型不用读整个文件就能知道"项目里有哪些函数、它们在哪",改代码时不会瞎编 API。
7.4 LangGraph(编排框架)
- 短期记忆:checkpointer 自动持久化每一步的 state(SQLite/Postgres),相当于自动 scratchpad。
- 压缩节点:在 graph 里加一个 summarization node,条件边判断"消息历史超过 N token 就走压缩分支"。
- 长期记忆:LangMem / Store API,支持 embedding 检索和按用户分库。
- 隔离:state schema 设计 + 子图(subgraph)天然隔离不同阶段的上下文。
7.5 OpenAI Responses API / Assistants
- Thread:服务端维护对话历史,客户端每次只传新增消息,不用自己管 messages 数组。
- 自动截断:超过模型上下文窗口时,服务端自动丢旧消息(具体策略不透明)。
- 内置工具:web_search / file_search / code_interpreter 都是服务端执行,大结果不会回到上下文。
7.6 长期记忆专门层:Mem0 / Letta / Zep
不自己造轮子时直接用:Mem0 适合"用户画像 + 偏好",Letta 适合"Agent 自主管理记忆",Zep 适合"需要实体关系推理"的场景。它们都是独立的 memory service,Agent 通过 API 读写,不占 LLM 上下文。
八、Prefix Cache 工程:被低估的成本杠杆
前面说过,system prompt + tools + memory file 这一段在多轮间是固定的。主流推理服务(OpenAI、Anthropic、火山方舟)都支持 Prefix Caching:这段固定前缀只算一次,后续轮次命中缓存直接复用,输入 token 价格打 1~2 折,首 token 延迟大幅下降。
工程上的硬规则:
- 固定前缀放在最前面:system prompt → tools → memory file → 最近消息。顺序不能变。
- 同一会话内不要改 system prompt / 工具列表 / thinking 配置------改一处整段缓存失效。调试期可以接受,线上必须冻结。
- 用户消息和历史放在前缀之后,这样它们变化不会影响前缀缓存命中。
- 多轮 Agent 的成本大头其实是缓存命中后的输入 token,不是输出。一个长会话不做 prefix cache,账单可能差 5~10 倍。
九、工具结果怎么处理:最容易被忽略的一块
工具结果是上下文膨胀的最大来源------一次搜索返回 10 条结果、一次 Read 读一个 500 行文件、一次 SQL 返回 1000 行。直接全塞窗口,3 轮就满。
| 工具类型 | 处理策略 |
|---|---|
| 文件读取 | 超过 ~200 行只返回目录/前 N 行 + 文件大小提示,让模型决定要不要 offset 继续读。 |
| 搜索结果 | 只返回 title + URL + snippet,不要全文;re-rank 后取 Top-5。 |
| SQL / API | 限制返回行数(LIMIT 20),返回 schema + 前几行样本,不要全量 dump。 |
| 命令输出 | stderr 全保留,stdout 截断到 ~2000 token,附"输出被截断,需要更多请用 head/tail 工具"。 |
核心原则:工具返回给 LLM 的是"索引和样本",不是"数据本身" 。数据留在工具侧,模型要更多就再调一次工具。
十、上线前的上下文管理检查清单
| 问题 | 合格标准 |
|---|---|
| 固定前缀占比多少? | system + tools + memory file 合计 ≤ 窗口的 30%,且多轮不变(prefix cache 友好)。 |
| 工具结果怎么截断? | 大结果有明确的截断策略和"继续读"的工具,不是全量塞。 |
| 窗口到 80% 时怎么办? | 有自动压缩(tool result clearing → summarization),不是硬切或报错。 |
| 压缩时保留什么? | 有明确的 summary prompt,保留任务目标、未解决问题、用户偏好;丢弃冗余工具输出。 |
| 跨会话记忆存哪? | 独立 memory service / 文件,按用户隔离,有过期策略。 |
| 工具超过 20 个怎么办? | 做工具描述语义检索,每轮只塞相关 Top-K。 |
| 长任务怎么拆? | 子 Agent 独立窗口,只回摘要;大对象走 sandbox 不进上下文。 |
| 怎么验证上下文管理有效? | 有离线 case 库,改压缩策略后跑回归,对比任务成功率不下降。 |
资料来源
- Anthropic: Effective context engineering for AI agents(2025-09):context rot、compaction、structured note-taking、sub-agent 三大长任务技术的官方定义。
- LangChain: Context Engineering(2025-07):Write / Select / Compress / Isolate 四策略框架,以及 Cognition、Windsurf、HuggingFace 的工程实践。
- Claude Platform: Context Editing (Beta):服务端自动管理对话上下文的 API。
- Cline: How to Think About Context Engineering(2025-08):编码 Agent 的上下文取舍。
- Cline: Why We Don't Index Your Codebase(2025-05):为什么编码 Agent 不做 RAG 索引。
- Aider: Repository Understanding and Context:Repo Map 的 tree-sitter 实现。
- LangChain Deep Agents: Context Engineering:Deep Agents 的 input context / runtime context / offloading / isolation 实践。
- Mem0: Long-Term Memory for Agents:生产级长期记忆的写入与召回。
本文为工程综述,具体阈值(如 95% auto-compact、Top-K 取值)来自各产品公开文档与工程博客,实际项目需按自身任务和模型窗口实测调整。