第三章:上下文窗口管理
学习目标
- 理解为什么 Claude 在长会话中变差(不是 bug,是上下文)
- 用
/context、/usage、/cost监控消耗 - 掌握
/compact的最佳时机和代价 - 用
--resume和会话管理实现断点续传 - 内化 40-60% 黄金区间的概念
附录链接:A03 LLM 基础 解释了本章所有内容背后的 Transformer 架构和注意力机制。A07 Token 经济学 涵盖了 BPE 分词、成本模型和预算优化。A10 提示缓存 详解 Claude Code 如何复用缓存前缀来降低成本和延迟。
核心概念
上下文窗口就是 Claude 的工作记忆
你的对话中的一切 -- 你的提示、Claude 的回复、它读的每个文件、每个命令输出 -- 都在一个叫上下文窗口(Context Window)的内存缓冲区里。当它满了,Claude 就开始忘东西或者输出质量下降。
┌──────────────────────────────────────────────────┐
│ 上下文窗口 (200K tokens) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ 系统提示 │ │ CLAUDE.md│ │ Memory │ 固定 │
│ │ │ │ │ │ 文件 │ 开销 │
│ └──────────┘ └──────────┘ └───────────┘ │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ 对话历史 + 工具调用结果 │ │
│ │ │ │
│ │ 你: "分析路由模块" │ │
│ │ Claude: [Read 5个文件] -> 2000行代码 │ │
│ │ Claude: "路由的工作方式是..." │ │
│ │ 你: "现在加测试" │ │
│ │ Claude: [Write 测试文件] │ 持续 │
│ │ Claude: [Bash] pytest -> 输出 │ 增长 │
│ │ ...一直在增长... │ │
│ └────────────────────────────────────────┘ │
│ │
│ ████████████████████░░░░░░░ 68% 已用 │
└──────────────────────────────────────────────────┘为什么上下文窗口有限制
上下文窗口不是随意的产品限制 -- 它是 Transformer 模型工作原理的必然结果。每个 LLM 的核心是注意力机制(Attention),它让每个 token 能"看到"序列中的其他所有 token。这产生了平方级扩展(Quadratic Scaling):处理 N 个 token 大约需要 N 的平方次运算。将上下文从 100K 翻倍到 200K,计算成本大约翻四倍。
这就是为什么即使有了 200K 或 1M token 的窗口,仍然存在实际限制。随着上下文增长,模型需要越来越多的内存和计算。硬件约束设定了上限。
深入了解:A03 LLM 基础 详细介绍了 Transformer 架构和注意力机制,包括滑动窗口注意力和稀疏注意力等有助于扩展上下文长度的最新进展。
什么是 Token?
在讨论上下文预算之前,你需要理解 token 到底是什么。Token 不是单词,也不是字符 -- 它们是通过 BPE(Byte Pair Encoding,字节对编码) 分词产生的子词单元。分词器将文本拆分为训练数据中出现频率最高的子词片段。
粗略经验法则:1 个 token 大约等于英文中 3-4 个字符,或约 0.75 个单词。对于代码,比例有所不同:
"hello world" → 2 tokens (常见单词 = 每个 1 token)
"implementation" → 1 token (常见长单词)
"XMLHttpRequest" → 4 tokens (驼峰命名会拆分)
"const x = arr.map()" → 8 tokens (代码拆分更多)
"你好世界" → 4 tokens (中文:每个字大约 1-2 token)这对上下文管理很重要,因为代码是 token 密集型的:一个 200 行的 Python 文件可能是 3000 token,而不是你根据单词数估计的 1000。
深入了解:A07 Token 经济学 完整解释了 BPE 分词,包括如何在运行会话前估算成本。
上下文腐烂:无声的杀手
GSD(50k stars)创造了一个每个 Claude Code 用户都应该知道的术语:上下文腐烂(Context Rot)。
他们的研究发现,一旦上下文使用量超过 ~50%,Claude 的输出质量就开始下降。到 70%,就明显变差了。到 80%,你拿到的输出会漏掉需求、引入 bug、忘记前面的指令。
质量 vs. 上下文使用量:
100% |████
|████████
|████████████
|████████████████
|████████████████████
|██████████████████████████
|████████████████████████████████
|██████████████████████████████████████████
0% |____________________________________________
0% 20% 40% 60% 80% 100%
上下文使用量 -->
最佳区间是 40-60%。过了这个范围,质量快速下降。这不是理论。HumanLayer 对数千个会话的研究确认了 40-60% 黄金区间 -- Claude 在上下文窗口使用不超过 60% 时表现最好。超过这个比例,你在和递减的回报作斗争。
重要说明:40-60% 黄金区间是 Claude Code 社区(GSD、HumanLayer 和众多实践者)广泛报告的观察结论,不是经过同行评审的学术发现。你的实际情况可能因任务复杂度和内容类型而异。核心洞察 -- 质量在窗口填满之前就开始下降 -- 在各方报告中是一致的。
"大海捞针"效应
对长上下文模型的研究("大海捞针" / Needle in a Haystack 测试)表明,模型在整个上下文窗口中的表现不是均匀的。模型倾向于最强烈地关注:
- 上下文的开头(系统提示、CLAUDE.md)-- 注意力最高
- 最近的对话轮次 -- 强烈的近因偏差
- 上下文的中间 -- 注意力最弱,这里的细节最容易被"遗忘"
这被称为**"中间遗失"(Lost in the Middle)**现象。这意味着你在 30 轮前做的一个关键决策,埋在对话中间,是 Claude 最可能失去追踪的内容 -- 即使上下文窗口还没满。
实际建议:如果你有重要的约束或决策,把它们放在 CLAUDE.md 中(始终在上下文顶部),或者在当前提示中重新提及,而不是依赖 Claude 记住 40 轮会话中第 12 轮说过的东西。
什么在消耗 Token?
| 内容 | 大约消耗 | 影响 |
|---|---|---|
| 1 行代码 | ~10-15 tokens | 读文件积少成多 |
| 1 个文件 (200 行) | ~2,000-3,000 tokens | 几个文件 = 上下文的 5-10% |
| 1 轮对话 | ~100-500 tokens | 取决于提示长度 |
| Bash 命令输出 | 差别很大 | npm install 的输出能烧掉 5,000+ tokens |
| CLAUDE.md | ~200-500 tokens | 每轮都加载 |
| Memory 文件 | ~100-300 tokens | 每轮都加载 |
最大的 token 消耗来源通常是读文件。Claude 每次为了回答你的问题读一个文件,整个文件内容就进入上下文并一直待在那里。
不同计划的上下文大小
| 计划 | 上下文窗口 | 适合 |
|---|---|---|
| 标准 (Sonnet) | 200K tokens | 大多数任务,中等长度会话 |
| Max/Team/Enterprise + Opus 4.6 | 1M tokens | 超大代码库,长时间分析 |
到达上限时会怎样
在上下文使用达到 ~83.5% 时,Claude Code 触发自动压缩(Auto-Compaction):把对话历史压缩成摘要,保留 CLAUDE.md 和记忆文件,但丢失前期对话的细节。
以下是自动压缩在终端中的样子:
$ [auto-compaction triggered]
⚠ Context usage reached 83.5% (167,000 / 200,000 tokens)
Compacting conversation history...
Preserved:
✓ CLAUDE.md (full)
✓ Memory files (full)
✓ Last 2-3 conversation turns (full)
Compressed:
↓ 47 earlier turns → summary (2,100 tokens)
↓ 12 file reads → key findings only
↓ 8 bash outputs → result summaries
Context after compaction: 24,800 / 200,000 tokens (12.4%)
ℹ Tip: Use /compact proactively at 40-50% for better summaries.问题是:等到自动压缩触发的时候,你已经在降级区间待了一段时间了。所以主动管理才重要。
Demo 6:在真实代码库上观察上下文填充
目标
克隆 Flask,让 Claude 分析它的路由系统,实时观察上下文窗口填充。然后 compact,看看效果。
步骤
1. 准备
我们用 pallets/flask(68k+ stars)。Flask 的路由系统足够复杂,能快速消耗 token。
mkdir -p ~/claude-demos/demo-06 && cd ~/claude-demos/demo-06
git clone --depth 1 https://github.com/pallets/flask.git
cd flask
cat > CLAUDE.md << 'EOF'
# Flask Context Demo
- Focus on the routing and request handling subsystems
- Keep explanations concise
EOF2. 基线测量
claude/context以下是会话开始时 /context 输出的样子:
$ /context
Context usage: 9,400 / 200,000 tokens (4.7%)
Breakdown:
System prompt: 4,200 tokens
CLAUDE.md: 180 tokens
Memory files: 120 tokens
Conversation: 4,900 tokens
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 4.7%
Status: Healthy — plenty of room.同时检查一下你的配额/费用:
/usage(如果你用 API key,就用 /cost。)
3. 开始消耗上下文
让 Claude 读取并分析 Flask 的路由:
Read the core routing files in src/flask/ -- I want to understand how Flask registers routes and dispatches requests. Start with app.py and find the route() decorator implementation./context你应该看到类似这样的内容:
$ /context
Context usage: 36,200 / 200,000 tokens (18.1%)
Breakdown:
System prompt: 4,200 tokens
CLAUDE.md: 180 tokens
Memory files: 120 tokens
Conversation: 31,700 tokens ← 因为文件读取和分析,大幅跳升
███░░░░░░░░░░░░░░░░░░░░░░░░░░░ 18.1%
Status: Healthy — good headroom remaining.记录新的百分比。读几个源码文件大概就跳了 5-15%。
4. 继续深入
Now trace what happens when a request comes in. How does Flask match the URL to a route? Show me the code path from the WSGI entry point to the view function being called./context应该在上升。Claude 需要读更多文件、给出更长的解释,上下文在填充。
5. 再推一把
Analyze Flask's blueprint system. How do blueprints register their routes? What happens when there's a URL conflict between a blueprint route and an app route?/context6. 30% vs. 70% 下的同一任务
这是关键实验。如果你已经超过 50%,现在做这个:
/compact keep the routing analysis and blueprint explanation以下是 /compact 输出的样子:
$ /compact keep the routing analysis and blueprint explanation
Compacting with guidance: "keep the routing analysis and blueprint explanation"
Before: 104,600 / 200,000 tokens (52.3%)
After: 22,400 / 200,000 tokens (11.2%)
Preserved (per your instructions):
✓ Routing analysis (route() decorator, URL rule registration)
✓ Blueprint explanation (blueprint routes, conflict resolution)
✓ CLAUDE.md (always preserved)
✓ Memory files (always preserved)
Dropped:
✗ Raw file contents from app.py, blueprints.py, scaffold.py
✗ Intermediate reasoning and exploratory reads
✗ Bash command outputs
ℹ Context freed: 82,200 tokens (41.1%)/context上下文应该大幅下降。然后问:
Explain Flask's error handling system. How does @app.errorhandler work, and where in the request lifecycle do error handlers execute?把这个回答的质量和上下文更满时得到的回答对比。你应该会发现 compact 后的回答更聚焦、更有条理、捕捉到更多细节。
刚才发生了什么?
以下是 Claude 在路由分析中使用的工具调用序列,以及它为什么选择每个工具:
上下文消耗时间线:
开始: [系统提示 + CLAUDE.md + Memory] = ~5%
读路由文件: = ~18%
追踪请求处理: = ~35%
分析蓝图: = ~52%
/compact: = ~12%
分析错误处理(清爽的上下文): = ~20%在 20% 时做的错误处理分析几乎肯定比在 65% 时问同样问题要好。这就是上下文腐烂的实际效果。
GSD 的关键教训:"长时间运行的会话是敌人。"把工作拆分成聚焦的任务,主动 compact,或者直接开新会话。
Demo 7:会话管理与恢复
目标
学会退出和恢复会话而不丢失上下文。这对跨越几小时或几天的工作至关重要。
步骤
1. 创建一个值得恢复的会话
cd ~/claude-demos/demo-06/flask
claudeHelp me design a middleware system inspired by Flask's approach. I want:
1. A middleware registration API
2. Before-request and after-request hooks
3. Error-handling middleware
Just produce the design doc, no code yet.等 Claude 给出设计后,记下几个关键细节(模块名、钩子执行顺序),然后退出:
Ctrl+C
2. 恢复到上次的位置
claude --resume验证是否生效:
What was the hook ordering we decided on for the middleware system?Claude 应该能回忆起前一个会话的所有内容。--resume 加载的是完整的会话日志,不是压缩摘要。
3. 浏览会话历史
开一个新会话看看你的历史:
claude/sessions这会列出最近的会话,包含时间戳和摘要。你可以恢复其中任何一个。
4. 恢复特定会话
# 每个会话在 /sessions 中都有一个 ID
claude --resume <session-id>刚才发生了什么?
以下是 Claude 在设计中间件系统时使用的工具调用序列:
关键洞察:即使 Claude "只是"在生成设计文档而不是写代码,它仍然使用工具来将设计基于真实的实现模式。这就是为什么设计任务本身也会消耗大量 token。
关键区别
| 操作 | 加载什么 | 细节级别 |
|---|---|---|
| 新会话 | 只有 CLAUDE.md + Memory | 全新开始,没有之前的对话 |
--resume | 完整会话日志 | 一切如常,好像你从没离开 |
/compact 之后 | 压缩摘要 + CLAUDE.md + Memory | 保留要点,丢失细节 |
什么时候用哪个:
- 新会话:切换任务,开始不相关的事情
--resume:中断后继续,被会议打断后接着干/compact:同一个会话里继续,但上下文快不够了
常见问题排查
上下文管理并不总是一帆风顺。以下是最常见的故障模式和处理方法。
任务进行中上下文溢出
症状:Claude 正在实现一个功能到一半。自动压缩触发了。Claude 的下一个回复遗漏了之前一直在追踪的需求,或者重新读取已经分析过的文件。
⚠ Context usage reached 83.5%
Compacting conversation history...
...
Context after compaction: 24,800 / 200,000 tokens (12.4%)然后 Claude 说了类似这样的话:
I'll continue implementing the feature. Let me re-read the main file
to understand the current state...它已经失去了对自己在做什么的追踪。
解决:在开始大型任务之前,提前告诉 Claude 你的计划,这样计划就在最近的对话轮次中(压缩时会保留)。如果你已经深入一个任务且看到上下文超过 50%,主动带指令 compact:
/compact keep: 1) the API design we agreed on 2) the three files I need modified 3) the test planCompact 丢失了关键上下文
症状:compact 后,Claude 不再记得之前的关键架构决策、你解释过的约束条件或讨论过的棘手边界情况。
解决:在 compact 之前预先总结重要决策。输入类似这样的消息:
Before we compact, let me summarize our key decisions for reference:
1. We chose event-based middleware over chain-of-responsibility because...
2. The hook ordering is: auth -> rate-limit -> logging -> handler
3. Error handlers must NOT swallow exceptions, only transform them
Now: /compact keep the above summary and the current implementation plan把总结放在最近的对话轮次中,它就能在压缩后高保真地保留。
更好的做法:把持久性决策写入 CLAUDE.md 或记忆文件。它们在压缩时始终完整保留。
会话太长 -- 什么时候该开新的 vs. compact
经验法则:
| 情况 | 操作 |
|---|---|
| 同一任务,上下文在 40-50% | 带指导地 /compact |
| 同一任务,已经 compact 过两次了 | 开新会话。先把关键上下文写入 CLAUDE.md。 |
| 切换到不同任务 | 新会话(不要 compact,直接离开) |
| 需要引用昨天的工作 | 需要完整细节用 --resume,只需要结论用新会话 + CLAUDE.md 笔记 |
压缩的压缩问题:每次 /compact 都是有损的。对一个已经是压缩结果的摘要再次压缩,会丢失更多细节。同一个会话中 compact 两次后,你在使用的是摘要的摘要。不如开新会话。
深入探索
上下文管理策略
| 场景 | 策略 |
|---|---|
| 快速任务(<10 分钟) | 直接做,别想太多 |
| 中等任务(10-30 分钟) | 在 40-50% 时主动 compact |
| 长任务(>30 分钟) | 拆成子任务。每个子任务一个新会话。 |
| 超大代码库分析 | 用子 agent 隔离上下文(Ch8) |
| 读大文件 | 告诉 Claude 你关心哪些行/函数 |
来自 GSD 的"波次执行"(Wave Execution)模型:把每个任务当成一个波次。每个波次有自己独立的上下文。上一个波次的结果以精简的输入传递给下一个波次,而不是原始的对话历史。
减少 Token 消耗的技巧
# 1. 明确指定要读什么
# 差: "Read main.py"(可能 2000 行)
# 好: "Read the handle_request function in main.py, around line 150"
# 2. 限制命令输出
# 差: "Run npm install"(几百行输出)
# 好: "Run npm install and just tell me if it succeeded"
# 3. 搜索代替读取
# 差: "Read all Python files and find database code"
# 好: "Grep for 'database' across all .py files"
# 4. 带指示地 compact
/compact keep the API design and the test results
# Claude 会优先保留你指定的内容自动压缩的细节
上下文达到 ~83.5% 时 Claude 自动压缩。压缩后:
- CLAUDE.md:完整保留
- Memory 文件:完整保留
- 最近的对话(最后 2-3 轮):保留
- 早期对话:压缩成摘要
- 读过的文件原始内容:丢失(只保留发现了什么的摘要)
- 长的 Bash 输出:丢失
这就是为什么 50% 时主动 compact 比等 83.5% 的自动压缩更好。在 50% 时,你还在黄金区间内,Claude 能写出更好的摘要。到 83.5% 的时候,它已经在降级状态了。
提示缓存与上下文成本
Claude Code 使用**提示缓存(Prompt Caching)**来降低重复上下文的成本。系统提示、CLAUDE.md 和记忆文件作为缓存前缀发送 -- 后续对话轮次复用这个缓存前缀,而不是重新处理。这意味着:
- 固定开销(系统提示 + CLAUDE.md + 记忆)在第一轮之后很便宜
- 但对话历史线性增长且不被缓存
- 压缩会重置对话部分,但缓存前缀保持热状态
深入了解:A10 提示缓存 解释了缓存机制、前缀匹配规则,以及这如何影响你的费用。
知识检测
练习:上下文管理压力测试
任务
- 在 Flask 仓库中(Demo 6)启动一个会话
- 让 Claude 分析三个不同的子系统,每个之后检查
/context:- 模板渲染系统(Jinja2 集成)
- Session/Cookie 处理
- 测试工具(Flask 的测试客户端)
- 当上下文到 40-50% 时,运行
/compact keep the analysis of all three subsystems - compact 后,让 Claude 对比三个子系统的设计模式
- 退出并用
--resume恢复,然后对其中一个子系统提一个后续问题 - 记录每个阶段的上下文百分比
成功标准
- [ ] 在 3 个以上不同时间点记录了
/context输出 - [ ] 在正确的时机 compact 了(40-50%,而不是 80%+)
- [ ] Claude compact 后的输出连贯且有用
- [ ] 成功用
--resume恢复了会话 - [ ] 能解释 40-60% 黄金区间和 80%+ 危险区间的区别
参考数据
典型上下文消耗(200K 窗口):
- 系统提示 + CLAUDE.md:~3-5%
- 读一个 200 行文件:~1-2%
- 一轮对话(提问+回答):~0.5-1%
- 生成一个 100 行代码文件:~1.5-2%
- compact 后:回到 ~8-12%
本章小结
- 上下文窗口是 Claude 的工作记忆。所有内容都在其中竞争空间。
- 上下文限制的存在是因为注意力机制中的平方级扩展 -- 不是随意的产品限制。
- Token 是 BPE 子词单元(约 3-4 个字符)。代码比散文消耗更多 token。
- **上下文腐烂(Context Rot)**是真实存在的:质量在超过 50% 使用量后开始下降。40-60% 是黄金区间(社区广泛观察并确认)。
- **"中间遗失"**效应意味着 Claude 天然对埋在对话中间的信息关注度更低。把关键上下文放在 CLAUDE.md 或最近的对话轮次中。
- 用
/context监控。订阅用户查/usage,API 用户查/cost。 - 在 40-50% 时主动 compact,不要等 83.5% 的自动压缩。
/compact保留 CLAUDE.md 和记忆但丢失早期细节。给它提示要保留什么。--resume恢复完整会话上下文,适合跨休息时间的工作。- 出问题时:compact 前预先总结,避免双重压缩,会话走太远就开新的。
- GSD 的原则:"长时间运行的会话是敌人。"把工作拆成聚焦的波次。
延伸阅读:A03 LLM 基础(注意力机制、为什么上下文有限制)| A07 Token 经济学(BPE 分词、成本估算)| A10 提示缓存(Claude Code 如何用缓存前缀降低成本)
下一章:第四章:Git 工作流与 PR 自动化。你一直都在自己敲 git 命令。这马上要变了。Claude 能接管你的整个 branch-commit-PR 工作流,而且有了 checkpoint 系统,你可以放心让它尝试冒险的重构。/rewind 命令将成为你最好的朋友。