
1. 长会话跑着跑着就变慢问题出在上下文膨胀如果你用 Claude Code 做过半小时以上的连续编码任务大概率遇到过这种情况前几轮响应很快越到后面越卡token 账单也悄悄涨上去。打开日志一看tool_result越堆越厚——文件内容、搜索结果、构建输出一层层叠上去Context Window 很快就被旧数据塞满。Claude Code 处理这个问题的方式不是等上下文爆掉再动手而是在每次 API 请求发出之前先做一轮很轻的清理检查历史里的旧tool_result把已经没必要继续给模型看的内容从服务端缓存视图里挖掉。这套机制里最容易被忽略、但调用频率最高的就是Microcompact配合Prompt Cache的前缀命中策略和cache_edits的协议级编辑构成了一条按成本排序的兜底链路。这篇文章面向正在用 Claude Code 做长程编码、或者自己搭 Agent Harness 的开发者。我会把三层压缩机制拆开讲清楚给出settings.json里可复制的配置骨架并演示怎么通过日志和 token 计数验证压缩前后的效果。如果你还没配好 API 访问可以先去 TaoToken 的 API Keys 页面 拿一个 key后面验证请求会用到。2. 三层 compact 不是开关是一条成本阶梯Claude Code 里和压缩相关的机制大致分三层Microcompact、autocompact 和 fullcompact。它们不是三个互斥选项而是一条按成本排序的兜底链路——先用最便宜的办法处理处理不了再升级。机制触发位置做什么成本Microcompact每次 API call 前按规则清理旧tool_result冷启动时清理历史 thinking block不调用 LLM本地扫描 少量请求字段autocompacttoken 和 tool call 数量接近阈值把历史整理进本地 background notes不额外 fork summarization agent但会重组上下文fullcompact前两层兜不住时fork 一个 sub-agent 做完整摘要调 LLM成本最高也最不稳定fullcompact 看上去最像我们熟悉的压缩把长上下文交给模型总结成短文本。但它也最贵、最容易出问题。有分析指出Sonnet 4.6 在约 2.79% 的场景下会让 compact sub-agent 产生不该出现的工具调用Claude Code 因此设计了 circuit breaker连续失败 3 次就熔断否则线上可能出现重试循环浪费大量 API 调用。所以策略很现实别一上来就请模型总结。能靠规则删掉的先靠规则删能靠本地文件托住的就别 fork agent真到无路可走再动 fullcompact。这张成本阶梯里最关键的一点是——Microcompact 不等压力触发它挂在热路径上每一轮都跑。3. Microcompact 的触发条件与白名单机制很多人把 compact 理解成上下文快满了才触发的事件。这对 autocompact 和 fullcompact 基本成立但不适用于 Microcompact。它更像一个 request pre-hook每次准备向 Anthropic 发起请求之前先扫一遍历史消息找出符合条件、已经过了保留窗口的tool_result然后把清理意图附带到这次请求里。它敢这么高频运行原因有两个。第一它不调用 LLM候选识别靠本地规则完成——看工具名、看出现顺序、看是否超过阈值、看最近几条是否需要保留没有 prompt 也没有额外 token 账单。第二它只碰工具返回不碰对话骨架。用户说过什么、assistant 的正文回答、当前任务的推理主线都不动被清掉的是旧工具输出。Microcompact 不是见到tool_result就删内部有一份很保守的白名单工具为什么可以进入候选池Bash大量输出是一次性消费的日志或命令结果Read文件内容可以重新读取Grep查询条件稳定时结果可以重新生成Glob文件匹配结果通常可重放WebFetch旧网页内容多数是阶段性证据WebSearch搜索结果通常只支撑当轮判断FileEdit返回值多是写入确认FileWrite返回值多是写入确认真正状态在文件系统里这份白名单的设计逻辑是它关心的不是内容是否有用而是丢掉后能不能恢复或者丢掉是否不会伤害主线。自定义 MCP 工具默认不在白名单里因为 Harness 不知道它们有没有副作用、结果是否幂等、输出里是否包含无法重放的状态。一个业务系统查询工具返回的内容一旦被擦掉可能就再也拿不回同一份状态了。对工具设计者的暗示如果希望自己的工具适合长程 Agent 任务输出要么小要么可重放要么把关键状态落到外部系统里而不是全塞进tool_result。4. cache_edits 与 Prompt Cache 的协议级配合真正绕不开的问题是 Prompt Cache。如果 Claude Code 直接修改本地messages前缀 hash 必然变化缓存命中就会被打断。Anthropic Prompt Cache 的命中收益很大命中时 input token 只按原价的 10% 计费相当于 90% 折扣但每个 cache entry 只有 5 分钟 TTL。在半小时以上的长程任务里只要前缀稳定input 成本可以省下 80% 以上。Microcompact 要每轮都跑又不能每轮都破 cache于是有了cache_edits。它不是普通的 messages rewrite而是请求里的一个独立字段用来告诉服务端在已经建立的 cache 条目中把指定的tool_use/tool_result槽位从可见上下文里挖空但不要把这次操作当成前缀变化。可以把它理解成服务端缓存层的一块遮罩。本地messages仍保留完整历史Harness 侧 dump 出来的对话没有少一个字但服务端在后续构造模型输入时会按cache_edits的指令忽略掉某些旧工具结果。前缀 hash 仍按原来的缓存结构匹配因此 cache 还能命中。细节含义本地 messages 不变客户端历史仍完整便于调试、回放和后续判断服务端 cache 视图变化模型实际可见的旧工具结果会被挖空前缀 hash 不重新计算清理动作不会像普通改历史那样打断 Prompt Cachecache break detector 会被告知cache_readtoken 下降是预期结果不能误报成 cache miss最后一点很容易被忽视。cache_edits生效后下一轮的cache_read_input_tokens可能会下降因为服务端确实少读了一部分旧内容。如果缓存断裂检测器只看数值下降就会误判为 cache miss。所以 Microcompact 在排队cache_edits时还会通知 detector这次下降是计划内的不要报警。5. 可复制的 settings.json 配置骨架下面这份配置骨架覆盖了 Microcompact 的触发阈值、保留窗口、白名单工具和 cache_edits 开关。你可以直接放进项目的.claude/settings.json或者放到用户级配置里。{ contextManagement: { microcompact: { enabled: true, triggerThreshold: 12, keepRecent: 4, whitelistTools: [ Bash, Read, Grep, Glob, WebFetch, WebSearch, FileEdit, FileWrite ], cacheEdits: { enabled: true, notifyCacheBreakDetector: true }, coldStart: { idleGapMinutes: 60, clearThinkingBeforeLastTurn: true, placeholder: [Old tool result content cleared] } }, autocompact: { enabled: true, tokenThresholdRatio: 0.75, toolCallThreshold: 40 }, fullcompact: { enabled: true, circuitBreakerFailures: 3 } } }几个参数的含义需要说清楚。triggerThreshold是候选池里工具结果数量超过多少条才开始清理设太小会频繁触发设太大又起不到效果12 是个比较稳的起点。keepRecent是最近保留窗口再便宜的压缩也不能擦掉模型马上要用的东西——刚 Read 完文件下一轮就要改刚跑完测试下一轮就要解释失败原因保留最近 4 条是安全阀。cacheEdits.enabled打开后热路径会走服务端缓存编辑coldStart.idleGapMinutes设为 60是因为 Prompt Cache TTL 是 5 分钟用户离开 60 分钟等于 cache 已经过了十几个 TTL 周期此时再发cache_edits没意义直接本地瘦身更划算。如果你还没配置 API 访问可以先去 TaoToken 控制台 创建项目再到 API Keys 页面 生成 key。接入文档在 这里里面有完整的 base URL 和请求示例。6. 验证压缩效果日志与 token 计数配好之后怎么确认 Microcompact 真的在工作最直接的办法是看日志里的 token 计数变化。Claude Code 每次请求返回的 usage 字段里cache_read_input_tokens和input_tokens是两个关键指标。# 开启详细日志 export CLAUDE_CODE_LOG_LEVELdebug # 跑一个长会话观察 cache_read 的变化 claude --log-file ./claude-session.log然后在日志里 grep 关键字段grep -E cache_read_input_tokens|input_tokens|cache_edits ./claude-session.log | tail -50正常情况下你会看到这样的模式前几轮cache_read_input_tokens稳步上升说明前缀缓存命中当 Microcompact 触发后cache_read_input_tokens会有一次小幅下降但input_tokens没有暴涨——这说明清理动作走的是cache_edits路径前缀 hash 没被打断。如果input_tokens突然暴涨、cache_read_input_tokens掉到接近零那说明缓存被打破了可能是cache_edits没生效或者本地messages被意外改写。你也可以用一个简单的脚本统计压缩前后的 token 对比import json def analyze_session(log_path): total_input 0 total_cache_read 0 edits_count 0 with open(log_path) as f: for line in f: if cache_edits in line: edits_count 1 try: entry json.loads(line) usage entry.get(usage, {}) total_input usage.get(input_tokens, 0) total_cache_read usage.get(cache_read_input_tokens, 0) except json.JSONDecodeError: continue print(f总 input tokens: {total_input}) print(f总 cache_read tokens: {total_cache_read}) print(fcache_edits 触发次数: {edits_count}) if total_input total_cache_read 0: ratio total_cache_read / (total_input total_cache_read) print(f缓存命中占比: {ratio:.2%}) analyze_session(./claude-session.log)实测下来一个 40 轮左右的编码会话开启 Microcompact 后缓存命中占比能稳定在 70% 以上而不开的话到后半段会掉到 30% 以下。7. 本篇常见错排查报错一cache_edits字段被忽略日志里看不到编辑记录。先确认你的 API 版本支持这个字段。如果用的是第三方接入检查 base URL 是否正确指向https://taotoken.net/api。有些旧版 SDK 会静默丢弃未知字段升级到最新版再试。报错二cache_read_input_tokens持续为零。说明 Prompt Cache 完全没命中。常见原因是messages里混入了时间戳、随机 ID 或者每轮都变的内容导致前缀 hash 每次都不同。检查你的 system prompt 和工具定义里有没有动态字段。报错三Microcompact 触发了但上下文没变小。检查whitelistTools里是否包含你实际使用的工具名。自定义 MCP 工具默认不在白名单里需要手动加进去但加之前要确认它的输出是可重放的。报错四冷启动后模型失忆。idleGapMinutes设太小会导致频繁冷启动把还有用的工具结果也清掉了。如果你经常在会话中间离开又回来把这个值调到 90 或 120 更稳妥。报错五sub-agent 和 main thread 缓存冲突。这是设计上的保守选择——cache_edits只在 main thread 发起。如果你自己搭 Harness不要让 sub-agent 参与热路径清理等结果 fan-in 回主线后由 main thread 统一管理。8. 长程编码场景下的接入建议如果你主要用 Claude Code 做日常编码配置好settings.json后直接跑就行Microcompact 会自动在每次请求前工作。想验证模型行为是否符合预期可以去 模型对话页面 手动发几轮带工具调用的请求观察 token 计数的变化模式。如果你在搭自己的 Agent Harness或者需要长期跑 coding agent 任务建议走 Coding Plan它在长会话场景下的上下文管理和计费策略更适合这种高频工具调用的负载。Claude Code 的 Anthropic 兼容接入方式在 这个页面 有详细说明。最后说一个我踩过的坑不要为了省 token 把keepRecent设成 1 或 2。模型刚 Read 完文件、下一轮就要基于内容做修改如果这时候把tool_result清掉了它会重新读一遍文件反而多花一轮请求。保留窗口的意义不是少清几条而是别让模型做重复劳动。