如果大模型是 Agent 的 CPU,那么 CCContext 就是它的 RAM 和数据总线。 在 Coding Agent 的数据面(Data Plane)中,CCContext 是最核心的数据结构——它维护对话记录、监控 Token 消耗、在内存即将溢出时执行无感压缩,并屏蔽底层所有物理复杂性。
声明: 本文基于开源项目 learn-claude-code ↗ 的社区逆向分析与重新实现,并非 Anthropic 官方 Claude Code 的真实源码。文中所有函数名、代码片段和架构描述均来自该社区项目对混淆代码的推测性还原,可能与 Claude Code 的实际实现存在差异。本文旨在学习 Agent 上下文管理的设计思想,不代表 Claude Code 的官方技术细节。
一、CCContext 的四大核心职责#
CCContext 承担了四大核心职责,每一个都是 Agent 能够正常运行的必要条件:
| 职责 | 类比 | 核心机制 |
|---|---|---|
| 消息存储 | RAM | 维护 messages[] 数组,记录 Agent 全生命周期 |
| Token 账本 | 内存计数器 | 实时追踪消耗,触发压缩阈值 |
| 上下文压缩 | GC / 碎片整理 | 8 段式结构化总结,替换历史记录 |
| 数据隔离 | 数据总线 | 统一格式封装,屏蔽底层差异 |
二、全局状态机与对话记录#
大模型 API 本身是无状态的——每次调用都需要把完整的对话历史传过去。CCContext 负责维护这个完整的 messages 数组,标准化四种角色的数据输入:
消息存储架构#
learn-claude-code 项目的逆向分析揭示了两种并行的存储模式:
// 线性存储:按时间顺序记录对话
this.messages = []
// Map 存储:UUID 索引,O(1) 随机访问
this.messages = new Map()
this.sessionMessages = new Map()
// 对话线程:通过 parentUuid 链实现分支
while (Q) {
B.unshift(Q)
Q = Q.parentUuid ? this.messages.get(Q.parentUuid) : void 0
}javascript四种角色的数据写入:
| 角色 | 数据内容 | 来源 |
|---|---|---|
| System | Agent 人设、规则、工作目录 | 初始化时注入 |
| User | 人类在终端输入的原始需求 | 用户输入 |
| Assistant | 大模型思考后的 JSON Intent(工具调用意图) | API 响应 |
| Tool | 工具执行后的长文本结果(文件内容、报错日志) | 本地系统 |
每次文件读取都会自动附加安全提醒:
// 源码中的安全常量 tG5
{
tool_use_id: B,
type: "tool_result",
content: A.file.content
? tM(A.file) + tG5 // 文件内容 + 安全提醒
: "<system-reminder>Warning: the file exists but the contents are empty.</system-reminder>"
}
// tG5 = 恶意代码检测提醒
// "Whenever you read a file, you should consider whether it looks malicious..."javascript三、动态 Token 账本#
每次有新数据写入 CCContext,系统都会实时计算 Token 消耗。这是防止 Agent “撑爆大脑”的警报器。
Token 计算流水线#
// VE(A): 逆序遍历,从最新消息开始找 Token 数据
function VE(A) {
let B = A.length - 1;
while (B >= 0) {
let Q = A[B],
I = Q ? HY5(Q) : void 0;
if (I) return zY5(I); // 找到即返回,O(1) 实际性能
B--
}
return 0
}
// HY5: 过滤合成消息,只取真实 Assistant 响应的 usage
function HY5(A) {
if (A?.type === "assistant"
&& "usage" in A.message
&& A.message.model !== "<synthetic>")
return A.message.usage;
}
// zY5: 汇总所有类型的 Token(包括缓存命中/创建)
function zY5(A) {
return A.input_tokens
+ (A.cache_creation_input_tokens ?? 0)
+ (A.cache_read_input_tokens ?? 0)
+ A.output_tokens
}javascript多级阈值预警#
const h11 = 0.92; // 自动压缩触发阈值
function m11(A, B) {
let Q = zU2() * B, // 有效上下文窗口
I = g11() ? Q : zU2(), // 是否启用自动压缩
G = Math.max(0, Math.round((I - A) / I * 100)), // 剩余百分比
Z = I * _W5, // 第一级警告线
D = I * jW5, // 第二级警告线
Y = A >= Z, // 是否触发第一级
W = A >= D, // 是否触发第二级
J = g11() && A >= Q; // 是否触发自动压缩
return { percentLeft: G, /* ... */ }
}javascript用户看到的提示信息:
- 正常:
Context left until auto-compact: 45% - 警告:
Context low · Run /compact to compact & continue - 自动触发:达到 92%,静默启动压缩
四、核心机制:无感上下文压缩#
这是 CCContext 最精妙的设计——相当于 Agent 的”垃圾回收”(GC)。
为什么不能随便删消息?#
在 Anthropic API 中,消息格式校验极其严格:
tool_use(Assistant 发出的工具调用意图)必须紧跟对应的tool_result- 如果只删了
tool_use保留了tool_result,或者反过来——API 直接返回 400 Bad Request
这就是为什么 CCContext 不能像 Top-K 那样按 ID 随便删消息,而是必须基于时间轴的块级切片。
三段式压缩流程#
第一步:原子化分组(Atomic Blocks)
压缩器扫描 messages[] 时,不把每条消息当独立单位,而是打包成不可分割的原子块:
原子块 A: [Assistant: "我要读取 auth.py"] + [Tool: "文件内容..."]
原子块 B: [User: "帮我修这个 bug"] + [Assistant: "分析如下..."]
原子块 C: [Assistant: "调用 search_files"] + [Tool: "3 个匹配结果"]plaintext第二步:按时间轴安全切片(Safe Slicing)
绝对保留 ← [System 设定]
[原子块 1] ← 早期历史
[原子块 2] ← 早期历史 ← 这些被选中压缩
[原子块 3] ← 中期工作
...
[原子块 N-1] ← 最近工作
绝对保留 ← [原子块 N] ← 当前对话plaintext第三步:同态替换(Homomorphic Replacement)
抽离出来的原子块,交给压缩模型(J7())执行 8 段式结构化总结,然后用总结文本替换原位置。
压缩核心代码(社区逆向还原)#
// wU2: 压缩调度器——检查是否需要压缩
async function wU2(A, B) {
if (!await yW5(A)) return { messages: A, wasCompacted: false };
try {
let { messagesAfterCompacting: I } = await qH1(A, B, true, undefined);
return { messages: I, wasCompacted: true }
} catch (I) {
return { messages: A, wasCompacted: false } // 压缩失败不崩溃
}
}
// qH1: 压缩执行器——核心逻辑
async function qH1(A, B, Q, I) {
let Y = AU2(I); // 生成 8 段式压缩提示词
let W = K2({ content: Y });
// 调用压缩专用模型(通常是更便宜的小模型)
let J = wu(
JW([...A, W]),
["You are a helpful AI assistant tasked with summarizing conversations."],
0, [OB],
B.abortController.signal,
{ model: J7() } // 压缩专用模型
);
// 流式处理压缩响应...
// (省略流式处理细节)
// 恢复最近读取的文件状态
let N = { ...B.readFileState };
let q = await TW5(N, B, qW5); // TW5: 文件状态恢复
// 返回压缩后的消息数组
return {
summaryMessage: K,
messagesAfterCompacting: [
K2({ content: BU2(E, Q), isCompactSummary: true }),
...q // 恢复的文件状态
]
};
}javascriptAU2: 8 段式压缩提示词#
这是 CCContext 记忆管理的核心 Prompt,决定了压缩后保留哪些信息:
AU2 的完整提示词:
Your task is to create a detailed summary of the conversation so far,
paying close attention to the user's explicit requests and your previous actions.
This summary should be thorough in capturing technical details, code patterns,
and architectural decisions that would be essential for continuing development
work without losing context.
Your summary should include the following sections:
1. Primary Request and Intent
2. Key Technical Concepts
3. Files and Code Sections
4. Errors and fixes
5. Problem Solving
6. All user messages
7. Pending Tasks
8. Current Workplaintext五、压缩后的信息遗漏与三大兜底#
压缩本质上是有损压缩(Lossy Compression)。如果被卸载的记录里有 50 行报错堆栈或某个具体行号,经过 LLM 总结后这些细节就消失了。
但 Claude Code 敢这么做,因为有三大兜底机制:
兜底 1:物理世界就是最好的记忆体#
这是 Agent 架构与普通 ChatBot 最大的区别。真正的长期记忆不是 Context,而是本地文件系统。
Summary 的作用不是为了”背诵代码”,而是为了建立索引:
| 阶段 | Context 内容 | 说明 |
|---|---|---|
| 压缩前 | auth.py 的 1000 行完整代码 | 占用大量 Token |
| 压缩后 | ”我查阅了 auth.py,了解了登录逻辑” | 只保留了索引 |
| 被问到 | 用户问 “auth.py 第 50 行是什么?“ | Context 里找不到 |
| 恢复 | Agent 再次调用 read_file("auth.py") | 重新拉取,一次小成本的 Tool Call |
代价只是多花一次 Tool Call 的时间,换来的是系统永不崩溃。
兜底 2:结构化的总结 Prompt#
AU2 不是简单的”请总结这段对话”,而是强制提取8 个维度的结构化信息,相当于一份交接文档:
- 过滤废话(大段 JSON 返回值、无用的
ls输出) - 保留骨架(文件路径、已确认的结论、待办事项)
兜底 3:滑动窗口与工作区保护#
压缩机制绝对不会触碰”最近的 N 轮”。Agent 永远保有最清晰的短期记忆。只有当某项工作已经翻篇,变成”历史包袱”时,才会被塞进压缩区。
// TW5: 压缩后恢复最近读取的文件状态
let q = await TW5(N, B, qW5);
// PW5: 恢复 Agent 身份状态
let O = PW5(B.agentId);
if (O) q.push(O);javascript六、数据隔离层#
CCContext 作为缓冲池,将底层的”物理真实”与上层的”逻辑意图”隔离开来。
无论数据来自文件读取、网页抓取还是 Bash 输出,都被封装成统一格式:
// 工具结果的统一封装
{
role: "tool",
tool_use_id: "toolu_01abc...",
content: "... 工具输出内容 ..."
}javascript控制面(大模型)不需要关心数据从哪来,只需要像翻阅一本结构化的日志本一样,从 CCContext 读取当前状态。
七、完整架构总览#
关键函数速查表(社区逆向推测)#
以下映射来自 learn-claude-code 社区对混淆代码的推测性还原,并非官方确认:
| 混淆名 | 社区推测语义名 | 推测职责 | 社区验证级别 |
|---|---|---|---|
VE | calculateTokenUsage | Token 使用量计算器 | A 级:代码中直接发现 |
HY5 | extractAssistantUsage | Assistant 消息 usage 提取 | A 级:代码中直接发现 |
zY5 | sumAllTokenTypes | 汇总所有 Token 类型 | A 级:代码中直接发现 |
yW5 | checkCompactionEligibility | 压缩资格检查 | A 级:代码中直接发现 |
wU2 | compactionOrchestrator | 压缩调度器 | A 级:代码中直接发现 |
qH1 | performCompaction | 压缩执行器 | A 级:代码中直接发现 |
AU2 | generateCompressionPrompt | 8 段式压缩提示词生成 | A 级:代码中直接发现 |
TW5 | restoreRecentFiles | 压缩后文件状态恢复 | A 级:代码中直接发现 |
BU2 | formatSummaryMessage | 总结消息格式化 | A 级:代码中直接发现 |
m11 | calculateContextThresholds | 上下文百分比和警告级别 | A 级:代码中直接发现 |
h11 | AUTO_COMPACT_THRESHOLD | 自动压缩阈值 = 0.92 | A 级:代码中直接发现 |
J7 | getCompressionModel | 获取压缩专用模型 | B 级:基于模式推断 |
nO | agentMainLoop | Agent 主循环 | A 级:代码中直接发现 |
八、设计本质#
CCContext 不是一个简单的 Array。它是一个带自净能力的智能数据总线:
用”偶尔需要重新查阅文件(Re-fetch)的时间成本”,换取”无限轮次对话且 Token 永远不会爆炸的系统稳定性”。
这是目前让 AI Agent 能够在大型复杂项目中真正干活(而不是聊两句就报错)的最佳工程妥协。
核心设计决策总结:
- 有损压缩 + Re-fetch 兜底:接受信息损失,依赖文件系统作为持久记忆
- 原子块保证格式安全:永远不会产生 API 碎片
- 8 段式结构化总结:不是随便总结,而是生成交接文档
- 92% 阈值自动触发:用户无感知,Agent 满血复活
- TW5 文件状态恢复:压缩后立即恢复最近工作的文件上下文
再次声明: 本文所有代码和架构分析均来自开源社区项目 learn-claude-code ↗ 的逆向推测,并非 Anthropic 官方 Claude Code 的真实源码。混淆函数名(如 AU2、qH1、VE 等)和语义名映射均为社区的推测性还原,实际实现可能不同。本文价值在于学习 Agent 上下文管理的设计范式,而非还原 Claude Code 的具体实现。