窥探Claude Code源码:Context上下文管理机制

2026 年 3 月 31 日凌晨,Claude IDE 的 51 万行核心源代码泄露,让所有人得以一窥 Anthropic 自研 AI Agent 的完整设计逻辑与实现思路。

作为IT开发从业者,更应充分了解Claude IDE 的设计、工作原理,有助于更好的掌控AI Coding技巧,同时学会如何节省Token消耗、降低使用成本。

1. 导读

在长生命周期的对话与自动化任务中,LLM 的上下文窗口(Context Window)始终是稀缺资源。Claude Code 并非简单地采用"超过截断点就丢弃历史"的暴力做法,而是构建了一整套复杂的监控、预测和自动压缩(Auto-Compact)机制。

本文将从源码层面拆解这套机制的每一个核心设计细节,让你彻底搞懂 Claude Code 背后的上下文管理逻辑;并基于这些底层原理,总结出一套可直接落地的节省 Token 实用习惯,帮助你在日常使用中避免无效消耗、显著降低 AI 编码成本。

2. 上下文额度的分配与基线

2.1 动态 Context 窗口边界

Context 窗口是什么?

  • 可以把模型的 Context 窗口想象成一个 "临时背包"------ 模型在处理对话时,会把历史对话、代码、提示词等内容都放进这个背包里,背包的容量越大,能记住的信息就越多。

背包不是全给你用的!

  • 系统并不是将所有的模型 Context 窗口都开放给 Agent 自由写入,可以理解为不是把整个背包都开放给 Agent(智能体)随便用,而是要提前预留一部分空间。
typescript 复制代码
// 默认上下文窗口设为 200k(Claude 3 系默认值)
export const MODEL_CONTEXT_WINDOW_DEFAULT = 200_000

// 针对 [1m] 或特性开启的模型使用百万级上下文
// 如果模型名字里带 [1m](比如 Claude 3 Opus [1m]),背包容量直接升级到 100 万 Tokens
export function has1mContext(model: string): boolean {
  return /\[1m\]/i.test(model)
}

// 必须预留的 "应急空间"
// 系统要从背包里提前划走 2 万 Tokens 的 "应急空间",
// 专门留给 "Summary API"(对话压缩工具)。
const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000

// 你实际能用多少空间?
// 计算有效可用窗口:总窗口 - 为 Summary 预留的 token
export function getEffectiveContextWindowSize(model: string): number {
   // 先算预留多少:取"模型最大输出"和"2万"里的较小值
  const reservedTokensForSummary = Math.min(
    getMaxOutputTokensForModel(model),
    MAX_OUTPUT_TOKENS_FOR_SUMMARY,
  )
  // 再拿背包总容量
  let contextWindow = getContextWindowForModel(model)

  // 支持环境变量硬覆盖(比如你想手动改预留空间)
  const autoCompactWindow = process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW
  
  // 最终可用空间 = 总容量 - 预留空间
  return contextWindow - reservedTokensForSummary
}

你实际能用的背包空间 = 背包总容量(20 万 / 100 万)- 预留的应急空间(最多 2 万)

设计要点

  • 当对话越来越多,背包快被塞满时,系统会触发 "Auto Compact"(自动压缩)------ 用 Summary API 把旧对话压缩成一个简短的摘要,腾出空间放新内容。

  • 但压缩过程需要两步:

    1. 先把旧对话暂时放进去;
    2. 再把 "帮我压缩这段对话" 的提示词放进去。
  • 如果背包被塞得满满当当,连这两步的空间都没有,压缩就会失败。所以系统必须提前预留 2 万 Tokens,确保压缩时能 "转得开身"。

3. Auto-Compact 触发策略

  • 想象你的 Context 窗口是一个背包,对话历史、代码、图片都是往里装的东西。东西装多了,背包会满 ------ 这时候就需要 Auto-Compact(自动整理):把旧东西压缩成 "摘要",腾出空间装新东西。

  • 但整理过程中可能遇到两个麻烦:

    • 麻烦 1:等背包全满了才整理,发现连 "伸手整理" 的空间都没了(溢出);
    • 麻烦 2:如果背包里是一块 "根本塞不下的大石头"(比如一张超大图片),再怎么整理也没用,还会反复尝试浪费资源(死循环)。
