
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 把旧对话压缩成一个简短的摘要,腾出空间放新内容。
-
但压缩过程需要两步:
- 先把旧对话暂时放进去;
- 再把 "帮我压缩这段对话" 的提示词放进去。
-
如果背包被塞得满满当当,连这两步的空间都没有,压缩就会失败。所以系统必须提前预留 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 }
}
}
双重保证的设计巧思
- 缓冲界限 :在上下文快满时前置
13_000个 tokens (AUTOCOMPACT_BUFFER_TOKENS) 触发缩减。 - 熔断机制 :
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一样,在每次压缩后被重新注入;