目录
  1. 1. 一、核心问题
  2. 2. 二、压缩系统架构
  3. 3. 三、阈值计算
    1. 3.1. Token 警告阈梯
  4. 4. 四、压缩算法 (compact.ts)
    1. 4.1. 4.1 核心流程
    2. 4.2. 4.2 压缩 Prompt 策略
    3. 4.3. 4.3 压缩后消息结构
  5. 5. 五、微压缩 (Micro-Compact)
  6. 6. 六、上下文分析
  7. 7. 七、Snip 机制 (HISTORY_SNIP feature)
  8. 8. 八、自动压缩的断路器
  9. 9. 九、Recompaction (再压缩)
  10. 10. 十、Session Memory Compact
【Claude Code源码剖析】10-上下文窗口管理与压缩

Claude 的上下文窗口有限 (200K tokens)。当对话变长时,必须智能压缩以保持工作效率。


一、核心问题

对话开始: [System(5K)] [User(1K)] [Assistant(2K)]  = 8K tokens   ✅ 轻松

50 轮后: [System(5K)] [50轮对话(180K)] [新消息(15K)] = 200K tokens ❌ 溢出!

解决方案: 自动检测 → 压缩 → 继续工作


二、压缩系统架构

services/compact/
├── autoCompact.ts — 自动压缩触发逻辑
├── compact.ts — 压缩核心算法 (1706 行)
├── microCompact.ts — 微压缩 (单条消息级别)
├── apiMicrocompact.ts — API 侧微压缩
├── grouping.ts — 消息分组策略
├── prompt.ts — 压缩用的 prompt 模板
├── postCompactCleanup.ts — 压缩后清理
├── sessionMemoryCompact.ts — 会话记忆压缩
├── compactWarningHook.ts — 压缩警告 hook
└── compactWarningState.ts — 警告状态管理

三、阈值计算

// autoCompact.ts
const AUTOCOMPACT_BUFFER_TOKENS = 13_000; // 自动压缩缓冲
const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000; // 警告阈值
const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000; // 压缩摘要最大输出

export function getEffectiveContextWindowSize(model: string): number {
const contextWindow = getContextWindowForModel(model); // 200,000
const reserved = Math.min(getMaxOutputTokensForModel(model), MAX_OUTPUT_TOKENS_FOR_SUMMARY);
return contextWindow - reserved; // ~180,000
}

export function getAutoCompactThreshold(model: string): number {
return getEffectiveContextWindowSize(model) - AUTOCOMPACT_BUFFER_TOKENS;
// ~167,000 tokens → 超过此值自动压缩
}

Token 警告阈梯

0         ────────────────────────── 100%
├─────────────────────────┤
│ │
│ 正常工作区域 │
│ │
├─────────────────────────┤ ← 自动压缩阈值 (~83.5%)
│ 自动压缩触发区域 │
├─────────────────────────┤ ← 警告阈值 (~90%)
│ ⚠️ 警告:空间即将耗尽 │
├─────────────────────────┤ ← 错误阈值 (~90%)
│ ❌ 错误:必须立即压缩 │
└─────────────────────────┘ ← 200K (上下文窗口)

四、压缩算法 (compact.ts)

4.1 核心流程

export async function compactConversation(
messages: Message[],
context: CompactContext,
): Promise<CompactionResult> {

// ===== Phase 1: 消息分区 =====
// 找到最后一个 "压缩边界" 之后的消息
const activeMessages = getMessagesAfterCompactBoundary(messages);

// ===== Phase 2: 构建摘要请求 =====
// 将需要压缩的消息发给 Claude,请求生成摘要
const summaryPrompt = buildCompactPrompt(activeMessages, context);

// ===== Phase 3: 调用 LLM 生成摘要 =====
const summary = await runForkedAgent(summaryPrompt, {
model: context.model,
maxTokens: COMPACT_MAX_OUTPUT_TOKENS,
});

// ===== Phase 4: 重建消息历史 =====
const compactedMessages = buildPostCompactMessages(
messages, // 原始消息
summary, // LLM 生成的摘要
activeMessages, // 被压缩的消息
);

// ===== Phase 5: 后置处理 =====
await runPostCompactCleanup(compactedMessages, context);

return {
compactedMessages,
removedCount: activeMessages.length,
savedTokens: estimateTokensSaved(activeMessages, summary),
};
}

