006、会话管理与上下文窗口:输入限制、自动压缩机制与 token 消耗优化
一次让我抓狂的调试经历
上周五晚上十一点,我盯着终端里Claude Code输出的“Context window full. Truncating oldest messages…”这条警告,血压直接拉满。项目上线前最后一轮集成测试,Agent连续跑了四轮代码修改后,突然开始“失忆”——它忘记了自己十分钟前刚重构过的那个模块的接口签名,开始生成完全不兼容的代码。
我第一反应是“这AI是不是抽风了”,直到我检查了会话日志,才发现上下文窗口已经被塞满了。前面三轮对话里,我每次让Agent“先看看整个项目结构”,它都把整个src/目录的树形结构和关键文件内容塞进了上下文。到第四轮,它只能看到最近两条消息,前面的“记忆”全被截断了。
这个坑,我替你们踩过了。今天这篇笔记,就是要把会话管理和上下文窗口的底层逻辑掰开揉碎,顺便给出我压箱底的优化策略。
上下文窗口不是无限记事本
Claude Code的上下文窗口,本质上是一个固定大小的“短期记忆缓冲区”。别被那些营销号忽悠说什么“无限上下文”,那是针对API层面的流式处理,不是给你当数据库用的。
窗口大小通常以token为单位。一个token大约对应0.75个英文单词,或者0.5个中文字符。Claude 3.5 Sonnet的上下文窗口是200K tokens,听起来很大对吧?但你让Agent读一遍你的微服务项目,光是pom.xml加上几个核心Service的代码,轻松吃掉30K-50K tokens。再来一轮“帮我分析这个bug”,Agent会把异常堆栈、相关代码片段、你的问题描述全部塞进去,窗口瞬间见底。
关键点在于:上下文窗口不是按对话轮次平均分配的,而是按消息的token消耗累加的。你每发一条消息,Agent的回复,加上中间的工具调用记录(比如读取文件、执行命令的输出),全部算在窗口里。
自动压缩机制:它比你想象的更“暴力”
很多人以为Claude Code的自动压缩是“智能摘要”,会保留关键信息。天真了。
实际机制是这样的:当上下文窗口使用率达到某个阈值(通常是80%-90%),系统会触发压缩。压缩策略是丢弃最早的消息,保留最近的消息。注意,是“丢弃”,不是“总结”。那些被丢弃的消息里包含的信息,Agent再也看不到了。
更坑爹的是,压缩不是按条数算的,是按token量算的。如果你早期消息里塞了一个巨大的JSON配置文件(比如5000行),压缩时这条消息会被整个丢掉,而不是只丢掉其中一部分。这意味着你之前让Agent“记住”的项目配置,在压缩后彻底消失。
我见过最离谱的情况:一个同事让Agent分析一个Spring Boot项目的application.yml,文件有3000多行。Agent读完后,同事又问了一堆业务逻辑问题。等到第五轮,Agent突然开始生成错误的配置项名称——因为那个巨大的YAML文件已经被压缩丢掉了,Agent只能靠自己的训练数据“猜”配置,结果可想而知。
别这样写:把整个项目配置文件一次性丢给Agent,指望它一直记住。
正确做法:只给Agent当前任务需要的配置片段。比如改数据库连接,只给spring.datasource那几行。
Token消耗的隐形杀手
我统计过自己一个月内使用Claude Code的token消耗数据,发现几个惊人的规律:
第一杀手:工具调用输出。每次Agent执行read_file、grep、list_directory这些操作,返回的结果会完整地塞进上下文。你让Agent“搜索所有包含@Deprecated的类”,如果项目里有200个匹配项,这200个结果全部进上下文。一次搜索吃掉10K tokens是常事。
第二杀手:错误堆栈。当Agent执行命令失败时,它会返回完整的异常堆栈。一个Java的NullPointerException堆栈,从最底层到最顶层,轻松吃掉3K-5K tokens。如果你连续调试三次,光堆栈就吃掉15K tokens。
第三杀手:你的“废话”。“请帮我看看这个代码,我觉得可能有问题,但我不确定具体是什么问题,你先分析一下,然后给我建议,如果建议可行的话,我们再讨论下一步…”——这段话大概50个token,但如果你每轮都这么写,十轮下来就是500个token。积少成多。
实战优化策略
策略一:主动管理会话生命周期
不要一个会话用到底。我现在的习惯是:每个独立任务开一个新会话。
比如“修复登录模块的NullPointerException”是一个会话,“重构订单服务的接口设计”是另一个会话。两个任务之间没有依赖关系,强行放在一个会话里只会互相污染上下文。
具体操作:在Claude Code中,用/new命令开启新会话。旧会话的日志会自动保存,需要时可以回查,但不会占用新会话的窗口。
策略二:控制每次输入的信息量
这是最容易被忽视的优化点。很多人习惯把问题描述得特别详细,生怕Agent不理解。但Agent不是人类,它不需要你铺垫背景。
踩过坑的写法:
“我们有一个微服务架构,包含三个服务:用户服务、订单服务、支付服务。用户服务负责用户注册登录,订单服务负责创建订单,支付服务负责处理支付。现在用户服务里有一个bug,当用户注册时,如果邮箱已经存在,应该返回错误码400,但实际返回了500...”这段描述大概200个token,但真正有用的信息只有最后一句。
优化后的写法:
“用户服务注册接口,邮箱重复时返回500,预期返回400。异常堆栈如下:[堆栈内容]”省掉了150个token,而且信息密度更高。
策略三:善用“上下文锚点”
这是一个我从Claude Code官方文档里学到的技巧,但很多人不知道。
你可以在对话中插入一个“锚点消息”,告诉Agent哪些信息是必须保留的。比如:
[重要上下文] 当前项目结构:src/main/java/com/example/service/OrderService.java 是核心业务逻辑,依赖 src/main/java/com/example/repository/OrderRepository.java。数据库表结构见 schema.sql 第10-50行。这条消息会占据一个固定的位置。当压缩发生时,如果这条消息是最近的消息之一,它会被保留。但注意,如果后续消息太多,它还是会被挤掉。
更可靠的做法是:在关键节点手动重置上下文。比如让Agent完成一个阶段后,用/compact命令(如果支持)或者手动总结当前状态,然后开启新会话。
策略四:监控token消耗
别等到“Context window full”才后悔。我写了一个简单的脚本,定期检查Claude Code的会话日志,统计每个会话的token消耗趋势。当某个会话的token使用量超过窗口的70%时,我会主动干预——要么总结当前状态开新会话,要么精简后续输入。
你也可以手动估算:每次Agent回复后,看它输出的最后有没有“Tokens: XXXX used”之类的信息。如果没有,可以问Agent“当前会话已使用多少token?”——它自己知道。
自动压缩的“逃生门”
如果压缩已经发生,怎么办?别慌,有办法。
Claude Code的会话日志是持久化存储的。压缩只是把旧消息从“活跃上下文”中移除,并没有删除日志。你可以:
- 用
/history命令查看完整会话历史 - 找到被压缩的关键信息(比如项目配置、接口定义)
- 在新会话中重新提供给Agent
但注意:Agent在压缩后不会主动告诉你它忘了什么。它只会表现得越来越“笨”,开始犯低级错误。这时候你要警觉,而不是继续追问。
我个人的经验是:一旦发现Agent开始重复问已经问过的问题,或者给出明显与之前结论矛盾的答案,立即检查上下文状态。大概率是压缩已经发生了。
一个真实案例的优化前后对比
优化前(一个典型的“翻车”会话):
- 第1轮:让Agent读取整个项目结构(15K tokens)
- 第2轮:让Agent分析一个bug,附带完整堆栈(8K tokens)
- 第3轮:Agent给出修复方案,附带修改的代码(10K tokens)
- 第4轮:我要求再检查另一个模块,Agent重新读取文件(12K tokens)
- 第5轮:上下文窗口满,压缩发生,Agent忘记第1轮的项目结构
- 第6轮:Agent开始生成不兼容的代码,我花半小时排查才发现问题
优化后(同一个任务):
- 会话A(项目结构分析):让Agent读取关键文件,输出摘要后结束(20K tokens,但只用了1轮)
- 会话B(bug修复):基于会话A的摘要,提供堆栈,让Agent修复(15K tokens,3轮完成)
- 会话C(模块检查):基于会话A的摘要,检查另一个模块(12K tokens,2轮完成)
三个会话独立运行,每个都远未达到压缩阈值。总token消耗反而更少,因为避免了重复读取和无效对话。
个人经验总结
别把Claude Code当数据库用。它擅长的是推理和生成,不是存储。需要持久化的信息,写在代码注释里,写在文档里,别指望Agent记住。
每轮对话都要有明确的目标。如果一轮对话结束后,你发现Agent的输出里包含大量你不需要的信息(比如完整的文件内容),说明你的提示词不够精准。下次直接说“只输出修改的部分”,能省一半token。
工具调用是token黑洞。
grep、find、read_file这些操作,能少用就少用。如果必须用,限制输出范围。比如grep -l只输出文件名,而不是匹配内容。异常堆栈要精简。不是所有堆栈都有用。只给最关键的几行——异常类型、错误消息、你的代码调用栈(去掉框架内部的调用)。一个精简后的堆栈可能只有10行,而不是50行。
定期“重启”Agent。我每完成一个子任务,就会用
/new开新会话。虽然看起来麻烦,但长远来看,这比在一个会话里死磕到压缩崩溃要高效得多。最后一条,也是最重要的:永远不要假设Agent记得你十分钟前说过的话。如果你需要它基于之前的上下文工作,要么把关键信息重新说一遍(精简版),要么开一个新会话并手动提供摘要。信任,但要验证。
下篇预告:007、工具链集成:自定义 MCP 服务器、文件系统操作与 Shell 命令执行的安全边界。我会分享如何让Claude Code安全地操作你的生产环境,以及那些“看起来方便但实际是坑”的配置。