CC 上下文管理:从爆窗到精打细算的完整思考

做 Agent Coding 项目时发现一个绕不过去的问题:上下文窗口太容易满了。年初各大厂商以百万上下文窗口为卖点,现在 256K 已经是标配,但还是不够用。这篇文章记录我从发现问题、踩坑压缩、理解底层机制,到摸索出一套分窗策略的完整思考过程。


上下文,怎么就不够用了?

事情是这样的。我在公司项目和个人项目里做 Agent Coding,第一件事是让 AI 读项目的约束文档——CLAUDE.md、spec 文件、harness 架构下的 GUARDRAILS、contracts、DONE 等等。这些是项目的「规矩」,AI 不读就没法正确干活。

然后我用 Superpower 这类插件调用 Skill——brainstormingopenspeccodebase-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。

具体操作:

  1. 复制报错信息
  2. /new 开新窗口(不带任何项目约束文档)
  3. 贴报错 + 相关代码文件,让 AI 定位和修复
  4. 修完后,只把修改的 diff 粘回主窗口

这个窗口不需要知道项目的全局架构,不需要读约束文档,它只需要解决一个问题:这个报错怎么修。

主窗口从来没接触过那堆堆栈日志,它的注意力始终集中在当前任务的核心逻辑上。实测下来,主窗口的上下文连贯性能延长 2-3 倍。

主窗口是指挥中心,不是维修车间。红灯亮了,派人去修,修好了汇报结果就行。


技巧 3:子 Agent 跑测试,主窗口只看结果

测试日志是上下文的头号杀手。一次 pytestgo test 的完整输出可能有几百行,全部喂回主窗口,窗口直接爆掉一半。

按照 TDD 开发的话,每个点的测试都可以独立出来单独运行。单元测试、集成测试、E2E 测试——这些统统交给子 Agent。

子Agent 主窗口 子Agent 主窗口 完整堆栈在这里处理 不回传主窗口 执行 Task,跑单元测试 跑 pytest / go test {"status":"pass"} 跑集成测试 {"status":"fail","error":"timeout on /api/login"} 修复后重跑 {"status":"pass"}

我在 Task 文件里会硬性规定子 Agent 的输出格式:

测试完成后只输出以下 JSON:
{"status": "pass"} 或 {"status": "fail", "first_error": "错误摘要"}
禁止输出完整 Traceback、完整测试日志。

主窗口完全不关心「如何跑通测试」,只关心「测试通过还是失败」。确保测试完成后,再回到主窗口进行沉淀和归档操作。

子 Agent 我认为是一个好习惯——针对性的内容针对性地处理,节省主窗口的上下文空间。如果你把 pytest 的详细堆栈日志也喂回主窗口,那又是瞬间爆窗。强制要求极简 JSON 输出,能省出至少 15% 的主窗口空间用于核心逻辑推理。


技巧 4:Task 文件写验收标准,不写实现方案

这一条是我踩坑最多的。

最初写 Task 文件时,我习惯写得很详细——涵盖改动范围,包括改动的文件、改动的位置、改动的内容。想着「不用过多但一定要准,关键内容尽可能详细」。

结果发现两个问题:

  1. 子 Agent 变成「填鸭机」,每次都要重读我的长篇大论,Token 烧得快
  2. 我指定的改动点未必是最优的,限制了 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 的底层机制,再到摸索出分窗策略——这个过程让我形成了一套习惯:

  1. 分窗:规划和实现拆到两个窗口
  2. 冷启动:Debug 开独立窗口,不污染主窗口
  3. 隔离测试:子 Agent 跑测试,只回传结果摘要
  4. 验收标准:Task 文件写行为,不写实现细节
  5. 按需加载:Skill 和约束文档不全量塞进上下文
  6. 健康自检:定期检查上下文状态,该分窗就分窗

不需要全用,从你最痛的点开始。如果你跟我一样,发现「还没改代码上下文就满了」,先试试分窗——把规划和实现拆到两个窗口里,立刻能感受到区别。

上下文是有限的,但好的习惯能把有限的上下文花在刀刃上。


Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