一、引言:百万上下文带来的新挑战
原文链接:Using Claude Code: session management and 1M context 作者:Thariq Shihipar(Anthropic 技术团队)
Claude Code 近期将上下文窗口升级到了 100 万 Token——这意味着模型在单次会话中可以"看到"远超传统编程助手的一次性上下文规模。按常见经验粗略换算,它大致可以容纳 数万行代码、长篇技术文档以及多轮工具调用结果;但真实可用空间仍会受到系统提示、工具输出和文件内容长度的共同影响。这是一个巨大的飞跃,但同时也引发了一系列新的工程问题:
- 是否应该在一个超长会话中完成所有工作?
- 会话变长后,模型的表现会如何变化?
/compact、/clear、/rewind这些命令该在什么时候用?- 子代理(Subagent)到底解决了什么问题?
本文将从原理层面深入剖析这些问题,并提供可操作的最佳实践。
本文导读
- 第二节 解析上下文窗口的内部结构与组成
- 第三节 深入理解上下文腐化(Context Corruption)的机制与影响
- 第四节 逐一拆解五种会话管理策略
- 第五节 提供基于场景的决策模型
- 第六节 总结实战最佳实践与进阶技巧
二、上下文窗口:你需要知道的一切
2.1 什么是上下文窗口?
上下文窗口(Context Window)是模型在生成下一个响应时能"看到"的全部信息。可以把它想象成模型的"工作记忆"——就像你在解决一个复杂问题时,桌面上能同时摊开的所有资料。
在 Claude Code 中,上下文窗口包含以下四类内容:
2.2 上下文窗口的组成
| 组成部分 | 说明 | 典型占用 |
|---|---|---|
| 系统提示(System Prompt) | 包含 CLAUDE.md 配置、工具定义、行为规则 | 数千~数万 Token |
| 对话历史(Conversation History) | 用户的每条消息和 Claude 的每次回复 | 随对话增长 |
| 工具调用及输出(Tool Calls & Outputs) | read_file、search、bash 等工具的调用参数和返回结果 |
通常是最大消耗者 |
| 已读取的文件内容 | 通过工具读取的源代码、配置文件等 | 单个大文件可达数万 Token |
让我们用一段伪代码来直观理解上下文窗口的构成:
# 概念模型:上下文窗口的组成
context_window = {
"capacity": 1_000_000, # 百万 Token 上限
"components": [
SystemPrompt(
claude_md="项目配置与规则",
tool_definitions=["read_file", "search", "bash", "..."],
behavior_rules="安全规则、输出格式等"
),
ConversationHistory([
Turn(user="帮我重构 auth 模块", assistant="好的,让我先分析..."),
Turn(user="改用 JWT 方案", assistant="了解,我来修改..."),
# ... 每个 Turn 都占用 Token
]),
ToolOutputs([
ToolCall("read_file", "src/auth/middleware.ts", output="... 200行代码 ..."),
ToolCall("bash", "npm test", output="... 测试输出 ..."),
# ... 工具输出通常是最大的 Token 消耗者
])
]
}
# 关键洞察:上下文 ≠ 无限记忆
# 即使有百万 Token,也需要策略性地管理
used_tokens = sum(len(component) for component in context_window["components"])
remaining = context_window["capacity"] - used_tokens
2.3 百万 Token 到底有多大?
为了建立直观感受:
| 换算 | 大约等于 |
|---|---|
| 1M Token | ~750,000 个英文单词 |
| 代码量 | ~75,000 行代码(取决于语言) |
| 书籍 | 约 3-4 本技术书的全文 |
| 对话轮次 | 取决于工具调用量,通常数十到数百轮 |
关键认知:百万 Token 虽然很大,但并不意味着你应该把它全部用完。上下文质量远比上下文数量重要。
三、上下文腐化:大上下文窗口的隐性风险
3.1 什么是上下文腐化?
上下文腐化(Context Corruption)是指随着上下文增长,模型性能逐渐下降的现象。这不是 Bug,而是 Transformer 架构的内在特性。
说明:这里展示的是一种趋势,而不是固定阈值。不同模型、任务类型、工具输出密度,都会影响上下文腐化出现的时机与程度。
3.2 腐化的技术原理
从 Transformer 的注意力机制角度理解:
# 简化的注意力机制示意
def attention(query, keys, values):
"""
当 keys 和 values(即上下文)变得很长时:
1. 注意力分数被更多 token 稀释
2. 模型需要在更多信息中找到相关内容
3. 早期的、不相关的信息开始"干扰"当前任务
"""
scores = dot_product(query, keys) / sqrt(d_k)
weights = softmax(scores) # 注意力权重分散到更多 token 上
return weighted_sum(weights, values)
# 上下文腐化的直觉理解:
# - 上下文较短时:注意力更容易集中在关键信息上
# - 上下文持续拉长时:检索相关线索和排除噪声的成本会上升
# - 当无关历史与工具输出堆积过多时:模型更容易"走神"
3.3 腐化的实际表现
在日常使用中,上下文腐化通常表现为:
- 遗忘早期指令:在长会话后期,模型可能忘记你在开头设定的约束
- 重复错误:之前已经被否定的方案可能被再次提出
- 混淆文件内容:读取了太多文件后,可能混淆不同文件的内容
- 忽略关键细节:在大量调试信息中遗漏重要的错误线索
核心洞察:上下文腐化意味着模型在接近上下文满载时,处于其"最不智能"的状态——而这恰恰是自动压缩触发的时机。这就是为什么主动管理上下文如此重要。
四、五种会话管理策略深度解析
每当 Claude 完成一个任务后,你面临一个分支决策。以下是五种核心策略的深度解析:
4.1 继续对话(Continue)
操作:直接发送下一条消息。
适用场景:当前上下文中的信息对下一个任务仍然高度相关。
# 典型场景示例
# Turn 1: 实现了一个新的 API 端点
> 帮我实现 /api/users/profile 端点
# Claude 读取了路由配置、数据模型、中间件等文件,完成了实现
# Turn 2: 为刚实现的功能编写文档(继续对话是最优选择)
> 现在帮我为这个端点编写 API 文档
# 为什么继续比新建更好?
# - Claude 已经"记住"了端点的参数、返回格式、使用的中间件
# - 启动新会话意味着要重新读取所有这些文件
# - 继续对话节省了重新加载的 Token 成本和时间
核心原则:如果下一个任务需要用到当前会话中已加载的上下文,就不要浪费它。
4.2 回退(Rewind)
操作:双击 Esc 键或运行 /rewind 命令。
适用场景:Claude 走错了方向,但之前的文件读取和分析仍然有价值。
这是一个非常强大但容易被忽视的功能。来看一个具体例子:
# 场景:调试一个 API 响应格式问题
# Turn 1: Claude 读取了 5 个相关文件
> 调查为什么 /api/orders 返回的日期格式不对
# Turn 2: Claude 尝试了方案 A(修改序列化器)— 但不work
# 此时你发现问题其实在数据库层
# ❌ 错误做法:
> 那个方案不行,问题在数据库层,试试修改 migration
# 问题:Turn 2 中失败方案的所有代码变更和解释
# 仍然占用上下文空间,还可能误导后续推理
# ✅ 正确做法:
# 按 Esc Esc,回退到 Turn 1 之后
# 然后用你学到的知识重新提示:
> 问题不在序列化器层,是数据库 migration 中的日期列类型不对。
> foo 模块的序列化器没有问题不用改。直接修改 migration 文件。
# 效果:
# - Turn 1 中读取的 5 个文件仍然在上下文中 ✓
# - Turn 2 的失败尝试被完全移除 ✓
# - 新的 Turn 2 从干净状态开始,带着更精确的指令 ✓
进阶技巧——“时间旅行"模式:
# 在回退前,让 Claude 总结它学到的东西
> 在我回退之前,总结一下你从这次尝试中学到了什么,
> 以及你建议下一步尝试什么方案
# Claude 会输出一份总结
# 然后你 Esc Esc 回退
# 再把这份总结作为新提示的一部分发送
# 这就像收到了一封"来自未来的 Claude"的信——
# 它尝试了某个方案、失败了,然后把经验教训传回给你
4.3 压缩(/compact)
操作:运行 /compact 或 /compact <hint>。
机制:Claude 将当前对话历史总结成一段更简短的描述,然后用这个总结替换原始历史,在新的"压缩后"上下文中继续工作。
# 基本用法
/compact
# 带引导的用法(推荐)
/compact 重点保留 auth 重构相关的上下文,测试调试的部分可以丢弃
# 更具体的引导
/compact focus on the database schema changes and API contract,
drop the CSS debugging and test fixture setup details
压缩的本质是有损的——就像 JPEG 压缩图片会丢失细节一样,对话压缩也会丢失信息。但关键区别在于:你可以通过 hint 控制"丢什么、留什么”。
4.4 清除(/clear)
操作:运行 /clear,然后手动输入新的上下文。
机制:完全清空对话历史,从零开始新的会话。
# 典型用法
/clear
# 然后手动提供精炼的上下文:
> 我正在重构 auth 中间件。
> 约束条件:必须保持向后兼容,不能修改公开的 API 接口。
> 关键文件:src/middleware/auth.ts 和 src/models/user.ts
> 已排除的方案:直接替换 session 为 JWT(会破坏现有客户端)
> 当前进展:已完成 token 验证逻辑,接下来需要处理 refresh token 流程。
/compact vs /clear 的选择指南:
| 维度 | /compact |
/clear |
|---|---|---|
| 工作量 | 低(自动) | 高(手动编写) |
| 上下文质量 | Claude 决定重点 | 你决定重点 |
| 适用场景 | 同一任务继续深入 | 切换到新任务方向 |
| 腐化风险 | 中(可能保留无关信息) | 低(你控制一切) |
| 信息保留 | 可能更全面 | 可能遗漏重要细节 |
4.5 子代理(Subagent)
操作:提示 Claude 生成子代理,或者 Claude 在需要时自动调用。
机制:子代理获得一个全新的独立上下文窗口,完成工作后只将最终结论返回给父级会话。
关键心理测试:
“我是需要这个工具调用的完整输出,还是只需要最终结论?”
如果答案是"只需要结论",那就应该使用子代理。
# 场景 1:验证工作结果
> 生成一个子代理,根据以下规范文件验证我们刚完成的 auth 重构是否符合要求
# 子代理会:读取规范文件 → 读取实现代码 → 逐条验证 → 返回验证报告
# 所有中间的文件读取和分析过程不会污染父级上下文
# 场景 2:学习另一个代码库的实现方式
> 生成一个子代理,研究 stripe-node SDK 的错误处理模式,
> 总结它的 retry 机制和 error 类型层级,
> 然后我们在自己的项目中实现类似的方案
# 场景 3:编写文档
> 根据我最近的 git changes,生成一个子代理来编写这个功能的用户文档
# 场景 4:大规模代码库搜索
> 生成一个子代理,搜索整个代码库中所有使用 deprecated API 的地方,
> 列出需要迁移的文件和具体行号
子代理的本质优势:它是一种上下文隔离机制。大量中间工作产出(文件读取、搜索结果、分析过程)被封装在子代理的上下文中,只有精炼后的结论回流到主会话。
五、场景化决策模型
5.1 综合决策表
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 相同任务,上下文仍然相关 | 继续(Continue) | 上下文中的信息是关键资产,不要浪费重建成本 |
| Claude 走错了方向 | 回退(Rewind) | 保留有用的文件读取,删除失败尝试,用新知识重新引导 |
| 任务中途,会话充满过时的调试信息 | /compact <hint> |
低工作量;可引导 Claude 保留关键信息 |
| 开始全新任务 | /clear |
零腐化;你完全控制传递什么内容到新会话 |
| 下一步会产生大量中间输出 | Subagent | 中间噪声留在子级;只有结论返回 |
5.2 决策流程图(代码版)
def choose_session_strategy(current_state):
"""
根据当前状态选择最优的会话管理策略
"""
if current_state.is_new_unrelated_task:
return "/clear" # 新任务,干净起步
if current_state.claude_went_wrong_direction:
if current_state.has_useful_file_reads:
return "rewind" # 保留文件读取,删除错误尝试
else:
return "/clear" # 没什么值得保留的
if current_state.context_is_stale:
if current_state.continuing_same_task:
return "/compact" # 压缩后继续
else:
return "/clear" # 换方向了
if current_state.next_step_produces_lots_of_intermediate_output:
return "subagent" # 隔离中间输出
if current_state.context_still_relevant:
return "continue" # 上下文仍有价值,继续
# 默认:如果不确定,倾向于 /compact
return "/compact"
5.3 典型工作流示例
示例:全栈功能开发
六、实战最佳实践总结
6.1 四条黄金法则
| # | 法则 | 解释 |
|---|---|---|
| 1 | 新任务 = 新会话 | 不要在一个巨大的会话中混合不相关的任务 |
| 2 | 回退 > 纠正 | 与其说"那不对,试试这个",不如回退后用更精确的指令重新开始 |
| 3 | 主动压缩 > 被动压缩 | 在你还清楚下一步方向时压缩,而不是等到自动触发 |
| 4 | 只需结论 → 子代理 | 大量中间输出会污染上下文,用子代理隔离它 |
6.2 避免糟糕的自动压缩
自动压缩触发的时机恰恰是模型表现最差的时候(上下文接近满载,腐化最严重)。以下策略可以避免这个问题:
# ❌ 等到自动压缩被触发(模型此时最不智能)
# 会话越来越长... 突然自动压缩触发
# 压缩结果可能很糟糕,因为模型无法预测你的下一步
# ✅ 主动在"关键转折点"压缩
# 完成一个子任务后:
/compact 保留数据库 schema 变更和 API 接口定义,
调试 CSS 的部分和 test fixture 的设置细节可以丢弃,
接下来我要做前端组件
# ✅ 或者在任务切换前清除
/clear
# 然后提供精炼的上下文简报
6.3 /usage 命令:了解你的消耗
Claude Code 新增的 /usage 命令可以帮助你了解当前的 Token 使用情况:
# 查看当前会话的使用情况
/usage
# 你可以据此判断:
# - 当前会话是否已经堆积了太多文件读取和工具输出
# - 下一步是否还会引入大量新上下文
# - 此时是继续、压缩、清空,还是交给子代理更合适
6.4 上下文管理命令速查表
| 命令 | 快捷键 | 功能 | 使用时机 |
|---|---|---|---|
/usage |
- | 查看 Token 使用情况 | 随时检查 |
/rewind |
Esc Esc |
回退到之前的消息 | Claude 走错方向 |
/compact |
- | 压缩当前会话 | 上下文过时或过满 |
/compact <hint> |
- | 带引导的压缩 | 精确控制保留内容 |
/clear |
- | 清除并开始新会话 | 切换到全新任务 |
七、结语:像管理内存一样管理上下文
如果把 Claude Code 的上下文窗口类比为计算机内存,那么:
- 继续对话 = 继续使用当前内存中的数据
- Rewind = 回滚到之前的内存快照
- Compact = 垃圾回收(GC),自动释放不需要的内存
- Clear = 手动清空内存并重新初始化
- Subagent = 在独立的进程空间中运行任务,只通过 IPC 传回结果
# 类比:上下文管理 ≈ 内存管理
class ContextManager:
"""管理 Claude Code 会话的上下文,就像管理内存一样"""
capacity = 1_000_000 # 百万 Token
def continue_session(self):
"""最高效——直接复用现有上下文"""
pass # 不做任何清理,继续使用
def rewind(self, checkpoint):
"""回滚到检查点——保留有用数据,丢弃错误尝试"""
self.state = self.snapshots[checkpoint]
def compact(self, hint=None):
"""垃圾回收——压缩但有损"""
summary = self.summarize(self.history, focus=hint)
self.history = summary # 用总结替换历史
def clear(self, briefing):
"""硬重置——零腐化但需要手动重建"""
self.history = []
self.add(briefing) # 用你的简报作为新起点
def spawn_subagent(self, task):
"""独立进程——中间状态不影响主上下文"""
child = ContextManager() # 全新上下文
result = child.execute(task)
return result.summary # 只返回结论
百万上下文窗口是一个强大的工具,但强大不等于可以无脑使用。掌握会话管理策略的开发者,能够让 Claude Code 始终保持在最佳性能状态,而不是在上下文腐化中逐渐失控。
记住:最好的上下文管理是主动的——不要等到模型表现下降才想起来清理。就像写高性能程序一样,最好的内存管理不是依赖 GC,而是在架构层面就避免内存泄漏。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付