第 3 章 / 共 11 章
写、选、压、隔
3.1 盘点做完了,然后呢
上一章你把 shopfront 的上下文窗口拆开数了一遍。数字大概是这样:系统提示和 CLAUDE.md 占掉一块,MCP 订单系统的工具定义占掉一块,几十轮里读进来的 src/checkout/ 文件占掉最大的一块,剩下是工具返回的 diff、报错、测试输出。
你现在知道 token 去哪了。但知道去向不等于知道该动哪里。
摆在你面前的是一堆症状,不是一堆答案。agent 忘了三十轮前定下的迁移顺序;它检索 checkout 的时候顺手捞回了 src/checkout/legacy/ 下面那批两年没动过的文件;你在第 12 轮亲口确认过的「v2 的 amount 字段单位是分」这条结论,现在被埋在几万 token 底下,模型再没引用过;每轮响应越来越慢,账单也在涨;agent 第三次改 src/api/client.ts 里同一个函数。
这些症状看起来像同一个问题——「上下文太满了」。它们不是。它们对应四种完全不同的动作。这一章给你一张表,把症状翻译成动作。
3.2 /clear 重开为什么是最差的止损
大多数人对上下文问题只有一个动作:/clear,或者干脆开个新终端重来。
这个动作有效,原因很朴素——它把窗口清空了,模型不再被噪声干扰,下一轮响应立刻变准变快。所以它给人一种「问题解决了」的错觉。
但你付出的代价是全部状态。/clear 之后:
- 迁移到第几个文件了,不知道了。
- 哪些接口已经确认过映射关系,哪些还没,不知道了。
- 上次那个
tests/checkout/refund.test.ts为什么会挂、你们讨论出的结论是什么,不知道了。 - 你在对话里口头补充过的三条团队约定,不知道了。
于是你花二十分钟把这些东西重新讲一遍。讲的过程中你会漏掉一两条,agent 又一次踩进同一个坑。你以为你在止损,其实你在周期性地把项目回滚。
盲目开新会话是同一类错误的另一种形态。它们共同的毛病是:把「上下文里有噪声」当成「上下文该被清空」。这两件事之间隔着一整套操作。噪声要清,信号要留,留的方式不是靠脑子记,是靠把它挪到窗口外面再取回来。
真正的框架是:上下文里的每一份信息,你都可以选择把它写出去、选回来、压小、或者隔开。四个动作,不是四个选项,是四个方向。
3.3 四个动作:写、选、压、隔
这四个动作是 LangChain 对现有实践的一次归纳。四个词各自都不新,归在一起才好用——它给了你一个提问的顺序。
写 Write:把信息存到窗口外面。 不需要一直摆在模型眼前的东西,写进文件、写进笔记、写进任务清单。窗口里只留一个指针。
shopfront里的样子:开一个docs/migration-progress.md,每完成一个文件就追加一行「src/checkout/cart.ts已迁移,amount字段单位=分,已确认」。这行字以后随时能读回来,而承载它的那八千 token 的对话可以安心丢掉。
选 Select:需要的时候再取回来。
不是把 src/checkout/ 整个目录塞进去,而是这一轮要改哪个文件就读哪个文件。检索是一个决定,不是一个默认动作。
shopfront里的样子:agent 要改退款逻辑,只读src/checkout/refund.ts和docs/api-migration.md里退款那一节,不读cart.ts,不读legacy/。
压 Compress:保留要点,缩减体积。 同样的信息,用更少的 token 表达。压实(compaction)是它的一种形态,把长对话高保真摘要后重新起头;手写一段「当前状态」小结也是。
shopfront里的样子:MCP 订单系统返回了一个 3000 token 的订单 JSON,而你只需要其中的status和currency。把它摘成两行再放进上下文。
隔 Isolate:把上下文切到不同的执行体里。 一个执行体一个视野。探索性的、脏的、可能产生大量中间产物的活儿,丢给子代理去做,主线只接收结论。
shopfront里的样子:「把tests/checkout/下所有引用 v1 的地方找出来」这件事,可能要翻二十个文件、几万 token。让子代理去翻,回传一份一两千 token 的清单,主对话的窗口一点没脏。
四个动作的判断顺序有个粗糙的直觉。信息该不该在窗口里?不该——写出去。该在,但现在不该?——用选。该在,但太大?——压。该在,但会污染主线?——隔。

