1. 为什么上下文工程成了 AI 编码代理的胜负手
1.1 从一次真实的翻车现场说起
去年冬天我接手了一个遗留的 Java 微服务项目,代码量大概在 12 万行左右,模块之间耦合严重,注释覆盖率不到 15%。当时我信心满满地把整个项目丢给一个主流的 AI 编码代理,想着让它帮我梳理一下订单模块的调用链路。结果呢?代理在第三轮对话之后就开始胡言乱语,把OrderService和PaymentService的方法签名混在一起,甚至凭空捏造了一个根本不存在的RefundHandler类。
我一开始以为是模型能力不行,换了个更强的模型,问题依旧。后来我把对话历史打印出来一看,傻眼了——上下文窗口里塞满了前几轮的文件内容、工具调用结果和我的追问,真正关键的当前任务描述被挤到了最边缘的位置,模型根本"看不见"。
这就是典型的上下文污染问题。AI 编码代理和普通聊天机器人最大的区别在于:它需要在一个持续的任务流中反复读写代码、调用工具、维护状态,上下文不是一次性的,而是像滚雪球一样越滚越大。如果不做工程化的管理,再强的模型也会被自己的历史拖垮。
上下文工程(Context Engineering)这个词这两年才逐渐被行业重视,但它的核心思想其实很朴素:在有限的上下文窗口里,动态地、有策略地放置"此刻最该被模型看到的信息"。它和提示词工程(Prompt Engineering)不是一回事——提示词工程关注的是"怎么问",上下文工程关注的是"给模型看什么、看多少、什么时候看、什么时候忘"。
这篇文章我会围绕两个具体的抓手展开:一个是ChatMemory 的滑动窗口机制,另一个是Context-mode MCP 的上下文优化策略。前者解决的是"对话历史怎么裁剪",后者解决的是"工具返回的内容怎么压缩和路由"。两者配合起来,基本能覆盖一个 AI 编码代理 80% 的上下文管理需求。
适合谁看?如果你正在做 AI 编码代理的开发、正在用 MCP 协议对接各种工具、或者单纯被"代理聊着聊着就失忆"的问题困扰过,这篇内容应该能给你一些可以直接抄作业的思路。
1.2 上下文窗口不是越大越好
很多人有个误区:既然模型支持 128K 甚至 200K 的上下文,那我全塞进去不就行了?
我实测过,在一个 128K 窗口的模型上,当上下文填充到 60% 以上时,模型对中间位置信息的召回率会明显下降。这个现象在业界被称为"迷失在中间"(Lost in the Middle)。也就是说,上下文长度和有效信息密度是两回事。你塞了 10 万 token 进去,模型真正能稳定利用的可能只有前 2 万和后 1 万。
更现实的问题是成本和延迟。每一次工具调用都要把完整上下文重新送进模型,token 消耗是线性增长的,而响应延迟会随着上下文变长而显著上升。在一个需要几十轮工具调用的编码任务里,不做上下文管理的代理,token 成本可能是优化后的 5 到 10 倍。
所以上下文工程的目标从来不是"塞满窗口",而是在正确的时间,把正确的信息,以正确的粒度,放到正确的位置。这句话听起来像废话,但真正落地的时候,每一个"正确"都需要具体的机制来支撑。
2. ChatMemory 滑动窗口:对话历史的裁剪艺术
2.1 ChatMemory 到底在管什么
ChatMemory 这个概念在不同的框架里叫法不太一样,有的叫 ConversationBuffer、有的叫 MessageHistory,但本质都是同一件事:维护一个有序的消息列表,并在每次调用模型前决定哪些消息进入上下文。
一个典型的 ChatMemory 里存的消息类型包括:
- System Message:系统提示词,定义代理的角色、能力边界、输出格式要求
- Human Message:用户的输入,包括原始任务描述和后续追问
- AI Message:模型的回复,可能包含思考过程、工具调用请求
- Tool Message:工具执行的结果,比如读文件返回的内容、命令执行的输出
这四类消息的"生命周期"是完全不同的。System Message 通常全程保留,Human Message 里的原始任务描述需要长期保留,而 Tool Message 里的文件内容往往只在当前这一步有用,用完就该丢。
如果只是简单地按时间顺序保留最近 N 条消息,会出现一个很尴尬的情况:一次读大文件的操作可能产生一条 5000 token 的 Tool Message,它会把前面好几轮有价值的对话全部挤出窗口。这就是为什么朴素的滑动窗口不够用,需要更细粒度的策略。
2.2 滑动窗口的三种裁剪策略与取舍
我在实际项目里试过三种滑动窗口的实现方式,各有适用场景,下面这张表是我自己的对比总结:
| 策略 | 核心逻辑 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 固定条数窗口 | 保留最近 N 条消息 | 实现简单,行为可预测 | 忽略消息长度差异,大消息会挤爆窗口 | 短对话、工具返回内容稳定的场景 |
| Token 预算窗口 | 按 token 数从后往前填充 | 精确控制上下文大小 | 可能截断一条消息的中间部分 | 需要严格控制成本的场景 |
| 分层保留窗口 | System + 首条 Human 永久保留,其余按 token 预算裁剪 | 关键信息不丢失 | 实现复杂度较高 | 长任务、多轮工具调用的编码代理 |
我最终在编码代理里用的是分层保留窗口,因为它解决了一个核心痛点:原始任务描述不能丢。你想想,一个编码任务可能要进行 30 轮工具调用,如果第 25 轮的时候模型已经忘了用户最初要求"用 Python 3.11 的语法特性",那前面 24 轮的工作可能都要返工。
分层保留的具体做法是这样的:
class LayeredChatMemory: def __init__(self, max_tokens=8000, reserve_ratio=0.3): self.max_tokens = max_tokens self.reserve_ratio = reserve_ratio # 为最新消息预留的比例 self.system_message = None self.first_human_message = None self.recent_messages = [] def build_context(self): # 第一层:System Message 永远保留 context = [self.system_message] # 第二层:首条 Human Message 永远保留(原始任务) context.append(self.first_human_message) # 第三层:从最近的消息往前填充,直到用完预算 budget = self.max_tokens - self._count_tokens(context) recent = [] for msg in reversed(self.recent_messages): msg_tokens = self._count_tokens([msg]) if budget - msg_tokens < 0: break recent.insert(0, msg) budget -= msg_tokens return context + recent这段代码里有个细节值得说:reserve_ratio这个参数我一般设成 0.3,意思是给最新消息预留 30% 的预算。为什么要预留?因为最新的工具返回结果往往是当前决策的直接依据,如果它被裁掉了,模型就会基于过时信息做判断。这个比例不是拍脑袋定的,我试过 0.2、0.3、0.4 三档,0.3 在大多数编码任务里表现最稳。
2.3 消息摘要:让被裁掉的历史"留个念想"
滑动窗口有个天然的缺陷:被裁掉的消息就彻底消失了。但有些信息虽然不需要原文,却需要"存在过的痕迹"。比如代理前面已经读过pom.xml,知道项目用的是 Spring Boot 3.2,这个结论比文件原文重要得多。
我的做法是在裁剪之前,先对即将被裁掉的消息做一次摘要压缩。具体来说,把连续的 Tool Message 和 AI Message 打包,让模型生成一段 100 到 200 字的摘要,然后作为一条特殊的 System Message 插回上下文。
def summarize_and_evict(self, messages_to_evict): summary_prompt = """请用不超过150字总结以下对话的关键信息, 重点保留:已确认的技术栈、已修改的文件、已发现的问题、待办事项。 不要保留具体的代码内容。""" summary = self.llm.invoke(summary_prompt + format_messages(messages_to_evict)) # 作为一条带标记的系统消息插回 self.recent_messages.insert(0, SystemMessage( content=f"[历史摘要] {summary}", metadata={"type": "summary", "evicted_count": len(messages_to_evict)} ))这里有个坑我踩过:摘要本身也会消耗 token。如果每裁一次就生成一次摘要,摘要会越积越多,最后反而占满窗口。我的解决办法是给摘要也设一个上限,比如最多保留 3 条摘要,超出的旧摘要再合并成一条。这个"摘要的摘要"机制听起来有点绕,但实测下来能把长任务的上下文稳定控制在预算内。
提示:摘要生成最好用便宜的小模型来做,不要用主模型。摘要质量差一点没关系,它只是"记忆锚点",不是决策依据。
2.4 滑动窗口的触发时机:别等到满了才动手
很多人实现滑动窗口是"被动触发"的——每次调用模型前检查一下,超了就裁。这个思路能用,但不够好。
我现在的做法是主动预判:在每次工具调用返回之后,先估算一下这条 Tool Message 会占多少 token,如果加上它之后会超过预算的 80%,就提前触发一次裁剪和摘要。这样做的好处是避免"临时抱佛脚"式的裁剪——被动裁剪往往只能粗暴地砍掉最近的消息,而主动裁剪可以从容地做摘要、做分层。
估算 token 有个简单的经验公式:英文大约 4 个字符 1 个 token,中文大约 1.5 个字符 1 个 token,代码介于两者之间,大约 3 个字符 1 个 token。这个精度对于预算控制足够了,不需要上真正的 tokenizer,那样反而拖慢速度。
3. Context-mode MCP:工具返回内容的压缩与路由
3.1 MCP 协议为什么让上下文问题雪上加霜
MCP(Model Context Protocol)这两年在 AI 编码代理圈子里火得不行,它把各种工具(文件系统、浏览器、数据库、IDE)统一成一套标准接口,代理可以通过 MCP Server 调用它们。但便利的背后是新的上下文挑战:每个 MCP 工具返回的内容格式、粒度、大小都不可控。
我举个例子。用 Playwright MCP 让代理去抓一个网页,返回的可能是完整的 DOM 树,动辄几万 token;用文件系统 MCP 读一个日志文件,可能返回几千行;用数据库 MCP 查一次,可能返回一个几百行的结果集。这些内容如果原封不动塞进上下文,几轮下来窗口就爆了。
更麻烦的是,MCP 工具返回的内容里大量是噪音。DOM 树里 90% 是样式和布局信息,日志文件里大部分是重复的 INFO 级别记录,数据库结果集里可能只有几列是当前任务关心的。上下文工程在这里的核心任务就是:在工具返回结果进入上下文之前,先做一次"提纯"。
3.2 Context-mode 的三种压缩手段
我在自己的代理里实现了一套 Context-mode 处理层,夹在 MCP 工具和 ChatMemory 之间,专门负责对工具返回内容做压缩。核心手段有三种:
第一种是结构化裁剪。针对不同工具返回的格式,用不同的规则提取关键字段。比如 Playwright 返回的 DOM,我只保留可见元素的文本内容和交互属性,把 style、class 里的样式类名全部剥掉。数据库结果集只保留前 50 行和列名,超出部分用"还有 N 行未显示"代替。
第二种是语义摘要。对于非结构化的长文本(比如日志、报错堆栈),用一个小模型做摘要,只保留错误类型、关键行号、异常信息。这里的关键是摘要要保留可操作性——比如报错堆栈,行号和异常类名必须保留,因为代理后续可能要基于这些信息去定位代码。
第三种是引用替代。对于大文件,不把内容塞进上下文,而是把内容存到一个临时的"上下文存储"里,只在上下文里放一个引用 ID 和一段简短描述。代理需要看具体内容时,再通过一个专门的工具去取。这个思路有点像操作系统的虚拟内存——物理内存(上下文窗口)里只放当前活跃的页,其余的存在磁盘(外部存储)上。
class ContextModeProcessor: def __init__(self, storage, summarizer, max_inline_tokens=1500): self.storage = storage self.summarizer = summarizer self.max_inline_tokens = max_inline_tokens def process(self, tool_name, raw_result): tokens = estimate_tokens(raw_result) # 小结果直接内联 if tokens <= self.max_inline_tokens: return raw_result # 大结果走引用模式 ref_id = self.storage.put(raw_result) summary = self.summarizer.summarize(tool_name, raw_result) return f"""[工具 {tool_name} 返回内容已存储] 引用ID: {ref_id} 摘要: {summary} 如需查看完整内容,请调用 fetch_context(ref_id="{ref_id}")"""这套机制实测下来,能把工具返回内容的平均 token 占用降低 70% 以上,而且代理的任务完成率没有明显下降——因为它需要细节的时候,知道去哪里取。
3.3 上下文路由:让不同的信息去不同的地方
Context-mode 的另一个核心能力是路由。不是所有信息都该进对话上下文,有些信息更适合放在别的地方。
我把代理运行时的信息分成了三个"存储层":
- 对话上下文:当前任务相关的、需要模型直接看到的信息,容量最小,最宝贵
- 工作记忆:任务执行过程中的中间状态,比如已修改的文件列表、已执行的命令历史,通过工具按需查询
- 长期记忆:跨任务的知识,比如项目的技术栈约定、代码规范,通过检索注入
路由的规则我总结成一句话:"决策依据进上下文,执行记录进工作记忆,背景知识进长期记忆"。
举个例子,代理在重构一个函数时,"这个函数当前的实现"是决策依据,要进上下文;"之前已经改过哪几个函数"是执行记录,进工作记忆;"这个项目的命名规范"是背景知识,进长期记忆。这样分层之后,上下文里永远只有当前这一步真正需要的东西。
注意:路由规则不要写得太复杂,我见过有人搞了七八层存储,结果代理自己都搞不清信息在哪。三层足够了,再多就是过度设计。
3.4 与 ChatMemory 的协同:谁先谁后
Context-mode 和 ChatMemory 的调用顺序很关键。我的实践是:MCP 工具返回 → Context-mode 压缩 → 写入 ChatMemory → ChatMemory 裁剪 → 送入模型。
这个顺序不能反。如果先写 ChatMemory 再压缩,那 ChatMemory 里存的就是原始大内容,裁剪的时候会误伤;如果先裁剪再压缩,那压缩的时候可能已经丢失了关键信息。先压缩后裁剪,能保证 ChatMemory 里存的都是"提纯过"的内容,裁剪的粒度也更可控。
还有一个细节:Context-mode 压缩后的引用 ID 要能被 ChatMemory 识别为"可回收"的消息。当引用对应的内容被取用之后,那条引用消息就可以标记为低优先级,在裁剪时优先淘汰。这个机制能让上下文始终保持"新鲜"。
4. 实操:从零搭一套上下文优化流水线
4.1 环境准备与依赖选型
先说技术栈。我用的是 Python 3.11,主要依赖这几个库:
pip install langchain-core langchain-openai mcp tiktokenlangchain-core提供消息类型和 ChatMemory 的基础抽象langchain-openai用于调用模型(换成其他厂商的 SDK 也行)mcp是 MCP 协议的官方 Python SDKtiktoken用于精确的 token 计数(预算控制用,估算用经验公式)
模型方面,我建议主模型和辅助模型分开。主模型负责编码决策,用能力强的;辅助模型负责摘要、压缩、路由判断,用便宜快的小模型。这样成本能降下来一大截,而且辅助任务对模型能力要求不高。
4.2 核心模块的代码骨架
整个流水线分四个模块,我按数据流的方向依次说。
模块一:MCP 工具调用层。这一层负责和 MCP Server 通信,拿到原始返回结果。关键是给每个工具调用加上超时和大小限制,避免一个工具返回把整个流程卡死。
async def call_mcp_tool(server, tool_name, params, timeout=30, max_size=1_000_000): try: result = await asyncio.wait_for( server.call_tool(tool_name, params), timeout=timeout ) if len(str(result)) > max_size: result = str(result)[:max_size] + "\n[内容已截断]" return result except asyncio.TimeoutError: return f"[工具 {tool_name} 调用超时]"模块二:Context-mode 处理器。前面已经给过骨架,这里补充一下摘要提示词的设计。摘要提示词要针对不同工具类型定制,不能一套提示词打天下。
SUMMARIZE_PROMPTS = { "read_file": "总结这个文件的内容,保留:文件用途、关键类/函数名、依赖关系。不要保留具体代码。", "run_command": "总结命令执行结果,保留:退出码、错误信息、关键输出行。", "query_db": "总结查询结果,保留:列名、数据规模、异常值。", "browse_page": "总结网页内容,保留:页面主题、主要交互元素、关键文本。" }模块三:分层 ChatMemory。前面给过核心逻辑,这里补充消息优先级标记的实现。每条消息写入时打一个优先级标签,裁剪时按优先级从低到高淘汰。
PRIORITY = { "system": 100, "first_human": 90, "summary": 70, "recent_human": 60, "recent_ai": 50, "tool_result": 30, "context_ref": 20 # 引用消息优先级最低,可随时淘汰 }模块四:上下文组装与调用。最后一步把 ChatMemory 组装成模型能接受的格式,调用主模型。这里要注意消息顺序:System 在前,摘要其次,然后是保留的历史,最后是最近的消息。
4.3 参数调优:几个关键数字的来历
这套流水线里有几个参数直接决定效果,我把调优过程说一下。
max_tokens:这个取决于主模型的窗口大小。我的经验是只用窗口的 50% 到 60%。比如 128K 窗口的模型,我设成 64K 到 76K。留出的空间是给模型输出和突发的大工具返回用的。设太满会导致模型输出被截断,设太少又浪费能力。
max_inline_tokens:工具返回内容内联的阈值,我设的是 1500。这个数字是试出来的——低于 1500 的内容,做引用模式的收益(省下的 token)抵不上引用带来的额外工具调用开销;高于 1500,引用模式就开始划算了。
reserve_ratio:给最新消息预留的预算比例,0.3。前面说过,这个比例保证最新工具返回不会被裁掉。
摘要触发阈值:当被裁消息的总 token 超过 2000 时才触发摘要。太小的裁剪不值得摘要,直接丢就行。
这几个数字不是绝对的,不同模型、不同任务类型会有差异。但作为起点,它们能让你少走很多弯路。
4.4 一次完整的任务执行记录
我拿一个真实任务跑一遍,让你看看这套流水线在实际中怎么工作。任务是:"给这个 Spring Boot 项目加一个订单导出 Excel 的接口"。
第 1 轮:用户输入任务。ChatMemory 里只有 System Message 和这条 Human Message,上下文占用约 800 token。
第 2 轮:代理调用文件系统 MCP 读取项目结构。返回一个 3000 token 的目录树。Context-mode 判断超过 1500 阈值,做摘要压缩到 200 token,原文存入引用存储。上下文占用约 1100 token。
第 3 轮:代理调用 MCP 读取OrderController.java。返回 2500 token。同样压缩,摘要保留类名、方法列表、依赖注入的字段。上下文占用约 1400 token。
第 4 到 10 轮:代理陆续读取了 Service、Mapper、实体类,每轮都走压缩。到第 10 轮时,ChatMemory 里积累了 7 条摘要消息,加上原始任务和最近的对话,上下文占用约 5000 token。
第 11 轮:代理开始写代码,调用文件写入 MCP。返回结果很短("写入成功"),直接内联。此时 ChatMemory 触发了一次裁剪,把第 2 到 5 轮的摘要合并成一条"项目结构摘要",上下文占用回落到 4200 token。
第 12 到 20 轮:代理反复修改代码、运行测试。每次测试失败,返回的报错堆栈被压缩成"错误类型 + 关键行号 + 异常信息"。到第 20 轮,上下文占用稳定在 6000 token 左右。
任务结束:整个任务 20 轮工具调用,如果不用这套流水线,上下文峰值会超过 40000 token;用了之后,峰值控制在 6000 token 以内,token 成本降低约 85%,任务完成质量没有下降。
5. 常见问题与排查技巧实录
5.1 代理"失忆"了怎么办
这是最常见的问题。表现是代理突然问一个前面已经确认过的信息,或者重复执行已经做过的操作。
排查思路按这个顺序走:
- 先看 ChatMemory 的裁剪日志。是不是关键消息被裁掉了?如果是,检查优先级标记是否正确,原始任务描述有没有被标记为
first_human。 - 再看摘要质量。如果关键信息被裁掉但摘要里应该有,那就是摘要生成出了问题。检查摘要提示词是否覆盖了这类信息。
- 最后看上下文组装顺序。有时候消息都在,但顺序不对,模型也会"看不见"。System 和摘要必须在前面,最近消息在后面。
我遇到过一次诡异的情况:代理反复忘记"项目用的是 Java 17"。查了半天发现是摘要提示词里写了"不要保留具体代码",结果小模型把"Java 17"也当成代码细节给删了。后来把提示词改成"保留技术栈版本信息",问题解决。
5.2 工具返回内容被压缩后代理无法操作
这个问题的表现是代理说"我需要查看完整内容"但不知道怎么取,或者取回来之后又不会用。
根因通常是引用消息的格式不清晰。代理不知道fetch_context这个工具的存在,或者不知道引用 ID 怎么用。解决办法是在 System Message 里明确说明引用机制:
当工具返回内容被压缩为引用时,你会看到 [工具 X 返回内容已存储] 的标记。 如需查看完整内容,调用 fetch_context 工具并传入引用 ID。另外,fetch_context返回的内容也要走一遍 Context-mode 处理,避免取回来一个大内容又把上下文撑爆。这里可以设一个"取用后自动摘要"的规则。
5.3 摘要越积越多反而占满窗口
前面提过这个问题,这里给一个具体的解决参数。我设的规则是:
- 摘要消息最多保留 5 条
- 超过 5 条时,把最旧的 2 条合并成 1 条
- 合并后的摘要标记为"二级摘要",优先级比普通摘要低
- 二级摘要最多保留 2 条,超过就再合并
这样层层收敛,摘要的总量始终有上限。实测在 50 轮以上的长任务里,摘要占用的 token 稳定在 800 以内。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查动作 | 解决方向 |
|---|---|---|---|
| 代理重复问已确认信息 | 关键消息被裁 | 查裁剪日志和优先级标记 | 提升关键消息优先级 |
| 代理说看不到完整内容 | 引用机制未说明 | 查 System Message | 补充引用工具说明 |
| 上下文占用居高不下 | 摘要未收敛 | 查摘要数量和大小 | 启用摘要合并机制 |
| 工具调用超时 | MCP Server 响应慢 | 查工具调用日志 | 加超时和降级返回 |
| 代理输出被截断 | max_tokens 设太大 | 查上下文占用比例 | 降到窗口的 50%-60% |
| 摘要丢失关键信息 | 提示词覆盖不全 | 查摘要提示词 | 按工具类型定制提示词 |
5.5 几个我踩过的坑
坑一:用主模型做摘要。一开始图省事,摘要也用主模型,结果成本没降下来,延迟还上去了。后来换成小模型,摘要质量略降但完全够用。
坑二:裁剪粒度太粗。早期我是按"轮"裁剪的,一轮对话要么全留要么全丢。后来改成按消息裁剪,灵活多了。一条大 Tool Message 可以单独被压缩,不影响同一轮的其他消息。
坑三:忽略工具返回的元数据。MCP 工具返回的结果里往往带有元数据(比如文件大小、查询耗时),这些信息对代理决策有用,压缩时不能丢。我现在会在摘要里专门保留一段元数据。
坑四:上下文路由规则写太死。一开始我给每类信息都写死了去哪一层,结果遇到边界情况就卡住。后来改成"默认进工作记忆,显式标记才进上下文",灵活多了。
6. 上下文工程的边界与延伸
6.1 什么情况下这套方案不适用
说实话,这套流水线不是万能的。如果你的任务很短(少于 5 轮工具调用),上这套机制反而是过度设计,直接全量塞上下文更简单。如果任务对细节精度要求极高(比如逐行代码审查),压缩和摘要可能丢失关键信息,这时候宁可牺牲成本也要保留原文。
还有一种情况是工具返回内容本身就很小。如果你的 MCP 工具都是返回几十 token 的短结果,那 Context-mode 的压缩层基本是空转,可以关掉。
判断标准很简单:当上下文占用成为瓶颈时,才需要上下文工程。没到瓶颈就上,是给自己找麻烦。
6.2 可以继续深挖的方向
这套方案目前解决的是"单代理、单任务"的上下文管理。往深了走,还有几个方向值得探索。
多代理协作的上下文隔离。当多个代理协同工作时,每个代理的上下文应该独立管理,只通过明确的接口交换信息。这比共享一个大上下文要清晰得多,也更容易调试。
基于任务阶段的动态策略。编码任务的不同阶段(探索、设计、实现、测试)对上下文的需求是不一样的。探索阶段需要大量文件内容,实现阶段需要精确的接口定义,测试阶段需要报错信息。如果能根据阶段动态调整压缩策略,效果会更好。
上下文质量的度量。现在判断上下文管理好不好,主要靠"代理有没有出错"这种间接指标。如果能有一个直接的度量,比如"关键信息召回率",调优会更有方向。这个方向目前还没有成熟的方案,但值得关注。
6.3 我个人的一点体会
做上下文工程这两年,最大的感受是:它更像是一门"取舍"的手艺,而不是"堆料"的技术。很多人一上来就想把所有信息都留住,结果反而什么都留不住。真正有效的做法是承认上下文的稀缺性,然后逼着自己去判断"什么才是此刻真正重要的"。
这个判断能力,一部分靠机制(优先级、摘要、路由),一部分靠经验(知道什么信息在什么阶段有用)。机制可以抄,经验只能自己攒。我建议你在自己的项目里先把这套流水线跑起来,然后根据实际遇到的问题去调参数、改规则。跑上十几个真实任务之后,你对"什么该留什么该丢"的直觉就会建立起来。
最后分享一个小技巧:给代理加一个"上下文自省"的能力。让它在每次决策前,先输出一句"我当前基于哪些信息做判断"。这句话会逼着代理去检索上下文,也能让你直观地看到它到底"看见"了什么。这个技巧帮我定位过好几次上下文丢失的问题,成本几乎为零,但效果立竿见影。