3110 words
16 minutes
Context Catch

Context Catch#

Context Catch 是 Claude Code 中上下文管理的技术,用于在上下文空间快用完时,智能地保留重要信息。

问题背景#

Claude Code 上下文窗口限制

Token 上限(比如 200K tokens)
已使用:
├─────────────────────────────────────────────────────────────┤
│ 系统提示 │ 历史消息 │ 代码上下文 │ 工具输出 │ 剩余空间 │
└─────────────────────────────────────────────────────────────┘
快满了!

如果继续添加 → 超出窗口 → 丢失早期上下文

Context Catch 的机制#

┌─────────────────────────────────────────────────────────────┐
│ Context Catch 工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 检测上下文使用率 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 当使用量 > 80% 时触发 catch 策略 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ Step 2: 识别"可压缩"的内容 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ❌ 工具执行结果(可重新执行) │ │
│ │ ❌ 重复的日志输出 │ │
│ │ ❌ 次要的中间过程 │ │
│ │ ✅ 关键决策点 │ │
│ │ ✅ 用户明确的需求 │ │
│ │ ✅ 代码修改的核心内容 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ Step 3: 执行压缩/摘要 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ "之前的搜索结果已保存,现在执行下一步..." │ │
│ │ "已完成的代码变更:[文件A, 文件B]..." │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ Step 4: 释放空间,继续工作 │
│ │
└─────────────────────────────────────────────────────────────┘

实际效果#

压缩前(原始记录)

[11:30] 用户: 帮我创建一个用户注册功能
[11:31] Claude: 我来创建这个功能...
[11:31] 工具: 搜索现有用户相关代码
[11:31] 工具: 找到 5 个相关文件
[11:32] 工具: 读取 user.ts
[11:32] 工具: 读取 auth.ts
[11:32] 工具: 读取 database.ts
[11:33] Claude: 分析了现有代码结构
[11:33] Claude: 开始实现...
[11:34] 工具: 创建 user-service.ts
[11:35] 工具: 更新 user.ts 添加新字段
[11:36] Claude: 完成了用户注册功能

Context Catch 压缩后

[11:30-11:36] 用户注册功能已完成
- 创建: user-service.ts
- 修改: user.ts
- 关键需求: 用户名、邮箱、密码字段
- 状态: 可运行,待测试

Claude Code 中的记忆系统分层#

┌─────────────────────────────────────────────────────────────┐
│ Claude Code 记忆系统 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Working Memory (当前上下文) │ │
│ │ • 当前会话的完整上下文 │ │
│ │ • 所有工具调用和结果 │ │
│ │ • 代码修改历史 │ │
│ └──────────────────┬──────────────────────────────────┘ │
│ │ Context Catch │
│ ↓ 压缩/摘要 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Compressed Context │ │
│ │ • 关键决策点摘要 │ │
│ │ • 已完成/进行中的工作摘要 │ │
│ │ • 重要文件状态 │ │
│ └──────────────────┬──────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Persistent Memory │ │
│ │ • 项目知识 (README, 架构) │ │
│ │ • 用户偏好设置 │ │
│ │ • 长期项目上下文 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Context Catch 的策略#

策略说明
工具输出压缩工具执行结果压缩为”已执行”摘要
重复检测连续相似的操作只保留一个
重要性评分根据用户反馈、决策点等评分
层级保留关键信息 > 中间过程 > 详细日志

使用示例#

对话很长后,Claude 自动压缩

用户: 继续刚才的工作
Claude: (Context Catch 检测到上下文已压缩)

我记起来了:

  • 正在实现用户注册功能
  • 已完成 user-service.ts 和 user.ts 修改
  • 需要完成: 验证逻辑 + 测试

让我继续…


总结:Context Catch 就是 Claude Code 在上下文快满时,自动压缩/摘要早期内容,保留关键信息,确保重要上下文不丢失的机制。


详细讲解:Context Catch 与 Claude Code 记忆系统#

针对你在 AI-Interview 项目中的应用需求,以下是更完整的架构说明和代码实现。

整体架构:三层记忆系统#

