Jerry's Blog

Back

如果大模型是 Agent 的 CPU,那么 CCContext 就是它的 RAM 和数据总线。 在 Coding Agent 的数据面(Data Plane)中,CCContext 是最核心的数据结构——它维护对话记录、监控 Token 消耗、在内存即将溢出时执行无感压缩,并屏蔽底层所有物理复杂性。

声明: 本文基于开源项目 learn-claude-code社区逆向分析与重新实现,并非 Anthropic 官方 Claude Code 的真实源码。文中所有函数名、代码片段和架构描述均来自该社区项目对混淆代码的推测性还原,可能与 Claude Code 的实际实现存在差异。本文旨在学习 Agent 上下文管理的设计思想,不代表 Claude Code 的官方技术细节。


一、CCContext 的四大核心职责#

交互演示:CCContext 的四层架构
点击每一层,查看具体的职责和源码实现

CCContext 承担了四大核心职责,每一个都是 Agent 能够正常运行的必要条件:

职责类比核心机制
消息存储RAM维护 messages[] 数组,记录 Agent 全生命周期
Token 账本内存计数器实时追踪消耗,触发压缩阈值
上下文压缩GC / 碎片整理8 段式结构化总结,替换历史记录
数据隔离数据总线统一格式封装,屏蔽底层差异

二、全局状态机与对话记录#

大模型 API 本身是无状态的——每次调用都需要把完整的对话历史传过去。CCContext 负责维护这个完整的 messages 数组,标准化四种角色的数据输入:

交互演示:消息队列的增长过程
点击”下一步”观察 Agent 一轮工作中 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

四种角色的数据写入:

角色数据内容来源
SystemAgent 人设、规则、工作目录初始化时注入
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 消耗的实时监控
观察 Token 如何随 Agent 工作逐步逼近 92% 阈值

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)。

为什么不能随便删消息?#

交互演示:为什么删消息会导致 API 崩溃
点击按钮观察:随机删除 vs 原子块安全切片

在 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                            // 恢复的文件状态
    ]
  };
}
javascript

AU2: 8 段式压缩提示词#

这是 CCContext 记忆管理的核心 Prompt,决定了压缩后保留哪些信息:

AU2 压缩提示词的 8 个段落
点击每个段落查看具体内容和设计意图

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 Work
plaintext

五、压缩后的信息遗漏与三大兜底#

压缩本质上是有损压缩(Lossy Compression)。如果被卸载的记录里有 50 行报错堆栈或某个具体行号,经过 LLM 总结后这些细节就消失了。

但 Claude Code 敢这么做,因为有三大兜底机制

交互演示:Re-fetch 机制——Agent 如何从”失忆”中恢复
点击”下一步”观察:压缩丢失细节后,Agent 如何通过工具调用自我恢复

兜底 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 读取当前状态。


七、完整架构总览#

CCContext 完整数据流
点击不同阶段查看详细说明

关键函数速查表(社区逆向推测)#

以下映射来自 learn-claude-code 社区对混淆代码的推测性还原,并非官方确认

混淆名社区推测语义名推测职责社区验证级别
VEcalculateTokenUsageToken 使用量计算器A 级:代码中直接发现
HY5extractAssistantUsageAssistant 消息 usage 提取A 级:代码中直接发现
zY5sumAllTokenTypes汇总所有 Token 类型A 级:代码中直接发现
yW5checkCompactionEligibility压缩资格检查A 级:代码中直接发现
wU2compactionOrchestrator压缩调度器A 级:代码中直接发现
qH1performCompaction压缩执行器A 级:代码中直接发现
AU2generateCompressionPrompt8 段式压缩提示词生成A 级:代码中直接发现
TW5restoreRecentFiles压缩后文件状态恢复A 级:代码中直接发现
BU2formatSummaryMessage总结消息格式化A 级:代码中直接发现
m11calculateContextThresholds上下文百分比和警告级别A 级:代码中直接发现
h11AUTO_COMPACT_THRESHOLD自动压缩阈值 = 0.92A 级:代码中直接发现
J7getCompressionModel获取压缩专用模型B 级:基于模式推断
nOagentMainLoopAgent 主循环A 级:代码中直接发现

八、设计本质#

CCContext 不是一个简单的 Array。它是一个带自净能力的智能数据总线

用”偶尔需要重新查阅文件(Re-fetch)的时间成本”,换取”无限轮次对话且 Token 永远不会爆炸的系统稳定性”。

这是目前让 AI Agent 能够在大型复杂项目中真正干活(而不是聊两句就报错)的最佳工程妥协。

核心设计决策总结:

  1. 有损压缩 + Re-fetch 兜底:接受信息损失,依赖文件系统作为持久记忆
  2. 原子块保证格式安全:永远不会产生 API 碎片
  3. 8 段式结构化总结:不是随便总结,而是生成交接文档
  4. 92% 阈值自动触发:用户无感知,Agent 满血复活
  5. TW5 文件状态恢复:压缩后立即恢复最近工作的文件上下文

再次声明: 本文所有代码和架构分析均来自开源社区项目 learn-claude-code 的逆向推测,并非 Anthropic 官方 Claude Code 的真实源码。混淆函数名(如 AU2、qH1、VE 等)和语义名映射均为社区的推测性还原,实际实现可能不同。本文价值在于学习 Agent 上下文管理的设计范式,而非还原 Claude Code 的具体实现。

CCContext 深度解析:Coding Agent 的运行内存与数据总线
https://jerry609.github.io/blog/claude-code-cccontext-deep-dive
Author Jerry
Published at March 17, 2026
Comment seems to stuck. Try to refresh?✨