Claude code源码精读:上下文管理与自动压缩机制拆解,附TaoToken配置骨架
1. 长会话为什么会突然“失忆”从 token 膨胀说起如果你用 Claude Code 写过稍大一点的项目大概率遇到过这种场景前面聊得好好的突然某次提问后它开始答非所问或者直接甩出一句prompt_too_long之前辛苦对齐的上下文像被橡皮擦抹掉了一样。这不是模型变笨了而是对话的 token 总量撞上了模型上下文窗口的天花板。Claude Code 里负责兜住这个天花板的机制就是autocompact自动压缩。它的定位很明确当对话 token 使用量接近模型上下文窗口上限时自动触发上下文压缩把历史消息折叠成摘要从而避免prompt_too_long报错让长会话能继续跑下去。适合谁看正在做 Agent 长会话、需要理解 token 膨胀治理、或者想自己搭一套类似压缩链路的开发者。这篇不空谈概念我会沿着源码里shouldAutoCompact的判定链路一层层拆到 token 阈值、来源排除、会话内存优先折叠、常规模型压缩回退最后给你一份可复制的settings.json与config.toml配置骨架并给出验证压缩是否真的生效的操作步骤。理解这套机制后你调参时就不会再靠猜。2. 前置准备用 TaoToken 打通模型调用链路要复现和验证压缩行为你得先有一个能稳定调用 Claude 系列模型的入口。我这边用的是 TaoToken它把模型对话、API Key 管理、编码计划这些能力放在一个控制台里配置起来比较省心。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个不加 UTM。你需要提前准备三样东西一个可用的 API Key、确认目标模型名、以及本地 Claude Code 的配置文件路径。API Key 在控制台的 API Keys 页面创建建议单独建一个用于压缩实验的 Key方便后面看调用量。模型对话入口可以用来做单轮验证确认 Key 和模型名对得上。注意压缩实验会产生多次模型调用建议先用小项目或短会话跑通链路再上真实长会话避免 Key 额度被快速消耗。拿到 Key 之后把它写进环境变量别硬编码进配置文件里。下面这一步是后面所有验证的基础。export TAOTOKEN_API_KEYsk-你的key export ANTHROPIC_BASE_URLhttps://taotoken.net/api3. 压缩决策链路shouldAutoCompact 到底在判断什么shouldAutoCompact是整个自动压缩的入口闸门它不是一个简单的“token 超了就压”而是做了多层检查。理解这几层你才能知道为什么有时候明明 token 很高却没触发压缩。第一层是递归防护。源码里排除了session_memory、compact、marble_origami这几个来源。原因很直白压缩过程本身也会发起查询如果不排除压缩触发的查询又触发压缩就会递归死锁。compact是压缩过程自身发起的查询session_memory是会话记忆压缩发起的查询marble_origami是上下文折叠代理启用CONTEXT_COLLAPSE时它的查询也会跳过自动压缩。第二层是功能开关。它会检查DISABLE_COMPACT、DISABLE_AUTO_COMPACT这两个环境变量以及用户设置。也就是说你可以通过环境变量一键关掉压缩这在调试压缩逻辑时非常有用。第三层是实验功能协调。它和REACTIVE_COMPACT、CONTEXT_COLLAPSE等实验功能互斥避免多套压缩机制同时生效打架。第四层才是Token 阈值检查通过getAutoCompactThreshold计算触发阈值。这里的关键点是不同模型的阈值不一样不能用一个固定数字套所有模型。来源枚举这块值得单独列一下因为它直接决定了哪些查询会被排除在压缩之外来源枚举含义是否参与压缩repl_main_thread主 REPL 线程用户交互主入口是repl_main_thread:outputStyle:custom主线程下自定义输出样式是sdkSDK 直接调用的查询是agent:custom用户通过 /agent 启动的自定义代理是agent:builtin:fork内置分叉代理递归防护会检查视情况compact压缩过程自身发起的查询否防递归session_memory会话记忆压缩发起的查询否防递归marble_origami上下文折叠代理否side_question侧边问题子查询否bash_classifierBash 命令分类器否这张表的核心信息是只有主交互链路和代理链路的查询才会被纳入压缩判定辅助性、工具性、压缩自身的查询全部排除。这样设计既避免了递归也避免了把一次性辅助查询的 token 算进主会话预算。4. 优先策略会话内存折叠零延迟的轻量压缩判定需要压缩之后Claude Code 并不是立刻调大模型去总结历史。它有一个优先策略先尝试会话内存压缩session memory compaction对部分会话内存进行折叠并保留完整内容的链接。源码里这段逻辑在autoCompact.ts的 L316-L339 附近核心调用是trySessionMemoryCompaction// EXPERIMENT: Try session memory compaction first const sessionMemoryResult await trySessionMemoryCompaction( messages, toolUseContext.agentId, recompactionInfo.autoCompactThreshold, ) if (sessionMemoryResult) { // ... 清理和通知 return { wasCompacted: true, compactionResult: sessionMemoryResult, } }这个策略的设计优势有三个轻量级基于预计算的会话内存摘要避免调用大模型零延迟本地执行没有 API 调用成本保真度高保留原始消息结构和工具调用链。它的输入是一个消息数组加上agentId和阈值。假设前 10 条消息已经被总结lastSummarizedMessageId指向第 10 条消息的 uuid那么第 11 条开始的消息会被保留const inputMessages: Message[] [ { type: user, uuid: msg-1, message: { content: 你好请帮我写一个Python函数... } }, { type: assistant, uuid: msg-2, message: { content: 当然以下是示例代码... } }, // ... 更多历史消息msg-3 到 msg-10 { type: user, uuid: msg-11, message: { content: 这个函数能再优化一下吗 } }, { type: assistant, uuid: msg-12, message: { content: 可以我们可以添加缓存机制... } }, // ... 直到最新消息 msg-20 ]; const agentId agent-123; const autoCompactThreshold 80000; // 自动压缩阈值80k tokens const result await trySessionMemoryCompaction( inputMessages, agentId, autoCompactThreshold );成功时的输出会带一个边界标记和摘要消息关键字段是压缩前后的 token 数const outputResult: CompactionResult { boundaryMarker: { type: system, uuid: compact-boundary-xyz, message: { id: system_compact, content: 对话已压缩, compactMetadata: { type: auto, preCompactTokenCount: 95000, lastMessageUuid: msg-20, preservedSegmentEndUuid: msg-20, preCompactDiscoveredTools: [] } } }, summaryMessages: [ { type: user, uuid: summary-abc, message: { content: 以下是我们对话的摘要\n\n${truncatedSessionMemory}\n\n完整会话记录可查看/path/to/transcript.json, role: user, isCompactSummary: true, isVisibleInTranscriptOnly: true } } ], attachments: [], hookResults: [/* CLAUDE.md 恢复结果等 */], messagesToKeep: inputMessages.slice(10), preCompactTokenCount: 95000, postCompactTokenCount: 15000, truePostCompactTokenCount: 15000 };注意preCompactTokenCount: 95000到postCompactTokenCount: 15000这个落差压缩比相当可观。摘要内容本身也有截断逻辑flushSessionSection会逐行保留直到累计字符数接近maxCharsPerSection然后追加一行截断标记{ truncatedContent: # Session Title\n_A short and distinctive 5-10 word descriptive title for the session._\n长时间调试会话\n\n# Worklog\n_Step by step, what was attempted, done?_\nStep 1: 初始化项目...\nStep 2: 安装依赖...\n保留前约 8000 字符的行\n[... section truncated for length ...], wasTruncated: true }这个截断标记很重要它告诉模型“这里被裁过”避免模型误以为摘要就是全部内容。5. 回退机制常规模型压缩怎么兜底如果会话内存压缩不适用——比如没有预计算的会话内存摘要或者当前场景不满足折叠条件——就会走常规模型压缩回退对历史消息进行总结降低总 token 数。这条路径和会话内存压缩的区别在于它需要真正调用大模型来做总结因此有 API 成本和延迟但适用面更广。压缩完成后同样会生成包含摘要信息的系统边界标记并保留关键消息确保对话连贯性的同时控制 token 总量。两条路径的取舍逻辑可以这样理解会话内存压缩是“本地折叠 保留链接”快且免费但依赖预计算摘要常规模型压缩是“调模型总结”慢且有成本但几乎总能兜住。实际运行中优先走前者前者不行才走后者。6. 可复制配置骨架settings.json 与 config.toml理解了链路接下来是能直接抄的配置。Claude Code 的压缩行为受环境变量和用户设置双重影响下面这份骨架把关键开关都列出来了。settings.json骨架{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的key, DISABLE_AUTO_COMPACT: 0, DISABLE_COMPACT: 0 }, autoCompact: { enabled: true, thresholdRatio: 0.85, preserveRecentMessages: 10, maxCharsPerSection: 8000 } }config.toml骨架[model] name claude-sonnet context_window 200000 [compact] auto true threshold_ratio 0.85 preserve_recent 10 max_chars_per_section 8000 session_memory_first true [compact.experimental] reactive_compact false context_collapse false几个参数的含义thresholdRatio控制触发阈值占上下文窗口的比例0.85 表示用到 85% 时触发preserveRecentMessages对应源码里的messagesToKeep保留最近 N 条不压缩maxCharsPerSection对应flushSessionSection的单节字符上限session_memory_first决定是否优先走会话内存折叠。注意DISABLE_AUTO_COMPACT和DISABLE_COMPACT设为1会关闭压缩调试时可以用它对比压缩前后的行为差异但生产环境别关否则长会话必炸。7. 验证压缩是否生效三步操作配置写完怎么确认压缩真的触发了给你三步可跟做的验证。第一步开一个长会话持续对话直到 token 接近阈值。你可以用一段循环脚本灌入大量文本观察是否出现prompt_too_long。如果压缩生效会话应该能继续而不是报错中断。第二步检查输出里是否出现压缩边界标记。压缩成功后会生成system_compact类型的边界消息compactMetadata.type为auto并带有preCompactTokenCount和postCompactTokenCount。你可以把响应日志打到文件里用 grep 过滤grep -n system_compact\|preCompactTokenCount\|postCompactTokenCount claude_session.log第三步对比压缩前后的 token 数。如果postCompactTokenCount明显小于preCompactTokenCount说明压缩链路走通了。如果两者接近可能是阈值没到或者压缩被环境变量关掉了。# 统计压缩事件次数 grep -c compactMetadata claude_session.log # 查看最近一次压缩的 token 落差 grep postCompactTokenCount claude_session.log | tail -18. 常见报错排查报错一prompt_too_long依然出现。先确认DISABLE_AUTO_COMPACT和DISABLE_COMPACT没被设成1。再检查thresholdRatio是不是设得太高比如 0.98导致压缩还没来得及触发就已经超窗。调到 0.8 到 0.85 之间比较稳。报错二压缩后模型答非所问。大概率是preserveRecentMessages设得太小最近的关键上下文被压掉了。把它从 10 调到 15 或 20 试试。另外检查maxCharsPerSection是不是太小导致摘要被截断得太狠。报错三压缩反复触发会话卡顿。这通常是递归防护没生效或者实验功能互斥没配好。确认reactive_compact和context_collapse没有和auto同时开启。源码里shouldAutoCompact会排除compact、session_memory、marble_origami来源如果你的自定义代理来源没被正确标记就可能绕过防护。报错四会话内存压缩一直不生效总是走模型压缩。检查是否有预计算的会话内存摘要。trySessionMemoryCompaction依赖lastSummarizedMessageId指向的已总结消息如果这个指针缺失或失效就会回退到模型压缩。确认session_memory_first为true。9. 继续深入与接入入口压缩机制调通之后下一步通常是把它接进真实的编码工作流。如果你要长期跑编码任务或 Agent可以看 Coding Plan 把压缩策略和编码计划结合起来https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。想先单轮验证模型对压缩摘要的理解是否准确用模型对话入口最快https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。需要管理多个实验用 Key、看调用量去控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。创建和管理 Key 在 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。接入细节和参数说明查文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你在用 Claude Code 的 Anthropic 兼容模式这个页面有专门说明https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite 。我自己的习惯是先用模型对话确认摘要质量再把thresholdRatio和preserveRecentMessages固定下来最后才上 Coding Plan 跑长任务。压缩参数没有万能值得按你的会话长度和任务类型微调跑几轮日志对比比看任何文档都管用。