┌─────────────────────────────────────────────────────────────────┐
│ Claude Code 记忆系统三层架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ L1: Working Context (活动层) │ │
│ │ │ │
│ │ • 当前 session 的完整上下文 │ │
│ │ • 实时消息、工具调用、代码修改 │ │
│ │ • Token 消耗大户 (会触发 catch) │ │
│ │ │ │
│ │ 容量:~200K tokens │ │
│ │ 淘汰:超出时触发 Context Catch │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ Context Catch ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ L2: Compressed Context (压缩层) │ │
│ │ │ │
│ │ • 压缩后的会话摘要 │ │
│ │ • 关键决策点 (用户确认的选择、设计决策) │ │
│ │ • 工作进度快照 (完成了什么、待完成什么) │ │
│ │ • 文件变更摘要 │ │
│ │ │ │
│ │ 容量:~20K tokens (L1 的 10%) │ │
│ │ 淘汰:Session 结束或长期不活跃 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ L3: Persistent Memory (持久层) │ │
│ │ │ │
│ │ • 项目知识 (项目 README、架构文档、代码规范) │ │
│ │ • 用户偏好 (编程语言偏好、注释风格) │ │
│ │ • 跨 session 的长期状态 │ │
│ │ │ │
│ │ 容量:无限制 (存储在磁盘) │ │
│ │ 淘汰:用户主动删除或项目结束 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

L1 → L2 的 Context Catch 压缩过程(代码示例)#

// 压缩前的原始数据 (假设 1500 tokens)
interface RawSession {
messages: [
{ role: "user", content: "帮我实现用户登录功能" },
{ role: "assistant", content: "我来帮你实现..." },
{ role: "system", content: "正在搜索现有代码..." },
{ role: "tool", content: "找到 5 个相关文件: user.ts, auth.ts..." },
{ role: "tool", content: "读取 user.ts 完成 (200行)" },
{ role: "tool", content: "读取 auth.ts 完成 (150行)" },
{ role: "assistant", content: "分析完成,开始实现..." },
{ role: "tool", content: "修改 user.ts: +50行" },
{ role: "tool", content: "创建 auth-service.ts: +80行" },
{ role: "assistant", content: "登录功能已完成" }
];
}
// Context Catch 压缩后
interface CompressedSnapshot {
sessionId: string;
startTime: Date;
endTime: Date;
duration: string; // "15分钟"
goal: "实现用户登录功能";
status: "已完成";
decisions: [
{ point: "使用 JWT 进行身份验证", timestamp: "11:35" },
{ point: "密码使用 bcrypt 加密", timestamp: "11:36" }
];
changes: {
created: ["auth-service.ts", "jwt-util.ts"],
modified: ["user.ts", "auth.ts"],
deleted: []
};
todo: [
"添加单元测试",
"更新 API 文档"
];
keySnippets: [
"function login(): Promise<{token, user}> { ... }"
];
compressedTokens: 300;
}

Context Catch 的具体策略(伪代码)#

// 1. 工具输出压缩
const toolOutputCompression = {
before: "读取文件 /src/user.ts 完成\n共 200 行:\nline 1: import {...}\n...",
after: "[已读取] user.ts (200行)",
preserveIf: (output) => output.includes("ERROR") || output.includes("找到")
};
// 2. 重复消息折叠
const duplicateCollapse = {
before: ["正在搜索...", "正在搜索...", "正在搜索..."],
after: "[重复操作已折叠] 搜索 x3"
};
// 3. 中间过程摘要
const processSummarization = {
before: ["分析代码结构...", "分析依赖关系...", "确定实现方案..."],
after: "完成代码分析和方案设计"
};
// 4. 决策点保留
const decisionPreservation = {
preserve: [
"用户确认使用 TypeScript",
"用户选择 REST API 风格",
"用户要求添加日志中间件"
],
priority: "HIGH"
};

AI-Interview 项目应用设计(基于 Context Catch)#

面试记忆系统架构#