4.2 压缩 Prompt 策略

// services/compact/prompt.ts
function buildCompactPrompt(messages: Message[]): string {
return `
请将以下对话历史压缩为一个简洁但完整的摘要。

要求:
1. 保留所有重要的技术决策和代码变更
2. 保留文件路径和关键代码片段
3. 保留当前工作状态和未完成的任务
4. 保留用户的偏好和约束条件
5. 丢弃冗余的工具输出和中间步骤
6. 如果有被拒绝的操作,保留拒绝原因

当前对话包含 ${messages.length} 条消息,
请将其压缩为一个清晰的摘要。
`;
}

4.3 压缩后消息结构

压缩前:
[System] [User1] [Asst1] [Tool1] [User2] [Asst2] ... [User50] [Asst50]

压缩后:
[System] [CompactBoundary: "以下是之前对话的摘要: ..."] [User50] [Asst50]
└─ 包含前 49 轮的精华摘要

CompactBoundary 消息格式:
{
type: 'system',
subtype: 'compact_boundary',
content: "## 对话摘要\n\n### 已完成的工作\n- 修复了 X bug...\n### 当前状态\n- 正在处理 Y..."
}

五、微压缩 (Micro-Compact)

大型工具结果的就地压缩,不触发完整的对话压缩:

// services/compact/microCompact.ts
// 单条消息级别的压缩

export function microCompactToolResult(
toolResult: string,
maxTokens: number,
): string {
if (tokenCount(toolResult) <= maxTokens) {
return toolResult; // 不需要压缩
}

// 策略 1: 截断 (保留头和尾)
// 策略 2: 移除重复行
// 策略 3: 折叠大块代码输出
return truncateWithContext(toolResult, maxTokens);
}

六、上下文分析

// utils/contextAnalysis.ts
export function analyzeContext(messages: Message[]): ContextAnalysis {
return {
totalTokens: tokenCountWithEstimation(messages),
messageCount: messages.length,
toolCallCount: countToolCalls(messages),

// 按类型分布
breakdown: {
systemPrompt: systemPromptTokens,
userMessages: userTokens,
assistantMessages: assistantTokens,
toolResults: toolResultTokens,
attachments: attachmentTokens,
},

// 最大的消息 (压缩候选)
largestMessages: findLargestMessages(messages, 10),

// 压缩建议
recommendation: totalTokens > threshold ? 'compact_now' : 'ok',
};
}

七、Snip 机制 (HISTORY_SNIP feature)

一种更激进的压缩方式——直接裁剪历史:

// services/compact/snipCompact.ts (feature-gated)
// 与压缩不同,snip 直接丢弃旧消息(不生成摘要)
// 适用于 SDK 长期运行的无头会话

function snipHistory(messages: Message[], keepRecent: number): Message[] {
// 保留最近 N 条消息,丢弃其余
const snipPoint = messages.length - keepRecent;
const tombstone = createTombstoneMessage(`Snipped ${snipPoint} messages`);
return [tombstone, ...messages.slice(snipPoint)];
}

八、自动压缩的断路器

const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3;

// 如果连续 3 次自动压缩都失败,停止尝试
// 这防止了 "上下文已经太大,压缩本身也会溢出" 的死循环
// BQ 数据: 1,279 个会话曾经连续失败 50+ 次,浪费了约 250K API 调用/天

九、Recompaction (再压缩)

// 场景: 压缩后的摘要 + 后续对话 又超过了阈值
// → 需要对 "摘要 + 后续" 再做一次压缩

type RecompactionInfo = {
isRecompaction: boolean;
previousSummary: string;
turnsSinceLastCompaction: number;
};

// 再压缩的 prompt 会包含之前的摘要
// 告诉 LLM: "这是之前的摘要,请在此基础上整合新内容"

十、Session Memory Compact

// services/compact/sessionMemoryCompact.ts
// 当对话压缩时,提取关键记忆存入持久化存储

export async function trySessionMemoryCompaction(
messages: Message[],
summary: string,
): Promise<void> {
// 1. 从摘要中提取关键事实
// 2. 存入 ~/.claude/memory/ 或项目的 .claude/memory/
// 3. 下次会话恢复时可以引用这些记忆
}
打赏
  • 微信
  • 支付宝

评论