
1. 长会话为什么突然变慢从一次 200K 上下文被撑爆说起如果你用 Claude Code 跑过稍微复杂点的重构任务大概率遇到过这种情况前半小时对话流畅读文件、跑测试、改代码都正常突然某一次请求开始卡顿接着报Prompt Too Long或者响应质量断崖式下跌开始忘记你十分钟前说过的约束。这不是网络问题也不是模型变笨了而是上下文窗口被塞满了。Claude Code 的上下文窗口标称 200K token听起来很宽裕。但一个中等复杂度的编程 session 消耗速度远超直觉读十几个源文件、跑几轮 grep 搜索、执行几次测试命令每个工具结果动辄几千到几万 token几轮下来就能吃掉大半窗口。真正的问题在于这些工具结果里绝大部分是一次性的——你读完一个文件、确认了某个函数签名那段原始内容就再也不会被用到但它依然死死占着上下文空间。Claude Code 在src/services/compact/目录下用超过 3960 行 TypeScript 代码构建了一套五级渐进式压缩流水线核心设计哲学只有一句话能用轻量手段解决的绝不动用重武器。Cheapest first, heaviest last。这套机制对需要控制长会话成本的开发者来说理解它的触发条件和 Token 预算分配直接决定了你的 API 账单和任务成功率。本文会拆解这五层压缩的工程实现给出可复制的压缩阈值配置片段并说明如何通过 TaoToken 统一 Key/API 通道观察各层压缩前后的 Token 消耗变化。适合正在用 Claude Code 做长程编码任务、或者想在自己的 Agent 系统里复刻类似压缩策略的开发者。2. 五层压缩的触发条件与 Token 预算分配Claude Code 上下文压缩机制逐层拆解先看整体架构。五层压缩构成一个漏斗每一层成功就阻止下一层触发在最小化信息损失的前提下最大化上下文利用率。前置压缩链在每次 API 调用前评估被动恢复链只在 API 报错后才触发。第一层是 Tool Result Budget解决单个工具吐出几 MB 内容瞬间撑爆上下文的问题。触发条件是单条工具结果超过 50000 字符DEFAULT_MAX_RESULT_SIZE_CHARS或单轮并行工具结果总量超过 200000 字符。处理方式不是截断——截断等于永久丢失——而是把完整结果写磁盘上下文只保留约 2KB 预览加文件路径persisted-output Output too large (2.3 MB). Full output saved to: /tmp/.claude/session-xxx/tool-results/toolu_abc123.txt Preview (first 2.0 KB): [前 2000 字节内容] ... /persisted-output这层的精妙之处在于可恢复模型后续真需要那段内容可以用 Read 工具从磁盘取回。而 Read 工具把自己的阈值设成无穷大避免读文件→存文件→再读文件的死循环。零成本、零信息损失这是对话脚手架垃圾回收的最前端防线。第二层是 Snip 历史剪除处理对话进行中积累的对话脚手架——重复的 assistant 回复框架、系统内部记账元数据、早期已完成的任务标记。处理方式是纯内存数组操作零 API 调用不做摘要只删除冗余条目移除完全重复的用户/助手消息、截断超长工具输出超过 100 字符保留前 100、删除空消息、合并连续相似的助手消息。这里有个极其容易被忽略的细节Snip 删除消息后会把snipTokensFreed本次剪除释放的 token 数记录下来传递给后续层级。如果不校正AutoCompact 在计算当前上下文用了多少 token时会拿到剪除前的旧数据可能在已释放空间的情况下仍强行触发全量摘要白白浪费一次 LLM 调用和费用。用户体验上REPL 侧仍保留完整消息用于 UI 滚动展示但发给 API 的消息载荷已被剪除——你看到的聊天记录是完整的模型收到的上下文是剪裁过的。第三层是 MicroCompact 微压缩处理工具调用产生的海量tool_result。这层无条件执行每次 API 调用前内部根据冷热缓存与时间阈值自动决策有两种互斥触发模式。时间窗模式检查每条tool_result的时间戳距离当前对话超过 60 分钟的工具结果替换为简短占位符[old tool result cleared]原因是 Anthropic API 的 Prompt Cache TTL 是 1 小时缓存过期后重写前缀也是冷的不如趁机清空。Cache 编辑模式利用cache_edits能力在服务端缓存中精准删除旧工具结果本地消息完全不动不改变缓存前缀的哈希特征只切除内部冗余保住 prompt cache 命中率。这层的设计哲学是压缩决策不仅要看 token 数还要看成本结构。cache_edits保全了缓存前缀省了 token 空间的同时不亏钱。第四层是 Context Collapse 上下文折叠。前三层解决的是删除垃圾但当上下文真正由有效历史对话组成时需要对对话本身进行结构化重组。处理方式是把历史消息切分成多个片段每个片段独立生成一份摘要用这些摘要替换掉原始消息保留比全量摘要更细粒度的上下文结构——每段都有自己的主题标记。执行特点是后台异步执行用户完全感知不到压缩的发生。如果折叠后 token 已达标第五层 AutoCompact 就不会被触发。传统全文摘要的致命缺点是压缩时会阻塞用户等 3 到 5 秒而 Context Collapse 把过程拆解成多个小任务在后台排队执行。第五层是 AutoCompact 自动压缩前四层都无法把上下文降到安全水位以下时的最后手段。触发阈值计算方式是有效上下文窗口 200000 - 20000摘要预留- 13000安全缓冲≈ 167000 token当上下文使用超过约 80%约 167K时触发。处理方式通过 forked agent子代理执行继承主会话的 system prompt、tools 和完整消息历史末尾追加压缩指令。摘要 prompt 采用两阶段结构先一个 analysis 思考块让模型按时间线梳理对话再生成正式摘要analysis 块最后会被删掉纯粹是提高摘要质量的草稿纸。工程上有个难题fork 继承了主会话的完整工具集模型有时会忍不住想调工具。在只允许一轮的限制下一次被拒绝的工具调用意味着没有文本输出整个压缩就废了。所以压缩 prompt 开头有一段非常强硬的声明CRITICAL: Respond with TEXT ONLY. DO NOT call any tools. Tool calls will be REJECTED and will waste your only turn -- you will fail the task.这个问题在 Sonnet 上尤其明显失败率约 2.79%。因此系统设有熔断机制连续失败 3 次自动熔断停止 AutoCompact防止无限循环的资源浪费。曾经有上千个 session 出现过 50 次以上连续压缩失败全局每天浪费约 25 万次 API 调用。兜底层是 Reactive Compact 响应式压缩当 API 报错PTL: Prompt Too Long后才触发。首选将对话写入长期记忆流Session Memory 模式走投无路时冻结界面新建子任务让原生 Claude 生成摘要仅当彻底失败时才抛出错误。各层成本与可逆性对照如下层级名称成本可逆性核心手段L0Tool Result Budget$0完全可恢复落盘预览L1Snip 历史剪除$0不可逆数组删除L2MicroCompact$0部分可逆cache_edits/时间清除L3Context Collapse$0折叠可回滚分段摘要L4AutoCompact$$$不可逆LLM 全量摘要L5Reactive Compact$$$不可逆紧急救援实测结论是绝大多数对话根本走不到最后一层前两层就能腾出足够空间。还有个永不压缩的特权CLAUDE.md 永远不会被压缩它每次从磁盘重新读取你写在里面的项目约定、架构决策、编码规范不管 session 怎么压缩都不会丢。这也是社区反复强调把最重要的规则写在 CLAUDE.md 里的根本原因。3. 可复制的压缩阈值配置片段settings.json 与 TypeScript 关键路径理解了机制接下来是能直接落地的配置。Claude Code 的压缩行为可以通过settings.json调整阈值路径通常在~/.claude/settings.json或项目级.claude/settings.json。下面是一份可复制的配置片段把各层触发阈值显式化方便你观察和调优{ compact: { toolResultBudget: { maxResultSizeChars: 50000, maxMessageAggregateChars: 200000, previewBytes: 2048, persistDir: /tmp/.claude/session-{sessionId}/tool-results }, snip: { enabled: true, truncateToolOutputOverChars: 100, keepFirstChars: 100, mergeSimilarAssistantMessages: true }, microCompact: { enabled: true, timeWindowMinutes: 60, useCacheEdits: true, placeholder: [old tool result cleared] }, contextCollapse: { enabled: true, segmentSizeTokens: 8000, async: true }, autoCompact: { enabled: true, contextWindowTokens: 200000, summaryReserveTokens: 20000, safetyBufferTokens: 13000, triggerRatio: 0.8, circuitBreakerFailures: 3 } } }这份配置里几个关键参数值得说明。maxResultSizeChars控制第一层落盘阈值如果你经常读大文件又需要完整内容可以适当调高但要注意上下文消耗。timeWindowMinutes对应第三层时间窗模式默认 60 分钟与 Prompt Cache TTL 对齐不建议随意改动否则可能破坏缓存命中率。triggerRatio是第五层触发比例0.8 对应约 167K token调低会更早触发全量摘要更贵但更安全调高则更省但风险更大。如果你在自己的 TypeScript Agent 项目里复刻这套机制关键代码路径可以参考这个结构。压缩流水线的入口通常是一个compactIfNeeded函数按顺序评估各层// src/services/compact/pipeline.ts interface CompactContext { messages: Message[]; tokenCount: number; snipTokensFreed: number; sessionId: string; } async function compactIfNeeded(ctx: CompactContext): PromiseCompactContext { // L0: Tool Result Budget - 落盘替代 ctx await applyToolResultBudget(ctx); if (isUnderBudget(ctx)) return ctx; // L1: Snip - 数组剪除记录释放量 const snipResult applySnip(ctx.messages); ctx.messages snipResult.messages; ctx.snipTokensFreed snipResult.tokensFreed; ctx.tokenCount - snipResult.tokensFreed; if (isUnderBudget(ctx)) return ctx; // L2: MicroCompact - 时间窗或 cache_edits ctx await applyMicroCompact(ctx); if (isUnderBudget(ctx)) return ctx; // L3: Context Collapse - 异步分段摘要 ctx await applyContextCollapse(ctx); if (isUnderBudget(ctx)) return ctx; // L4: AutoCompact - 全量摘要带熔断 if (circuitBreaker.isOpen()) return ctx; ctx await applyAutoCompact(ctx); return ctx; }注意snipTokensFreed的传递这是前面强调过的记账准确性细节。如果你自己实现务必在 Snip 之后校正 token 计数否则 AutoCompact 会误判。配置好之后怎么观察各层压缩前后的 Token 消耗变化这里就要用到 TaoToken 的统一 Key/API 通道。TaoToken 提供统一的 API 入口你可以在控制台看到每次请求的 token 消耗明细配合 Claude Code 的压缩日志就能定位到底是哪一层在起作用、省了多少 token。接入方式很简单先拿到 API Key。访问 https://taotoken.net/api-keys 创建 Key然后在 Claude Code 的环境变量里配置export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的TaoToken密钥如果你用的是 Claude Code 的配置文件方式可以在~/.claude/settings.json里加上{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的TaoToken密钥 } }这里三件套要写全Base URL 是https://taotoken.net/apiKey 是你在控制台创建的Model ID 按你实际使用的填比如claude-sonnet-4-20250514或claude-opus-4-20250514。配置完成后每次 Claude Code 发起请求TaoToken 控制台都会记录 token 消耗你就能对照压缩日志看效果。4. 逐层验证请求与成功结果观察压缩前后的 Token 消耗变化配置好之后怎么验证每一层真的在按预期工作我试过一套逐层验证的动作你可以照着操作。先验证第一层 Tool Result Budget。找一个明显会产出大结果的操作比如让 Claude Code 读取一个几 MB 的日志文件或者跑一个输出量很大的命令# 在 Claude Code 里执行 请读取 /var/log/syslog 并告诉我最后 10 行如果文件足够大你应该能在响应里看到persisted-output标签里面包含文件路径和 2KB 预览。同时去/tmp/.claude/session-{sessionId}/tool-results/目录下能看到完整的落盘文件。这一步成功的结果是上下文里只有约 2KB 预览完整内容在磁盘上可随时取回。验证第二层 Snip需要观察长对话中的消息数组变化。Claude Code 的调试日志里会打印snipTokensFreed字段。你可以开一个长 session反复让 Claude 读文件、跑命令然后在日志里搜索这个字段。如果看到非零值说明 Snip 在起作用。注意 REPL 界面上的聊天记录不会变少这是预期行为——剪除只作用于发给 API 的载荷。验证第三层 MicroCompact关键是看时间窗和 cache_edits。时间窗模式需要对话持续超过 60 分钟你可以挂一个长 session一小时后观察旧的tool_result是否被替换成[old tool result cleared]。cache_edits 模式更隐蔽它不改变本地消息你需要通过 TaoToken 控制台观察请求的 token 数是否下降同时 prompt cache 命中率是否保持。验证第四层 Context Collapse这层是异步的日志里会有折叠任务的排队和执行记录。你可以观察折叠后 token 是否降到阈值以下以及 AutoCompact 是否被跳过。如果折叠成功你应该看到 AutoCompact 的触发计数没有增加。验证第五层 AutoCompact需要把上下文推到 167K 以上。这比较费 token建议在测试环境做。触发后日志里会有 forked agent 的执行记录以及两阶段摘要的 analysis 块。成功的结果是上下文被压缩成一篇摘要token 数大幅下降任务可以继续。通过 TaoToken 控制台你可以把每次请求的 token 消耗和压缩日志对照起来。比如某次请求前 token 是 180K请求后变成 60K中间发生了什么对照日志就能看到是 AutoCompact 触发了全量摘要。这种可观测性对控制长会话成本非常关键。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth 报错配置和验证过程中最容易踩的坑集中在认证和网络层。下面按真实报错逐个排查。401 Unauthorized。这是最常见的认证失败。如果你在 Claude Code 里看到 401先检查ANTHROPIC_API_KEY是否正确设置。常见错误是 Key 复制时带了空格或者用了过期的 Key。用 TaoToken 的话去 https://taotoken.net/api-keys 确认 Key 状态是否正常。另外注意ANTHROPIC_BASE_URL必须指向https://taotoken.net/api如果漏了/api路径请求会打到错误端点也可能返回 401。local proxy failed。这个报错通常出现在网络层。检查你的环境变量里是否有残留的代理配置比如HTTP_PROXY或HTTPS_PROXY。如果有先清掉unset HTTP_PROXY unset HTTPS_PROXY unset ALL_PROXY然后确认ANTHROPIC_BASE_URL设置正确。如果还是失败检查 DNS 解析是否正常以及本地防火墙是否拦截了出站请求。reading choices 报错。这个错误通常出现在响应解析阶段提示读取choices字段失败。原因可能是 API 返回了非预期格式比如错误响应被当成正常响应解析。排查方法是打开调试日志看原始响应内容。如果是 TaoToken 通道确认 Model ID 填写正确比如claude-sonnet-4-20250514写错了模型名可能导致返回格式异常。OAuth 相关报错。如果你用的是 Claude Code 的 OAuth 登录方式而不是 API Key可能会遇到 token 过期或刷新失败。这种情况下建议切换到 API Key 方式配置更稳定。在settings.json里确保ANTHROPIC_API_KEY存在并且没有同时启用 OAuth 配置两者冲突会导致认证混乱。还有一个容易忽略的点如果你同时配置了ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY但 Claude Code 版本较旧可能不识别这些环境变量。确认你的 Claude Code 是最新版本旧版本可能只支持 OAuth 或特定的配置路径。排查时建议按这个顺序先确认 Key 有效再确认 Base URL 正确然后清掉代理最后看 Model ID。大部分问题都出在前两步。6. 把压缩策略用起来从观察到调优的下一步理解了五层压缩机制接下来最有价值的动作是把它变成可观测、可调优的。你可以先从 TaoToken 控制台拿到 token 消耗基线然后针对自己的任务类型调整压缩阈值。比如做长程重构任务可以把triggerRatio调到 0.75让 AutoCompact 更早介入避免任务中途因为上下文溢出而失败做短平快的问答保持默认 0.8 即可省 token。如果你在自己的 Agent 项目里复刻这套机制建议先实现前两层——Tool Result Budget 和 Snip这两层零成本、收益最大能解决大部分上下文膨胀问题。然后再逐步加上 MicroCompact 和 Context Collapse。AutoCompact 作为最后手段一定要配熔断否则连续失败会烧掉大量 API 调用。想深入看压缩过程中的 token 变化可以用 TaoToken 的模型对话功能做小规模测试观察不同压缩策略下的消耗差异。长期跑编码任务或 Agent 的话Coding Plan 能提供更稳定的通道和成本控制。接入文档在 https://taotoken.net/doc 有完整的配置说明遇到认证或通道问题可以先查文档再排查。最后留一个实用技巧把项目里最重要的约定写进 CLAUDE.md因为它永远不会被压缩。压缩机制再精巧也不如从源头减少无效上下文。