┌─────────────────────────────────────────────────────────────────┐
│ AI-Interview 记忆系统设计 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 用户会话层 (User Session) │ │
│ │ • 面试进行中的实时上下文 │ │
│ │ • 候选人的回答历史 │ │
│ │ • 评分和笔记 │ │
│ │ 触发 Context Catch 条件: │ │
│ │ - Token 使用 > 80% │ │
│ │ - 用户主动暂停/中断 │ │
│ │ - 长时间无交互 (>30分钟) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 面试快照层 (Interview Snapshot) │ │
│ │ 当用户中断面试时保存: │ │
│ │ { │ │
│ │ sessionId: "interview_2024_001", │ │
│ │ candidateName: "张三", │ │
│ │ position: "高级后端工程师", │ │
│ │ currentQuestion: 5, │ │
│ │ totalQuestions: 10, │ │
│ │ answeredQuestions: [1,2,3,4], │ │
│ │ scores: { q1: 4, q2: 3, q3: 5, q4: 2 }, │ │
│ │ keyInsights: [ │ │
│ │ "候选人对分布式系统理解深入", │ │
│ │ "算法能力偏弱", │ │
│ │ "项目经验描述不够具体" │ │
│ │ ], │ │
│ │ partialAnswer: { │ │
│ │ questionId: 5, │ │
│ │ content: "关于性能优化,我首先会..." │ │
│ │ }, │ │
│ │ overallImpression: "80%推荐进入下一轮", │ │
│ │ nextActions: "继续第5题的系统设计部分" │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 候选人档案层 (Candidate Profile) │ │
│ │ • 候选人基础信息 (简历、教育背景) │ │
│ │ • 历史面试记录 (如有) │ │
│ │ • 技能评估雷达图 │ │
│ │ • 面试偏好设置 │ │
│ │ 应用: │ │
│ │ - 下次面试快速预热 │ │
│ │ - 跟踪候选人成长 │ │
│ │ - 面试官间共享候选人信息 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

核心 TypeScript 实现#

interview-context-catch.ts
interface InterviewSnapshot {
sessionId: string;
candidateInfo: {
name: string;
position: string;
interviewId: string;
};
progress: {
currentPhase: 'introduction' | 'coding' | 'system-design' | 'behavioral' | 'qna';
currentQuestionIndex: number;
totalQuestions: number;
answeredQuestions: number[];
};
evaluation: {
questionScores: Record<string, number>;
strengthAreas: string[];
improvementAreas: string[];
overallScore?: number;
};
keyInsights: {
candidateStrengths: string[];
candidateWeaknesses: string[];
technicalDepth: string[];
redFlags: string[];
};
currentContext: {
lastQuestion: Question | null;
partialAnswer?: string;
ongoingDiscussion?: string;
};
createdAt: Date;
updatedAt: Date;
expiresAt?: Date;
}
class InterviewContextCatch {
private threshold = 200000; // token 上限
private maxMessages = 100;
shouldCompress(context: InterviewContext): boolean {
const tokenUsage = calculateTokens(context);
return (
tokenUsage > this.threshold * 0.8 ||
context.messages.length > this.maxMessages ||
this.hasLongSilence(context)
);
}
compress(context: InterviewContext): InterviewSnapshot {
return {
sessionId: context.sessionId,
candidateInfo: context.candidateInfo,
progress: context.progress,
evaluation: this.compressEvaluation(context.evaluation),
keyInsights: this.extractKeyInsights(context),
currentContext: {
lastQuestion: context.currentQuestion,
partialAnswer: context.currentAnswer?.slice(-500),
},
summary: this.generateSummary(context),
updatedAt: new Date()
};
}
extractKeyInsights(context: InterviewContext): InterviewSnapshot['keyInsights'] {
return {
candidateStrengths: this.deduplicate([
...context.evaluation.technicalStrengths,
...context.evaluation.communicationStrengths,
]),
candidateWeaknesses: this.deduplicate([
...context.evaluation.technicalWeaknesses,
...context.evaluation.areasForImprovement,
]),
technicalDepth: context.questions
.filter(q => q.answer?.depth > 7)
.map(q => q.topic),
redFlags: context.evaluation.redFlags || [],
};
}
async restore(snapshot: InterviewSnapshot): Promise<InterviewContext> {
return {
sessionId: snapshot.sessionId,
candidateInfo: snapshot.candidateInfo,
progress: snapshot.progress,
evaluation: snapshot.evaluation,
keyInsights: snapshot.keyInsights,
currentQuestion: snapshot.currentContext.lastQuestion,
partialAnswer: snapshot.currentContext.partialAnswer,
recoveryMessage: this.generateRecoveryMessage(snapshot),
};
}
generateRecoveryMessage(snapshot: InterviewSnapshot): string {
return `
面试已暂停,现在恢复。
候选人: ${snapshot.candidateInfo.name}
应聘岗位: ${snapshot.candidateInfo.position}
当前进度: 第 ${snapshot.progress.currentQuestionIndex + 1} 题,共 ${snapshot.progress.totalQuestions} 题
已完成评估:
- 总体评分: ${snapshot.evaluation.overallScore || '待定'}/10
- 优势: ${snapshot.keyInsights.candidateStrengths.join('、')}
- 不足: ${snapshot.keyInsights.candidateWeaknesses.join('、')}
待完成任务:
${snapshot.progress.answeredQuestions.length}/1 部分回答待完成
下一步: ${snapshot.keyInsights}
请继续面试。
`.trim();
}
}

快照持久化存储#

interview-snapshot-storage.ts
class InterviewSnapshotStorage {
private snapshotDir = "./snapshots";
async save(snapshot: InterviewSnapshot): Promise<void> {
const path = this.getSnapshotPath(snapshot.sessionId);
await fs.writeFile(path, JSON.stringify({
...snapshot,
createdAt: snapshot.createdAt.toISOString(),
updatedAt: snapshot.updatedAt.toISOString(),
expiresAt: snapshot.expiresAt?.toISOString(),
}, null, 2));
await this.saveToUserLocalStorage(snapshot);
}
async load(sessionId: string): Promise<InterviewSnapshot | null> {
const path = this.getSnapshotPath(sessionId);
if (await fs.exists(path)) {
const data = await fs.readFile(path, 'utf-8');
return this.deserialize(JSON.parse(data));
}
return null;
}
async list(candidateName?: string): Promise<InterviewSnapshot[]> {
const files = await fs.readdir(this.snapshotDir);
const snapshots = await Promise.all(
files
.filter(f => f.endsWith('.json'))
.map(f => this.load(f.replace('.json', '')))
);
if (candidateName) {
return snapshots.filter(s => s?.candidateInfo.name.includes(candidateName));
}
return snapshots.filter((s): s is InterviewSnapshot => s !== null);
}
async cleanup(): Promise<void> {
const snapshots = await this.list();
const now = new Date();
for (const snapshot of snapshots) {
if (snapshot.expiresAt && new Date(snapshot.expiresAt) < now) {
await this.delete(snapshot.sessionId);
}
}
}
}

用户中断 → 恢复的完整流程#

┌─────────────────────────────────────────────────────────────────┐
│ AI-Interview Session 恢复流程 │
├─────────────────────────────────────────────────────────────────┤
│ 用户场景: 面试进行到一半,用户关闭浏览器 │
│ │
│ Step 1: 检测中断 │
│ beforeunload 事件触发 → 自动保存快照 → 保存到 localStorage + 服务器 │
│ │
│ Step 2: 生成压缩快照 │
│ ContextCatch.compress(interviewContext) → 生成 InterviewSnapshot │
│ 保留关键洞察、进度、评分,压缩中间过程 │
│ │
│ Step 3: 持久化存储 │
│ 保存到 IndexedDB (浏览器本地) + 服务器数据库,设置7天过期 │
│ │
│ ---------------------- 用户重新打开应用 ---------------------- │
│ │
│ Step 4: 检测未完成面试 │
│ 应用启动 → 检查 localStorage → 发现未完成快照 → 询问用户是否恢复 │
│ │
│ Step 5: 恢复上下文 │
│ snapshot = load(snapshotId); context = restore(snapshot) │
│ 生成恢复消息,展示摘要给用户确认 │
│ │
│ Step 6: 继续面试 │
│ "上次面试进行到第5题,候选人正在回答系统设计问题,回答内容关于缓存策略..." │
│ 用户确认 → 继续面试流程 │
└─────────────────────────────────────────────────────────────────┘

关键设计要点#

要点说明
优先级关键洞察 > 评分数据 > 进度 > 中间过程
压缩比L1 → L2 通常压缩到 10-15%
保留决策面试官的评价、候选人表现判断必须保留
过期机制快照设置过期时间,定期清理
用户确认恢复前展示摘要,用户确认继续

效果对比#

项目无 Context Catch有 Context Catch
长面试支持❌ 受 token 限制✅ 支持任意长度
中断恢复❌ 全部丢失✅ 保留关键上下文
多轮面试❌ 每次重新开始✅ 累积候选人档案
Token 消耗线性增长保持稳定
Context Catch
https://sgjki547.top/posts/context-catch/
Author
SGJki
Published at
2026-04-11
License
CC BY-NC-SA 4.0