typescript 复制代码
export async function autoCompactIfNeeded(
  messages: Message[],
  toolUseContext: ToolUseContext,
  cacheSafeParams: CacheSafeParams,
  querySource?: QuerySource,
  tracking?: AutoCompactTrackingState,
  snipTokensFreed?: number,
): Promise<{ wasCompacted: boolean; compactionResult?: CompactionResult }> {
  
  // 第一步:先查 "保险丝"(熔断机制)
  // 熔断器:如果连续压缩失败 3 次,直接 "拉闸断电"------ 不再尝试压缩。
  // 为什么?因为遇到了 "不可恢复的问题"(比如那块 "塞不下的大石头"),
  // 再试也是浪费 API 额度。
  if (tracking?.consecutiveFailures !== undefined &&
      tracking.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) {
    return { wasCompacted: false }
  }

  const model = toolUseContext.options.mainLoopModel
  
  // 第二步:判断 "要不要整理"(缓冲界限)
  // 不是等背包全满了才整理,而是 提前 13000 个 Token 就触发。
  // 比如背包总容量 20 万 Token,用到 18.7 万(20 万 - 1.3 万)时就开始整理
  // 留足 "伸手整理" 的空间,避免真的溢出。
  const shouldCompact = await shouldAutoCompact(messages, model, querySource, snipTokensFreed)

  if (!shouldCompact) {
    return { wasCompacted: false }
  }

  // 第三步:尝试整理,成功 / 失败分开处理
  try {
  
    // 尝试压缩对话
    const compactionResult = await compactConversation(
      messages,
      toolUseContext,
      cacheSafeParams,
      true, 
    )
    return { wasCompacted: true, compactionResult, consecutiveFailures: 0 }
  } catch (error) {
    
    // 失败:增加失败计数
    const nextFailures = (tracking?.consecutiveFailures ?? 0) + 1
    return { wasCompacted: false, consecutiveFailures: nextFailures }
  }
}

双重保证的设计巧思

  1. 缓冲界限 :在上下文快满时前置 13_000 个 tokens (AUTOCOMPACT_BUFFER_TOKENS) 触发缩减。
  2. 熔断机制MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES 控制在极端情况下(比如用户的单张超级图片就超过了上下文)不再徒劳压缩发送导致无限报错循环。

4. 压缩与上下文反向重建机制

"如何在压缩对话历史时,不弄丢'正在用的工具和资料',让 Agent 无缝继续工作",核心是一套 "先精简脱水,再精准补回关键状态" 的机制。我们用 "整理工作台" 的类比来拆解

4.1 清理多媒体与低价值负载:先给对话 "脱水"

  • 想象 Agent 的上下文是一张 "工作台"

    • 旧对话历史是 "堆在桌上的旧文件";
    • 正在查看的外部文件、用着的工具、做了一半的计划是 "手里正在用的资料和工具"。
  • 如果只是简单把 "旧文件" 扔掉(压缩),很可能把 "正在用的资料和工具" 也弄丢了 ------Agent 就会忘了 "刚才在看什么文件、做什么计划",工作就断了。

  • 这段代码就是解决这个问题:既要把 "旧文件" 精简掉(4.1),又要把 "正在用的东西" 精准摆回桌上(4.4)。

typescript 复制代码
// 把图片、大文件等 "大块头" 剔除,换成简单的文本标记 [image]
export function stripImagesFromMessages(messages: Message[]): Message[] {
  // 类比:整理工作台时,把旧海报、空盒子收起来,
  // 只留个便签 "之前这里有张图"------ 既省空间,又不会完全忘了有过这东西。
}

// 把 "后面会重新补回来的附件"(比如 skill_discovery)先删掉,
// 避免重复总结导致 "幻觉"(模型以为有两个一样的东西)。
export function stripReinjectedAttachments(messages: Message[]): Message[] {
  // 整理时先把 "等下还要摆回来的笔记本" 暂时收走,避免整理完发现桌上有两本一样的
}

4.2 API 的 Prompt 缓存复用:借主对话的 "缓存" 省成本

总结工作是在 Forked Agent(分支 Agent) 里做的(和主对话不是同一个 "人"),但官方让它共享主对话的 Prompt 缓存------ 这是个很巧的省钱设计。

typescript 复制代码
// Forked Agent 借用主对话上下文的 Prompt Cache。省了大量 "贴标签" 的时间(Token 开销)。
// 测试(2026年1月)证明这个借用机制能省掉每次压缩所需的极大头部填充 Token 开销
const promptCacheSharingEnabled = getFeatureValue_CACHED_MAY_BE_STALE(
  'tengu_compact_cache_prefix',
  true,
)

4.3 PTL 防御(Prompt Too Long Fallback):实在太长就 "剥洋葱",救命用的

如果前面的 "脱水" 还是让总结服务报错 "Prompt 太长"(Prompt Too Long),就启动最后的 "救命稻草":

typescript 复制代码
// 像 "剥洋葱" 一样,一次剥掉 20% 的最旧内容,重试总结
// 类比:工作台实在太满,连 "脱水" 后都放不下 ------ 就先把最旧的一摞文件移走。
// 特点:虽然 "有损"(会丢一点旧内容),但能把 "被锁死的会话" 救回来 ------ 总比完全用不了强。
const truncated = ptlAttempts <= MAX_PTL_RETRIES
  ? truncateHeadForPTLRetry(messagesToSummarize, summaryResponse)
  : null

4.4 状态重启点补偿(State Re-injection):把 "正在用的东西" 摆回来

