CC 上下文管理:从爆窗到精打细算的完整思考
CC 上下文管理:从爆窗到精打细算的完整思考
做 Agent Coding 项目时发现一个绕不过去的问题:上下文窗口太容易满了。年初各大厂商以百万上下文窗口为卖点,现在 256K 已经是标配,但还是不够用。这篇文章记录我从发现问题、踩坑压缩、理解底层机制,到摸索出一套分窗策略的完整思考过程。
上下文,怎么就不够用了?
事情是这样的。我在公司项目和个人项目里做 Agent Coding,第一件事是让 AI 读项目的约束文档——CLAUDE.md、spec 文件、harness 架构下的 GUARDRAILS、contracts、DONE 等等。这些是项目的「规矩」,AI 不读就没法正确干活。
然后我用 Superpower 这类插件调用 Skill——brainstorming、openspec、codebase-recon。这些 Skill 的 SKILL.md 内容会和我们发给模型的消息拼在一起,一起塞进上下文窗口。
结果就是:约束文档 + Skill 指令 + 对话历史 = 上下文窗口的三座大山。还没开始改代码,70% 就没了。
上下文窗口 256K tokens
┌────────────────────────────────────────────────┐
│████████████████████████████████████░░░░░░░░░░░░│
│◀──── 约束文档 + Skill 指令 ────▶│◀─ 剩余空间 ─▶│
│ 占了 70%,还没开始写代码 │
└────────────────────────────────────────────────┘
模型窗口在不断变大,从 128K 到 256K,但约束文档也在变多、需求也在变复杂。光靠堆窗口大小,解决不了根本问题。
而且费用也是个现实问题——窗口越大,每次对话的 Token 消耗越高。256K 的窗口跑满,钱包也在滴血。
上下文是有限的,但约束又必不可少。这个矛盾怎么解?
第一次尝试:压缩
上下文满了,本能反应是用 compact(压缩)。把之前的对话历史压缩成一份摘要,腾出空间继续干。
效果怎么说呢……一言难尽。
我的真实场景:一个需求做到一半,过程中出现了一些理解和实现上的偏差,讨论了好几轮才纠正过来。这时候上下文满了,压缩。然后继续改之前的任务——AI 开始犯已经纠正过的错误,改回了之前已经修好的地方,行为变得很奇怪。
就好像你跟一个同事聊了一上午,把需求的来龙去脉、踩过的坑、达成的共识都聊清楚了。午饭后他回来了,说「我看了下会议纪要,咱们继续吧」——然后把上午讨论过的所有结论都推翻了一遍。
压缩不是续命,是换了一个「只看过摘要」的新人接你的班。
为什么压缩会「降智」
压缩后变蠢,不是玄学,是有技术原因的。
大语言模型处理上下文时,注意力分布并不均匀。模型对开头(系统指令)投入的注意力最多,对结尾(最近的几条对话)也关注得很紧,但中间部分天然会「失焦」。这个现象在研究中叫 Lost-in-the-Middle。
注意力强度
▲
高 │ ██ ████
│ ██ ████
│ ███ █████
│ ████ ████████
低 │ ████████████████████████████████████████████
└──────────────────────────────────────────────▶
开头 中间(失焦区) 结尾
系统指令 压缩摘要在这里 最新对话
压缩的原理是把前面的对话历史压缩成一份摘要,放在上下文的中间位置。这份摘要是有损的——丢失了细节、丢失了上下文之间的关联。模型拿到这份「中间摘要」后,注意力往两边跑,摘要里的关键信息很容易被忽略。
这就解释了为什么压缩后改旧代码会出「玄学问题」:你之前讨论过的约束和决策,被压缩成了一句话摘要,模型大概率不会认真读它。
明白这个之后,我的策略发生了根本性的转变:不再想着「续命」,而是主动「分叉」。
现在的顶级团队几乎已经废弃了用压缩来做长会话续命。行业的新做法是「分叉(Checkpoint Forking)」——当上下文达到 60% 时,主动创建当前状态的完成摘要,直接新开窗口继续,而不是在原窗口硬压缩。
一句话:能独立新窗口就独立新窗口,能开子 Agent 就开子 Agent。
策略落地:6 个实战技巧
从「能独立就独立」这个核心思路出发,我在实际开发中摸索出了 6 个具体技巧。
技巧 1:规划和实现拆到两个窗口
这是我用得最多、效果最明显的一条。
做需求改动之前,有一大堆准备工作:读约束文档和业务文档,判断这次改动是否符合项目约束,判断会不会影响其他功能,用 Superpower 的 brainstorming + openspec 给出方案。如果不符合约束,还要调整需求或者跟项目组沟通。
这些分析做完之后,上下文里已经堆满了约束文档、业务文档、讨论过程。带着这些「历史包袱」继续写代码,只会越来越慢。
窗口 1(规划) 窗口 2(实现)
┌──────────────────┐ ┌──────────────────┐
│ 读约束文档 │ │ 读 Task 文件 │
│ 分析需求 │ ─────▶ │ 写代码 │
│ 输出 Task 文件 │ │ 跑测试 │
│ 归档方案 │ │ 提交 diff │
└──────────────────┘ └──────────────────┘
上下文:约束+分析 上下文:只跟实现相关
规划窗口的职责是:读约束、出方案、写 Task、归档。做完这些,它的历史使命就结束了。
新开一个干净的窗口,只读 Task 文件,上下文轻量且聚焦。这个窗口从第一轮对话开始就是「写代码」状态,不会被之前的分析讨论干扰。
技巧 2:调试和 Bug 修复开独立窗口
错误做法:在主窗口贴报错堆栈,让 AI 修 Bug。堆栈一贴就是几十行,上下文瞬间膨胀,而且这些堆栈信息会一直占着空间不走。
正确做法:开一个「冷启动」窗口专门修 Bug。
具体操作:
- 复制报错信息
/new开新窗口(不带任何项目约束文档)- 贴报错 + 相关代码文件,让 AI 定位和修复
- 修完后,只把修改的 diff 粘回主窗口
这个窗口不需要知道项目的全局架构,不需要读约束文档,它只需要解决一个问题:这个报错怎么修。
主窗口从来没接触过那堆堆栈日志,它的注意力始终集中在当前任务的核心逻辑上。实测下来,主窗口的上下文连贯性能延长 2-3 倍。
主窗口是指挥中心,不是维修车间。红灯亮了,派人去修,修好了汇报结果就行。
技巧 3:子 Agent 跑测试,主窗口只看结果
测试日志是上下文的头号杀手。一次 pytest 或 go test 的完整输出可能有几百行,全部喂回主窗口,窗口直接爆掉一半。
按照 TDD 开发的话,每个点的测试都可以独立出来单独运行。单元测试、集成测试、E2E 测试——这些统统交给子 Agent。
我在 Task 文件里会硬性规定子 Agent 的输出格式:
测试完成后只输出以下 JSON:
{"status": "pass"} 或 {"status": "fail", "first_error": "错误摘要"}
禁止输出完整 Traceback、完整测试日志。
主窗口完全不关心「如何跑通测试」,只关心「测试通过还是失败」。确保测试完成后,再回到主窗口进行沉淀和归档操作。
子 Agent 我认为是一个好习惯——针对性的内容针对性地处理,节省主窗口的上下文空间。如果你把 pytest 的详细堆栈日志也喂回主窗口,那又是瞬间爆窗。强制要求极简 JSON 输出,能省出至少 15% 的主窗口空间用于核心逻辑推理。
技巧 4:Task 文件写验收标准,不写实现方案
这一条是我踩坑最多的。
最初写 Task 文件时,我习惯写得很详细——涵盖改动范围,包括改动的文件、改动的位置、改动的内容。想着「不用过多但一定要准,关键内容尽可能详细」。
结果发现两个问题:
- 子 Agent 变成「填鸭机」,每次都要重读我的长篇大论,Token 烧得快
- 我指定的改动点未必是最优的,限制了 AI 的自主判断
后来改成了 BDD 风格(Given-When-Then),只描述验收条件,不给具体改动方案:
## Task: 邮箱格式校验
Given: 用户提交注册表单
When: 邮箱字段格式不合法(缺少 @ 或域名)
Then: 返回 400 状态码,错误信息为 "邮箱格式不正确"
验收标准:
- 单元测试覆盖合法/非法邮箱各 3 种
- API 返回 JSON 格式:{"code":400, "message":"邮箱格式不正确"}
子 Agent 拿到这份 Task 后,自己去 grep 代码库、找改动点、写实现。它只在主窗口汇报「我准备改 A 文件和 B 文件,确认后开工」,只在主窗口确认后才动工。
实测下来,这种写法比「指定改动位置」平均节省 40% 的子 Agent Token 开销。子 Agent 自己探索代码库时,上下文里只加载它需要的文件,而不是你预设的全部文件。
技巧 5:Skill 按需调用,约束文档挂载
用 Superpower 这类插件时,每次调用 Skill 都会把 SKILL.md 的内容注入上下文。如果同时加载多个 Skill,固定开销会非常大。
我的做法:只在对应阶段调用对应的 Skill。
| 阶段 | 推荐 Skill | 说明 |
|---|---|---|
| 需求分析 | brainstorming |
只在规划窗口用 |
| 方案设计 | openspec |
输出 spec 和 Task |
| 代码实现 | 不加载 Skill | 保持上下文干净 |
| 测试验证 | 子 Agent 按需 | 主窗口不加载 |
| 文档归档 | content-writer |
收尾阶段用 |
约束文档(CLAUDE.md、spec 文件)通过 Prompt Caching 挂载,不注入对话历史。模型能读到约束,但不挤占「有效注意力」空间。具体做法是把约束文档设为缓存前缀,API 层会自动做前缀匹配,费用也降到 1/10。
关键区别:注入是把文档塞进对话,挂载是把文档放在系统层。 不是不读,而是换个方式读。
一句话总结:约束文档挂载,Skill 按需,对话历史只留当前任务。
技巧 6:上下文健康度自检
分享一个我自用的「体检清单」。每次开始新任务前扫一眼:
| 信号 | 判断 | 动作 |
|---|---|---|
| 对话超过 10 轮 | 上下文开始「衰减」 | 考虑分窗 |
| AI 开始重复纠正过的错误 | 注意力失焦 | 立刻新开窗口 |
| 堆栈/日志超过 20 行 | 大量噪音进入上下文 | 交给子 Agent 处理 |
| 同一个需求讨论了 3 轮还没动手 | 规划窗口太重了 | 归档方案,开实现窗口 |
| 测试日志回传后 AI 开始「跑偏」 | 测试噪音干扰 | 限制子 Agent 输出格式 |
不需要每次都严格遵守,但养成这个习惯之后,上下文爆窗的频率明显降低。
关于多 Agent 框架的冷思考
上下文管理做到极致,自然会想到:用多 Agent 框架,让不同模型各司其职。
OpenCode 的核心思想就是这样——多个 Agent 不同模型,每个 Agent 负责不同的任务。好的贵的模型做规划、下发分配任务;不同的子任务根据模型能力分配,有的性价比高,有的适合深度处理。
理念很好。但我实际用下来,发现 Token 烧得有点快。
原因在于「上下文传递」的开销——子 Agent 完成任务后,要把自己的完整思维链带回主窗口做汇总。这个过程本身就在烧 Token,而且烧得很猛。你让子 Agent 跑了一个复杂任务,它产出了几千 Token 的推理过程,这些全都要带回主窗口。主窗口的上下文里塞满了子 Agent 的思维链,反而比自己动手还费。
另外,Claude 本质只有 3 个模型槽位。对于大部分项目来说,这 3 个槽位够用了——不需要追求「每个子任务都用最适合的模型」这种理想状态。
有意思的是,我用的手动分窗方式——自己判断什么时候开新窗口、什么时候开子 Agent——在成本控制上反而更精细。本质上我是用自己的大脑当了「路由」,去掉了框架层那层冗余的思维链传递。现阶段,这种「半自动手动分窗」在成本控制上,比全自动多 Agent 框架更实用。
全自动框架适合财大气粗的团队,手动分窗适合精打细算的工程师。
总结
从发现上下文不够用,到压缩踩坑,到理解 Lost-in-the-Middle 的底层机制,再到摸索出分窗策略——这个过程让我形成了一套习惯:
- 分窗:规划和实现拆到两个窗口
- 冷启动:Debug 开独立窗口,不污染主窗口
- 隔离测试:子 Agent 跑测试,只回传结果摘要
- 验收标准:Task 文件写行为,不写实现细节
- 按需加载:Skill 和约束文档不全量塞进上下文
- 健康自检:定期检查上下文状态,该分窗就分窗
不需要全用,从你最痛的点开始。如果你跟我一样,发现「还没改代码上下文就满了」,先试试分窗——把规划和实现拆到两个窗口里,立刻能感受到区别。
上下文是有限的,但好的习惯能把有限的上下文花在刀刃上。
更多推荐




所有评论(0)