图 3.1:左列是你能直接观察到的症状,右侧四列是写/选/压/隔。注意大多数行不是只勾一个格子——症状和动作是多对多的,矩阵给的是「先动哪个」。
3.4 症状-动作决策矩阵
这是本章你要带走的东西。左边是你在终端里能直接看到的现象,右边告诉你先动哪只手。
| 观察到的症状 | 最可能的原因 | 先做哪个动作 | 具体怎么做 | 做完怎么验证 |
|---|---|---|---|---|
| 跨轮次丢失进度:agent 忘了迁移到第几个文件、忘了已确认的字段映射 | 状态只存在于对话历史里,被后续内容挤到了注意力边缘 | 写 | 建 docs/migration-progress.md,每完成一步追加一行(文件名、结论、待办)。让 agent 每轮开头先读它 | 新开一轮,只给它这个文件,问「下一个该改哪个文件」。答对说明状态真的在文件里,不在运气里 |
检索回来一堆不相关文件:要改退款,却读进了 legacy/ 下十几个陈旧文件 | 检索范围没有约束,语义相似的干扰项被一并捞回 | 选 | 把检索改成显式路径加显式条件;在 CLAUDE.md 里写明 src/checkout/legacy/ 不参与迁移;需要时才读,不预加载整个目录 | 数这一轮读进来的文件数。每个都能说出「这一轮为什么需要它」才算合格 |
关键结论被埋在历史深处:第 12 轮确认过的「amount 单位是分」,后面再没被引用 | 中间迷失——开头和结尾注意得好,中间差 | 写 + 压 | 把这类结论从对话里提炼出来,固化进 docs/api-migration.md 的「已确认事实」小节;并在每轮末尾复述当前待办 | 隔十轮问一次「amount 的单位是什么」。答错说明它还没被真正固化 |
| 成本和延迟上涨:同样一个小改动,响应越来越慢,账单曲线在爬 | 每轮都在重放一个越来越长的输入;缓存前缀可能也被打断了 | 压 + 隔 | 触发一次压实,或手写状态小结重开;把大块探索移进子代理;检查提示前缀是否稳定 | 对比压实前后单轮的输入 token 数和响应耗时 |
agent 反复改同一处:第三次动 src/api/client.ts 里同一个函数 | 「这件事已经做完了」这条信息不在有效注意力范围内 | 写 + 压 | 进度文件里明确标注「已完成/已锁定」;把 todo 重写到上下文末尾(复述),用位置把目标拉回注意力 | 让它列出剩余待办。列表里不再出现已完成项就对了 |
| agent 引用了一个不存在的函数,还越用越顺(上下文投毒 context poisoning) | 一次幻觉进了上下文,被后续轮次当成既定事实反复引用 | 压(重建) | 不要在被污染的上下文里纠正——错误的原文还在。摘出干净事实重新起头,或直接隔离到新的执行体;同时在事实文件里写明「applyV2Discount 不存在」 | 新窗口里问「客户端有哪些 v2 方法」,看它是否还提到那个函数 |
| 两份互相矛盾的规范同时在上下文里(上下文冲突 context clash) | docs/api-migration.md 的旧版本和新版本、或 CLAUDE.md 与对话里的口头约定同时在场 | 选 + 写 | 只保留一份权威来源;删掉或明确作废另一份,并在事实文件里写清「以 v2 章节为准,旧的第 3 节已作废」 | 直接问它相冲突的那条规则。给出单一答案、并能说出依据来源,才算解决 |
矩阵读法有两条。
第一,「先做哪个」是顺序,不是唯一。跨轮次丢进度这一行先写,但写完通常还得配合选——你得让 agent 每轮真的去读那个进度文件。
第二,验证列不可省。上下文工程最大的陷阱是「感觉变好了」。这一轮响应快了、准了,可能只是因为这次任务简单。验证要构造一次可重复的提问,答对才算修好。
3.5 四种命名的失败模式,以及它们落在矩阵哪一行
上面矩阵里的症状,有四种已经被起了名字。名字的价值不在于精确——它们边界互相重叠——而在于让你在团队里能一句话说清刚才发生了什么。
上下文投毒(context poisoning):一个幻觉进了上下文,并被后续轮次反复引用。因为它躺在历史里,每一轮都在为它提供证据。这是矩阵倒数第二行。它最阴险的地方是在被污染的上下文里纠正无效——你说「没有这个函数」,而那段幻觉原文仍然在窗口里,两种说法同时存在,你顺手制造了一次冲突。干净的解法是重建。
上下文分心(context distraction):上下文的体量压过了模型的训练知识。模型不再依据它学到的通用做法,转而模仿窗口里已有的模式。表现在 shopfront 里就是:前面二十轮都用某种写法处理错误,即使那个写法在当前文件里明显不合适,它也会照抄。对应矩阵里「反复改同一处」和「成本延迟上涨」这两行,通常靠压来缓解。
上下文混淆(context confusion):无关内容影响了回答。不是错的信息,只是不相关的信息——legacy/ 下那批陈旧文件里没有一行是假的,但它们把模型的判断带偏了。这是「检索回来一堆不相关文件」那一行,靠选来解决。这里有个来自 context rot(上下文腐化)研究的细节值得记住:语义相似的干扰项最伤。跟目标越像的东西,越不该在场。
上下文冲突(context clash):上下文里的两部分互相打架。矩阵最后一行。它比投毒更常见,因为它不需要任何幻觉——两份都是真的文档,只是一份过期了。
把这四个名字挂回矩阵,你就有了一套完整的诊断语言:症状 → 失败模式 → 动作 → 验证。
3.6 这是一个归纳框架,不是一份标准
有必要说清楚这四个词的地位。
写、选、压、隔不是某个规范里的定义,也不是产品里的四个按钮。它是对社区里已有做法的一次归纳——有人在写文件,有人在做检索,有人在做摘要,有人在开子代理,把这些行为归成了四类。归纳的好处是好记好用;代价是边界不硬。
把状态写进文件算写,但你下一轮再读回来就变成了选;子代理是隔,而它回传的那份一两千 token 的摘要,本身就是一次压。真实的操作几乎从来不是单一动作。矩阵里那些「写 + 压」「选 + 写」的格子不是偷懒,是实情。
所以别把这四个字当分类考试。它们的正确用法是提问的顺序:遇到症状,依次问「这信息该不该在窗口里/现在该不该在/是不是太大了/会不会污染主线」,四个问题走一遍,动作自然浮出来。
后面几章会分别拆开讲这四个动作的落地形态——第 6 章讲写,第 7 章讲选,第 8 章讲压,第 9 章讲隔。真正把它们编成一条能撑三小时的策略,是第 10 章的事。这一章你只需要拿走矩阵。