这是最关键的一步:总结完后,旧对话变成了一条 "短摘要",但必须把 **"刚才正在用的工具和资料" 补回来 **,让 Agent 感觉 "工作没断"。

typescript 复制代码
// ===== 保存状态重组补偿区 =====

// 1. 补回正在看的文件:把刚才通过 FileReadTool 查看、还没丢缓存的文件重新加回来(带截断上限,避免又太长)。
const fileAttachments = createPostCompactFileAttachments(preCompactReadFileState, ...)

// 2. 补回正在做的计划 / 技能:把之前进行中的 Plan(计划)、Skill(技能)重新加回来。
const planAttachment = createPlanAttachmentIfNeeded(context.agentId)
const skillAttachment = createSkillAttachmentIfNeeded(context.agentId)

// 3. 把被删掉的 Deferred Delta 工具协议重新发给模型。
for (const att of getDeferredToolsDeltaAttachment(...)) {
  postCompactFileAttachments.push(createAttachmentMessage(att))
}

最终效果:压缩后的上下文长什么样? [System 边界宣告] + [精简文本摘要] + [正在查看的文件内文截取] + [正在做的 Plan] + [仍然激活的各种 MCP Server 和 Tools 的完整声明]

  • 整理后的工作台 ------"桌面整洁了(旧文件精简了),但正在写的文档、用的工具、待办清单都在原位"。
  • 大模型既释放了 "庞大的旧文本空间",又像 "一直坐在工作台前" 一样,无缝连接下一轮输入 ------ 完全没感觉到 "刚才整理过桌子"。

5. 终极节省token实用习惯总结:

所有节省Token的操作,本质都是减少「不必要的上下文占用」和「避免无效压缩消耗」。

会话管理

  • 完全无关的新问题必须新开独立窗口

    • 单个会话的历史会被永久保留,即使压缩成摘要也会持续占用上下文;
    • 每次压缩本身需要消耗数千~数万Token(包括压缩提示词、Forked Agent调用)。
  • 大任务拆分为多个短会话,避免单会话过长

    • 长会话会反复触发自动压缩(每填满16.7万/96.7万Token触发一次),每次压缩都有额外开销;
    • 极端情况下会触发"剥洋葱"式截断,丢失重要历史。

输入内容控制

  • 严格控制单条输入大小,绝不超过压缩触发线
    • 200k模型:实际可用180k Token,167k Token就会触发压缩
    • 1m模型:实际可用980k Token,967k Token就会触发压缩
    • 单条输入超过触发线会立即触发压缩,甚至因"连压缩的空间都没有"导致失败。
  • 非必要不上传图片,能用文字描述就不用图片
    • 图片会占用大量Token(一张高清图≈数千~数万Token);
    • 压缩时只会被替换为[image]标记,但原始内容在压缩前已全额消耗Token。
    • 用文字清晰描述问题/界面;必须上传图片时,优先使用低分辨率截图。

工具与技能管理

  • 只开启当前任务必需的Skill,用完及时关闭
    • 激活的Skill会在每次压缩后被自动重新注入上下文,永久占用空间;同时开启多个无关Skill会导致上下文快速膨胀。
  • 不要让Agent同时加载过多文件
    • Agent最近读取的所有文件会在压缩后被完整重新注入上下文
    • 同时打开多个大文件会导致压缩后上下文依然很大,频繁触发二次压缩。
  • 及时终止无效的Plan,避免残留状态占用
    • 进行中的Plan(任务计划)会和Skill一样,在每次压缩后被重新注入;
相关推荐
workflower2 小时前
机器人应用-楼宇室内巡逻
大数据·人工智能·算法·microsoft·机器人·动态规划·享元模式
电子科技圈2 小时前
从进迭时空K3看RISC-V CPU与Imagination GPU协同:如何构建高性能SoC能力
大数据·图像处理·人工智能·嵌入式硬件·边缘计算·智能硬件·risc-v
ZPC82102 小时前
fanuc 机器人通过PR寄存器实现轨迹控制
人工智能·算法·计算机视觉·机器人
阿里云大数据AI技术2 小时前
EMR Serverless Spark 推出 Spark 4.0,加速湖仓架构下的数据处理升级
大数据·人工智能·spark
hERS EOUS2 小时前
Spring Boot + Spring AI快速体验
人工智能·spring boot·spring
JAVA学习通2 小时前
LangChain4j 与 Spring AI 的技术选型深度对比:2026 年 Java AI 工程化实践指南
java·人工智能·spring
机器之心2 小时前
特斯拉开源硬件,中国团队开源大脑!首个具身智能顶配全家桶上线
人工智能·openai
机器之心2 小时前
天塌了,Pro用户用不了Claude Code,除非100美元买Max
人工智能·openai
大模型实验室Lab4AI2 小时前
Aligning Agents via Planning: A Benchmark for Trajectory-Level Reward
人工智能