找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3011

积分

0

好友

403

主题
发表于 9 小时前 | 查看: 19| 回复: 0

大家好,我是小林。

Claude Code 源码泄漏这个瓜,大家都吃了吧?

堂堂业界最强编程 Agent,就因为 npm 打包时配置手抖,不小心把 .map 文件一起传了上去,结果 51 万行核心代码直接全网裸奔。

深色主题的代码编辑器界面,显示名为 'claude code' 的 IDE 窗口。左侧为文件资源管理器,展开 'src' 目录下的多个子目录(如 'cli', 'components', 'hooks', 'ink'),当前选中并高亮显示的是 'clearTerminal.ts' 文件。右侧主编辑区展示该 TypeScript 文件的源码内容,包含注释、import 语句及三个函数定义:isWindowsTerminal()、isMintty() 和 isModernWindowsTerminal(),涉及终端类型检测逻辑,使用了 process.platform 等环境变量判断。底部状态栏显示 'Problems 18', 'Output', 'Debug Console', 'Terminal', 'Ports' 等标签,右下角有 'node' 运行环境标识及一些工具按钮。

面对这场史诗级乌龙,大家都忙着吃瓜,而懂行的第一反应都是:赶紧下载源码!毕竟这可是目前地表最强 Agent 的底牌。

一张聊天界面截图,左侧为文件夹结构列表(包含assets、omx、src、tests等目录及文件),右侧为绿色气泡的对话内容,依次显示‘不对,被替换了’‘不是python’‘是ts’,下方有文件‘cc_src.zip’(9.5M)的传输信息,底部还有两条消息:‘我靠 八点半那就没了’和‘OKOK 稳妥啦 好东西😊’,整体背景为浅灰色,右侧头像为蓝色调人物背影。

先说清楚,这些泄漏的是 Claude Code 客户端的源码,并不是 Claude Opus 大模型的源码。

可能就有同学疑惑了,客户端源码有啥好看的?

说到这,不知道大家最近有没有注意到,AI 圈最近又造了一个新词,叫 Harness Engineering(线束工程),大家最近都刷到了吧?

一张关于'HARNESS 工程'的科技风格信息图,顶部中央为标题'HARNESS 工程',左侧是一个大齿轮内含大脑图案并标注'人工智能',中间是数据处理流程图,包含多个齿轮、代码符号(</>)、花括号{}、绿色箭头连接的模块,右侧为一个模拟终端窗口显示多行代码(含LET、SUBROUTINE等关键字),底部从左至右排列五个图标及对应文字:锁形图标配'强大的人工智能大脑'、加号与代码图标配'约束条件'、循环箭头图标配'反馈循环'、对勾盾牌图标配'验证系统'、立方体图标配'基础设施',最右侧为'可靠的软件开发'文字。

听起来是不是特别唬人?每次 AI 圈一出这种高大上的新词,其实大半都是在「重新包装常识」。

说白了,这词的意思就是大家终于认清现实了:与其天天「做法」祈求大模型变聪明、别产生幻觉,不如老老实实给这匹野马套上缰绳(Harness)。用系统去约束它的行为,给它划好赛道,它才能稳稳当当出活。

一张关于AI工程演进趋势的科普信息图,标题为‘2026年,“Harness Engineering”将成为趋势’。图中包含多个板块:左上角解释‘Harnes Engineering’字面意思为‘马具’,配图显示从套在马身上的缰绳到人骑马控制方向的演变;右上角‘AI中的马具’板块,用蓝色骏马驮着机器人形象比喻AI Agent如有力但不守规矩的马,Harnes使其既快又不跑偏;下方分为三个阶段:左侧‘过去三年,三个阶段’时间轴,标注‘阶段1: Prompt Engineering (2023-2024)’,强调‘怎么跟AI说话’,配图有喊话者、大脑、Write Book、Code App等元素;中间‘阶段2: Context Engineering (2025)’,关注‘给AI看什么信息’,展示System Prompt、Agent、Memory、RAG Retrieval Results、Tool Call Outputs等模块构成的信息环境;右侧‘阶段3: Harness Engineering (2026)’,聚焦‘构建什么环境让AI工作’,含Execution Environment、Memory、Tools、Guardrails等组件,最终输出Reliable Output。

这也是目前 AI 圈最真实的风向转变:从「拼模型智商」变成了「拼系统求稳」。

而这 51 万行的 Claude Code 源码,简直就是 Harness Engineering 的最佳教科书。

啃完你会发现,人家 80% 的代码根本不是在搞什么黑科技让 AI 更聪明,而是在死磕「可靠性」。

今天这篇文章,我就带大家一层一层把 Claude Code 的核心架构给扒明白。

一张浅灰色背景的图片,上面有两行黑色中文文字,第一行为“正文共: 22357字 43图”,第二行为“预计阅读时间: 56分钟”,字体为常规无衬线体,排版居中对齐,无其他图形或装饰元素。

搞懂这些实打实的工程实践,未来不管是你自己手搓 Agent,还是去面试被问到相关架构设计,那绝对是妥妥的降维打击。

一、Claude Code 是什么

在拆源码之前,先简单介绍一下 Claude Code 到底是什么东西。

Claude Code 是 Anthropic 官方推出的编程 Agent 工具。你可以把它理解成一个能直接在你的终端里干活的 AI 程序员,它不是一个聊天窗口,而是真正能读你的代码、改你的文件、跑你的命令、帮你管理 Git 的那种。

深色主题的Claude Code v2.0.0软件界面,顶部有三个窗口控制按钮(红黄绿),界面主体分为左右两栏:左栏显示'Welcome back Meaghan!'、一个橙色像素风格小人图标、'Sonnet 4.5 • Max 20x'及路径'/users/meaghan/code/apps';右栏包含'Recent activity'(列出1m ago、8m ago等时间点的更新内容)和'What's new'(列出新功能如'/agents to create subagents'等);底部为命令行提示符'> ry "edit < filepath> to ..."',整体为终端或IDE风格界面。

说到底,Claude Code 的本质就是一个 AI Agent。但 Agent 这个词现在被用得太泛了,很多人会把它和聊天机器人搞混,所以我得先把这个概念说清楚。

很多人会把 AI Agent 和聊天机器人搞混。其实它们的区别很大。ChatBot 就是你问一句它答一句,一次性的问答。Copilot 呢,是你写代码时给你补全建议,本质上也是一次性预测。

Agent 的核心是一个「感知-决策-行动」的自主循环。你给它一个目标(比如「帮我修复这个 bug」),它会自己决定先读哪个文件、再跑什么命令、然后改哪行代码,整个过程可能循环几十轮,直到任务完成。

一幅关于Agent核心自主循环的卡通风格信息图,中心为三个主要阶段:感知、决策、行动,呈顺时针环形流动。左侧‘感知’阶段包含机器人持放大镜观察环境,配文‘读哪个文件?’‘观察环境’;中间‘决策’阶段机器人思考,气泡内有齿轮、大脑、图表等元素,配文‘再跑什么命令?’‘制定计划’;右侧‘行动’阶段机器人在电脑前写代码、用扳手操作,配文‘执行计划’‘然后改哪行代码?’。顶部有用户与机器人互动场景,用户说‘帮我修复这个bug!’,机器人回应‘用户目标’。底部显示任务完成状态:带绿色对勾的代码框、红色bug图标、‘任务完成’标签。

Claude Code 的 Agent 循环大概是这样的:

一张流程图,展示大模型任务处理的逻辑流程。图中包含多个矩形框(表示步骤)和一个菱形框(表示判断),使用箭头连接表示流程方向。流程从顶部‘用户输入任务’开始,依次经过‘组装 System Prompt + 上下文’、‘调用大模型 API’,然后进入判断节点‘模型返回什么?’;若返回‘tool_use: 我要用某个工具’,则执行‘执行工具: 读文件/改代码/跑命令...’,再将结果拼入消息列表;若返回‘end_turn: 我说完了’,则直接‘返回最终结果给用户’;无论哪种路径,最终都汇合到‘把工具结果拼入消息列表’,并有一条箭头指回‘调用大模型 API’形成循环。

注意看这个循环的关键:大模型自己决定下一步做什么

它不是按照预定义的流程图走的,而是每次看到当前的对话上下文后,自主判断「我现在应该读个文件」还是「我应该执行一条命令」还是「我可以回复用户了」

好,理解了 Claude Code 是什么、它的核心循环长什么样,接下来我们就可以开始看源码了。先从它的整体架构看起。

二、架构设计

一个能自主编程的 Agent 要处理的事情非常多:调大模型 API、执行 40 多种工具、管理权限、压缩上下文、维护记忆、支持多 Agent 协作……如果这些东西全部塞在一个文件里,代码会立刻变成一团乱麻。

那 Claude Code 是怎么组织这些子系统的?它采用了一个四层分层架构

Claude Code 四层架构图,展示其系统分层结构:引擎层(QueryEngine.ts、query()循环、工具编排)、工具层(Agent全部能力,含文件操作、搜索、执行与协作等40+工具)、服务层(API服务、上下文压缩、记忆系统、MCP协议)、安全与治理层(权限系统、Hook系统、Bash安全)。各层间有箭头标注数据流与依赖关系,如‘调API’‘压缩上下文’‘Agent记忆’‘MCP通信’等,整体采用彩色圆角矩形与箭头连接,顶部标题为‘Claude Code 四层架构图’,配星形和齿轮装饰元素。

我们从上往下,一层一层来理解。

  • 引擎层是 Agent 的「大脑」,负责思考和调度。它的关键设计原则是不包含任何业务逻辑,它不知道怎么读文件、怎么改代码、怎么搜索,这些全是工具层的事。引擎层只做三件事:第一,协调,把用户输入、系统指令、历史对话拼在一起,发给大模型;第二,分发,大模型说「我要用某个工具」时,找到对应的工具并执行;第三,决策,根据大模型的返回决定是继续循环还是结束对话。这种设计的好处是:新增能力只需要新增一个工具,引擎层完全不用改

  • 工具层是 Agent 的全部「能力」,40 多个工具都在这一层。每个工具就是 Agent 的一个能力:执行 Shell 命令、读写文件、搜索代码、生成子 Agent……这些工具不是随便写的,它们遵循一个统一的规范。这个规范不仅定义了「工具能做什么」,还强制定义了三个安全属性:这个工具是只读的还是会改东西的?它是否具有破坏性需要额外确认?它能不能和其他工具同时执行?这三个属性不是「建议加上」的,而是类型系统强制要求的,漏了任何一个,代码就编译不过。这意味着每一把刀都有刀鞘,从出厂就配好了安全机制

  • 服务层是所有层共享的「基础设施」。这一层包括三样东西:调大模型 API(不管是谁要调,主循环也好、子 Agent 也好,都走这一层)、上下文压缩(后面会详细讲的五步压缩策略)、MCP 协议(和外部工具服务器通信的标准接口)。你可以把它类比成大楼的水电煤,所有楼层都需要,但谁也不会自己去铺设管道。

  • 安全与治理层有点特殊,它不像其他三层那样各管一块,而是像一张安全网罩在所有层上面。权限系统决定哪些操作需要用户确认、哪些可以自动执行;Hook 系统允许在工具执行前后插入自定义行为(比如「每次 git push 前自动跑 lint」);Bash 安全模块会对 Shell 命令做语法级别的分析,检测命令注入、路径逃逸等危险模式,而不是简单地用正则匹配关键词。

三、Agent 工作模式

搞清楚了四层架构的宏观布局之后,一个自然的问题来了:引擎层那个主循环里,到底发生了什么?Agent 是怎么「思考」和「行动」的?它用的是什么 Agent 框架?是大家常说的 ReAct 模式吗?

这个问题值得深入聊聊,因为 Agent 的工作模式决定了整个系统的架构走向。

Claude Code 的答案可能出乎你的意料,它没有用 ReAct,而是用了一个更简洁、更高效的模式。

什么是 ReAct

如果你接触过 Agent 开发,大概率听说过 ReAct(Reasoning + Acting)。它是 2022 年提出的一种 Agent 范式,核心思路是把 Agent 的每一步拆成三个阶段:

一张流程图,包含三个紫色边框的矩形框,呈水平布局并由箭头连接形成循环。左侧为'Thought'(推理:我要做什么),中间为'Action'(行动:调用工具),右侧为'Observation'(观察:工具返回了什么)。从Thought指向Action的箭头,从Action指向Observation的箭头,以及从Observation指回Thought的箭头构成闭环流程。

具体来说,模型在每一轮都会先输出一段「思考」(Thought),比如「我需要先读取 config.ts 文件来了解数据库连接配置」;然后选择一个工具调用(Action);最后拿到工具结果(Observation)。这三步不断循环,直到模型认为任务完成。

这个模式在 2022 年非常流行,因为当时的大模型(GPT-3.5 时代)推理能力有限,需要用显式的「Thought」步骤来引导模型一步步思考。

一张关于REACT框架三个关键问题的科普信息图,采用卡通插画风格。左上角标题为'REACT 的 3 个关键问题'。图分为三部分:1. TOKEN浪费:左侧机器人AI形象与右侧张嘴怪兽(象征Token浪费),怪兽旁有金币堆;下方循环箭头图示表示多轮Thought导致Token浪费。2. 代码复杂且易崩:中间区域展示用户在电脑前操作,模型输出经解析后分出Action和Observation两条路径,但标注'格式不标准!';文字说明解析Thought/Action易出BUG,提取Action+Observation复杂麻烦。3. 为弱模型设计:右下角对比弱模型(旧版AI机器人)与强模型(带闪电的奔马,代表Claude Opus等),弱模型需显式Thought辅助推理,强模型则内部隐式推理无需显式Thought文本。

但 ReAct 有几个问题:

  • 第一个问题:Token 浪费。  每一轮都要输出一段 Thought 文本,这些文本要作为上下文的一部分发给 API,占用了宝贵的 Token 预算。对于编程 Agent 来说,一次任务可能循环 50 轮,每轮都写一段「我打算先读取……然后分析……」的思考过程,加起来就是好几万 Token 的浪费。

  • 第二个问题:应用层代码太复杂。  你需要解析模型的输出,区分「哪部分是 Thought、哪部分是 Action」,然后提取 Action 调用工具,再把 Observation 拼回去。这个解析过程写起来很麻烦,而且很容易出 bug,因为模型输出的格式不一定标准,一崩就全崩了。

  • 第三个问题:ReAct 是为「弱模型」设计的。  当大模型的推理能力不够强时,用显式的 Thought 来「强迫」它一步步思考是有意义的。但 Claude Opus 这种级别的模型,推理能力已经足够强了,它完全可以在内部完成推理,不需要在输出里显式写出每一步的思考过程。

Tool-Use Loop

Claude Code 没有采用 ReAct 的 Thought-Action-Observation 三步循环,而是用了一个更简洁的模式,我把它叫做 Tool-Use Loop

核心思路非常简单,就一个 while(true) 循环:

一张流程图,展示大模型调用与工具执行的决策流程。图中包含五个紫色矩形框和一个菱形判断框,通过箭头连接。顶部矩形框为‘组装消息: System Prompt + 历史 + 用户输入’,向下指向‘调用大模型 API’,再进入菱形判断框‘模型返回什么?’;该判断框分出两条路径:左侧标注‘tool_use’指向‘执行工具’矩形框,右侧标注‘end_turn’指向‘任务完成,返回结果’矩形框;两个分支最终汇入底部‘工具结果拼入消息列表’矩形框;其中‘工具结果拼入消息列表’框有一条箭头指回顶部‘组装消息’框,形成循环。

看到区别了吗?没有 Thought 步骤

模型在内部完成推理(通过 Extended Thinking,这是 Claude Opus 的一个能力,模型在生成回复前会在内部进行一段不可见的深度推理,不占用上下文空间),然后直接返回两种结果之一:

  • **tool_use**:「我要用某个工具」,应用层执行工具,把结果拼入消息列表,继续循环  
  • **end_turn**:「我说完了」,跳出循环,把最终结果返回给用户

一张关于Claude Code简洁模式与传统ReAct模式对比的科普信息图,采用卡通风格设计。顶部标题为‘Claude Code 的简洁模式:工具使用循环’。左侧为‘传统模式:ReAct (Thought-Action-Observation)’,展示一个机器人思考(Thought)、执行外部工具(Action)、观察结果(Observation)并返回结果的多轮循环流程,强调‘思 考步骤可见且占用空间’、‘多轮循环,流程繁琐’、‘思考步骤的占用工用’等;右侧为‘Claude Code 模式:Tool-Use Loop (工具使用循环)’,突出‘Extended Thinking’和‘模型内部完成推理(不可见)’,无Thought步骤,通过‘工具使用 (tool_use)’直接调用应用层工具,结果拼入消息列表,支持‘我要用某个工具’→‘继续循环’或‘我说完了’→‘跳出循环,返回最终结果给用户’;底部‘核心哲学’区域包含两个要点:① 信任模型推理能力;② 保持应用层框架尽可能简单。

这个设计的核心哲学是:信任模型的推理能力,保持应用层框架尽可能简单

来看 query.ts 中的核心循环,它的实际代码长这样(这是一段 TypeScript 代码,其中 yield 的作用是流式输出,你可以理解为一边接收 API 的响应,一边把每个 token 实时传给 UI 显示):

async function* queryLoop(
  params: QueryParams,
  consumedCommandUuids: string[],
): AsyncGenerator<StreamEvent | Message, Terminal> {
  let state: State = { messages, toolUseContext, turnCount: 1, ... }

  while (true) {
    // 步骤 1:压缩上下文(五步从轻到重)
    // 步骤 2:调用大模型 API,流式接收
    for await (const event of streamAPI(params)) {
      yield event  // 流式输出每个 token
    }
    // 步骤 3:分析模型返回
    if (response.stopReason === 'end_turn') break // 完成了,跳出循环

    // 步骤 4:执行工具调用(并发/串行编排)
    const toolResults = await executeToolCalls(toolUseMessages)

    // 步骤 5:更新 state,继续循环
    state = { ...state, messages: updatedMessages, turnCount: turnCount + 1 }
    continue
  }
}

注意 breakcontinue,模型说 end_turnbreak 跳出循环,说 tool_usecontinue 回到循环开头。整个决策逻辑就这么简单。

为什么比 ReAct 更好

你可能会问:不就是把 Thought 去掉了吗,有什么了不起的?区别其实很大,我列了三个关键原因:

第一,Extended Thinking 让推理在「模型内部」完成。  Claude Opus 支持 Extended Thinking,模型在生成最终回复之前,会在内部进行一段不可见的深度推理。这段推理发生在模型内部,不占用应用的上下文空间,也不需要应用层去解析。所以 ReAct 的 Thought 步骤在 Claude 的架构里是多余的,模型已经在内部「想好了」,不需要在外部输出中再写一遍。

第二,API 原生支持 tool_use。  Claude 的 API 原生支持工具调用,模型可以直接返回 tool_use 类型的响应,不需要用正则表达式从文本中提取「Action」。这消除了 ReAct 的格式解析问题,应用层代码变得极其简洁。

第三,end_turn 作为天然的终止信号。  ReAct 需要一套额外的规则来判断「Agent 是否完成了」,比如检测输出中是否包含「Final Answer」。而 Tool-Use Loop 用模型的 end_turn 信号作为终止条件,这是 API 层面的原语,语义清晰,不需要任何解析。

用一个表格来总结两者的区别:

维度 ReAct Tool-Use Loop
推理方式 显式 Thought 文本 模型内部 Extended Thinking
工具调用 解析文本提取 Action API 原生 tool_use
终止判断 检测 「Final Answer」 API 原生 end_turn
Token 开销 每轮要输出 Thought 无额外开销
编排复杂度 高(需要解析 Thought/Action) 低(只需要 if/else)
适合场景 弱模型 + 简单工具 强模型 + 复杂工具集

Claude Code 的 Agent 工作模式可以总结为一句话:信任模型的推理能力,把应用层框架做得尽可能简单

ReAct 的设计哲学是「帮模型思考」,用显式的 Thought 步骤引导模型一步步推理。这在弱模型时代是必要的。

但 Claude Code 面对的是 Opus 级别的强模型,它的推理能力完全可以在内部完成,不需要应用层去「教」它怎么想。

所以 Claude Code 的 Tool-Use Loop 只做最简单的事:调 API、执行工具、再调 API。推理交给模型,执行交给工具,编排交给最简单的 while(true) 循环。

这种「大道至简」的设计,反而是最高效的。

Plan Mode

Claude Code 不仅有 Tool-Use Loop 这种「边想边做」模式,还有 Plan Mode,一个更精细的两阶段工作流:先规划、再执行。

一张关于Claude Code规划模式(Plan Mode)的流程图插画,背景为米色带齿轮、云朵、星星等装饰元素。顶部标题为‘Claude Code 的规划模式 (Plan Mode)’,副标题‘精细的两阶段工作流:先规划,再执行’。左侧展示‘Tool-Use Loop’循环:一个戴耳机、手持扳手和锤子的机器人在笔记本电脑与电路板间操作,标注‘边想边做’‘容易跑偏’‘浪费精力’,并用箭头指向右侧主流程;中间是‘1. 规划 (Planning)’阶段:机器人坐于桌前,面前有蓝图、放大镜,气泡内列有‘分析任务’‘设定目标’‘制定步骤’,下方配脑、指南针、地图图标,标注‘理清方向’;右侧是‘2. 执行 (Execution)’阶段:机器人手持螺丝刀操作电路板,旁有蓝图、代码窗口、芯片,标注‘精确操作’;底部横向排列三个图标组合:机器人+对勾→旗帜,代表‘针对复杂任务’;山丘→路径,代表‘避免跑偏’;手+齿轮→时钟→旗,代表‘避免浪费精力’。

Plan Mode 的核心思想是:复杂任务应该先规划再执行,避免方向跑偏、浪费精力。它并不是一个独立的框架,而是在同一个 Tool-Use Loop 中通过 EnterPlanModeExitPlanMode 两个工具实现的:

UML序列图,展示了用户重构认证模块时系统各组件的交互流程。从左到右有四个参与者:用户、query()循环、Plan Mode、实施阶段。图中包含带箭头的消息流和自循环,描述了从用户发起请求、模型判断任务复杂性、进入Plan Mode进行只读探索与方案设计、退出后展示计划请求审批、审批通过后恢复权限并进入实施阶段执行命令的完整流程。

整个流程分三步:

  • 第一步:模型自主进入或用户手动触发。  当模型判断「这是一个复杂任务」时,它会调用 EnterPlanMode 工具。对于简单任务(修 typo、加 console.log),则明确不进入。用户也可以通过 Shift+Tab 手动切换。

  • 第二步:只读探索 + 设计方案。  进入 Plan Mode 后,权限降为只读,模型只能用 Read、Grep、Glob 这些工具去探索代码库,不能写文件、不能改代码、不能跑命令。探索完后,把计划写入 .claude/plans/ 目录。每 5 轮对话,系统会偷偷给模型塞一张「小纸条」,提醒它「你现在还在 Plan Mode,别手痒改代码」,防止模型在长对话中「走神」。

  • 第三步:用户审批后实施。  模型调用 ExitPlanMode,此时需要用户确认。用户批准后,权限恢复为之前的模式,模型开始自由执行读写操作,按计划实施。

Plan Mode 最值得学习的设计是「工具即能力」

对模型来说,Plan Mode 不是一种特殊的「模式切换」,而只是调用了 EnterPlanModeExitPlanMode 这两个工具。

就像调用 Read 工具读文件一样自然。整个过程不需要引擎层做任何特殊处理,query() 仍然只是一个简单的 while(true) 循环。

四、System Prompt 的构造

System Prompt 就是 Claude Code 的灵魂,它定义了 Agent 的身份、行为规范、可用工具、安全约束……一切。

但 Claude Code 的 System Prompt 不是一个静态的文本文件。它是动态组装的,由十几个 Section 拼接而成,而且在组装过程中做了非常精巧的缓存优化

一张关于Claude Code系统提示词中动态组装与缓存优化的插画式说明图。画面左侧展示‘静态文本文件’(卷轴图标,标注‘陈旧、一成不变’)与‘非静态模式’(三个堆叠的彩色方块机器人,标注‘动态变化、灵活’、‘不是一个静态文件’)。中间为‘动态组装流程’,含传送带上的多个Section模块(Section 1至Section N),经‘动态组装器’处理,上方有‘十几个Section拼接’字样;下方标注‘缓存优化 精巧缓存设计’。右侧为‘缓存区’(含灯泡、问号等图标),通过箭头指示‘快速取出’‘缓存未命中→更新生成’‘精巧缓存设计’;右下角是‘最终Prompt文件’(卷轴图标,标注‘精巧优化结果’)。顶部标题为‘Claude Code 系统提示词:动态组装与缓存优化’,横幅强调‘核心特性:非静态、动态、精巧’。

我们先来看一下,Claude Code 的 System Prompt 到底长什么样,它是怎么「调教」大模型变成一个靠谱的编程 Agent 的。

注:Claude Code 源码中所有 Prompt 原文均为英文。为了让大家更好地理解设计思路,下面展示的 Prompt 内容我翻译成了中文,并保留了关键术语的英文原文。

角色定义与安全红线

每个 Agent 的 System Prompt 都要回答一个根本问题:你是谁?Claude Code 的开场是这样的:

你是一个交互式代理(interactive agent),帮助用户完成软件工程任务。

请使用下面的指令和可用的工具来协助用户。

重要:你绝对不能为用户生成或猜测 URL,除非你确信这些 URL
是为了帮助用户完成编程任务。你可以使用用户在消息或本地文件中
提供的 URL。

注意两个关键点。第一,它把自己定位为「interactive agent」,而不是「assistant」或「chatbot」,这从一开始就暗示了模型应该主动采取行动,而不是被动回答。第二,立刻划了安全红线:不能乱编 URL。这

看起来是个小事,但对编程 Agent 非常重要,如果模型瞎编一个 npm 包的 URL,用户执行了就可能中招。

紧接着是一段安全约束指令,这段话非常值得每个 Agent 开发者抄作业:

重要:允许协助已授权的安全测试、防御性安全研究、CTF 挑战赛
和教育场景。拒绝涉及破坏性技术、DoS 攻击、大规模目标扫描、
供应链攻击或用于恶意目的的检测规避请求。

这段 Prompt 没有用「绝对不能做 X」的口吻,而是先说「可以做什么」(授权的安全测试、CTF 挑战),再划定「不能做什么」(DoS、供应链攻击)。

这种「先肯定再约束」的写法,比纯禁止清单效果好得多,它给了模型清晰的判断依据,而不是一堆模糊的红线。

行为准则

接下来是一大段关于「怎么做事」的行为指南,这部分是 Claude Code System Prompt 的精华。我挑几条最值得学习的:

关于修改代码前先阅读:

一般来说,不要对你没有阅读过的代码提出修改建议。如果用户
要求你查看或修改某个文件,先读一遍它。在提出修改建议之前,
先理解现有代码。

这条看起来简单,但解决了 Agent 的一个常见问题:很多 Agent 会根据用户描述直接生成代码,而不先看看现有代码是什么样的,结果经常和项目风格不一致或者引入重复实现。

关于代码风格:「少即是多」:

不要在用户要求之外添加功能、重构代码或进行"改进"。修一个 bug
不需要顺手清理周围的代码。一个简单功能不需要额外的可配置性。

不要为一次性操作创建辅助函数、工具类或抽象层。
三行相似的代码比一个过早的抽象更好。

这个设计思路太重要了。

如果你用过 Agent 写代码,你一定遇到过这种情况:你让它修一个 bug,它顺手把整个文件重构了,加了一堆你没要求的类型标注和错误处理。Claude Code 在 Prompt 里明确禁止了这种行为。

关于失败处理:「先诊断再换方案」:

如果某个方案失败了,先诊断原因再决定是否换方案——读报错信息、
检查你的假设、尝试有针对性的修复。不要盲目重试完全相同的操作,
但也不要因为一次失败就放弃一个可行的方案。

这条解决了 Agent 的另一个常见问题,「摆烂式重试」或「草率放弃」。Claude Code 要求模型先搞清楚为什么失败了,再决定是修复还是换方案,而不是两个极端。

操作安全

Claude Code 对「什么操作需要用户确认」做了非常详细的规定。我建议每个 Agent 开发者都研读这段 Prompt:

仔细考虑操作的可逆性(reversibility)和影响范围(blast radius)。
一般来说,你可以自由执行本地的、可逆的操作,比如编辑文件或
运行测试。但对于难以撤销、影响共享系统或有风险的操作,
请先和用户确认后再执行。

需要用户确认的高风险操作示例:
- 破坏性操作:删除文件/分支、删表、rm -rf
- 难以逆转的操作:force-push、git reset --hard、修改已发布的 commit
- 对他人可见的操作:推送代码、创建/关闭 PR、发送消息
- 上传到第三方工具:内容可能被缓存或索引,即使删除也无法撤回

这段的核心思想是用可逆性影响范围两个维度来判断风险。读文件、改本地代码是低风险的(可逆、只影响本地),直接放行。git push、发 Slack 消息是高风险的(不可逆、影响他人),必须确认。

然后还有一句非常精妙的补充:

用户批准了某个操作(比如 git push)一次,并不意味着他在所有
场景下都批准这个操作。授权仅对指定的范围有效,不能超出范围。

这解决了「权限蔓延」的问题,用户同意了一次 push 不代表以后都自动 push,授权是一次性的、有范围的。这个原则在 Agent 权限设计中非常重要。

工具使用指南

当有专用工具可用时,不要用 Bash 来执行命令。使用专用工具可以
让用户更好地理解和审查你的工作。这一点至关重要:

- 读取文件用 Read 工具,而不是 cat、head、tail 或 sed
- 编辑文件用 Edit 工具,而不是 sed 或 awk
- 创建文件用 Write 工具,而不是 echo 重定向
- 搜索文件用 Glob 工具,而不是 find 或 ls
- 搜索内容用 Grep 工具,而不是 grep 或 rg

这条规则的设计动机值得深思。为什么不让模型直接用 cat 读文件、用 sed 改代码?技术上完全可以。

原因是可审查性。当模型调用 Read 工具读文件时,UI 会清晰地展示「Agent 正在读取 src/index.ts」。但如果模型执行 cat src/index.ts,用户看到的只是一条 Bash 命令和一大坨输出,完全不知道 Agent 在干什么。

而且,专用工具有专用的权限检查,Read 工具会检查文件路径是否在允许范围内,而 cat 命令就没有这层保护了。所以「用专用工具而不是 Bash」不仅是体验问题,更是安全问题。

Git 安全协议

Claude Code 对 Git 操作有一套非常严格的安全协议,这段 Prompt 写得极其细致:

Git 安全协议:
- 绝不修改 git config
- 绝不执行破坏性 git 命令(push --force、reset --hard、
  checkout .、clean -f),除非用户明确要求
- 绝不跳过 hooks(--no-verify),除非用户明确要求
- 绝不 force push 到 main/master 分支,如果用户要求则发出警告

关键:始终创建新的 commit(NEW commit),而不是用 --amend 修改。
当 pre-commit hook 失败时,commit 实际上并没有发生——所以
--amend 会修改上一个(不相关的)commit,可能导致代码丢失。
正确做法是:修复问题后创建一个新的 commit。

最后一条关于 --amend 的警告特别值得注意。

很多人(包括一些 Agent 实现)在 commit 失败后会习惯性地 git commit --amend。但如果失败原因是 pre-commit hook 拒绝了,那么 commit 实际上没发生!

这时候 --amend 会修改上一个(不相关的)commit,可能导致代码丢失。这种微妙的 bug 很难被发现,Claude Code 直接在 Prompt 里防住了。

输出风格约束

Claude Code 对模型的输出风格也有严格规定:

# 输出效率
直奔重点。先尝试最简单的方案。要极度简洁。
工具调用之间的文字不超过 25 个词。最终回复不超过 100 个词。

先给出答案或行动,而不是推理过程。跳过填充词、开场白和
不必要的过渡句。不要复述用户说过的话——直接做就行。

25 个词的限制非常苛刻,这意味着模型在两次工具调用之间,基本只能说一句话。这个设计的目的是避免 Agent 「话痨」,没人想看 Agent 在每次读文件前先写一段「让我来看看这个文件的内容……」的废话。

环境信息注入

每次对话开始时,Claude Code 会把当前环境信息注入 System Prompt:

# 环境信息
- 主工作目录:/Users/you/my-project
- 是否为 Git 仓库:是
- 操作系统平台:darwin (macOS)
- Shell 类型:zsh
- 当前模型:Claude Opus 4.6 (1M context)
- 知识截止日期:2025 年 5 月

这些信息让模型知道自己「在哪里」,是什么操作系统、什么 Shell、什么项目目录。没有这些信息,模型可能会在 macOS 上执行 apt-get install,或者在 zsh 环境里用 bash 语法。

分割线与三级缓存

了解了各个 Section 的内容,我们回到一个很实际的问题:这些 Section 是怎么组装到一起的?为什么组装方式会影响费用?

先看一段组装后的 System Prompt 长什么样(简化版):

┌─────────────────────────────────────────────────┐
│  [角色定义] 你是一个交互式代理,帮助用户完成...    │  ← 所有用户完全一样
│  [安全红线] 重要:允许协助已授权的安全测试...       │  ← 所有用户完全一样
│  [行为准则] 一般来说,不要对你没有阅读过的代码...   │  ← 所有用户完全一样
│  [操作安全] 仔细考虑操作的可逆性...              │  ← 所有用户完全一样
│  [工具使用] 当有专用工具可用时...               │  ← 所有用户完全一样
│  [Git 安全] 绝不修改 git config...             │  ← 所有用户完全一样
│  [输出风格] 直奔重点,要极度简洁...             │  ← 所有用户完全一样
├────── __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ ────────┤
│  [环境信息] 主工作目录: /Users/you/my-project    │  ← 每个用户不一样
│  [CLAUDE.md] 本项目使用 TypeScript + Jest...     │  ← 每个项目不一样
│  [记忆指令] 你有一个持久记忆系统...              │  ← 每次对话可能不一样
│  [MCP 指令] 你已连接 GitHub MCP server...         │  ← 每个用户不一样
└─────────────────────────────────────────────────┘

一张关于系统提示词结构概览的科普插画,分为上下两部分:上半部分为浅黄色背景,标题‘系统提示词结构概览’,下方标注‘[通用固定指令]’,包含机器人形象、三本堆叠书籍(分别标有‘[代理行为与规范]’‘[安全红线]’‘[工具使用]’)、卷轴(标有‘[Git与操作规范]’‘[输出风格]’),两侧有带绿色对勾的对话框写着‘通用指令,所有用户完全一致’;下半部分为浅蓝色背景,标题‘[系统提示词动态边界]’,下方标注‘[个性化动态信息]’,包含代码编辑器、文件图标、大脑、云朵等元素,分别对应‘[个性化环境与目录]’(如‘/Users/.../my-project’)、‘[特定项目文件]’(如‘CLAude.md’)、‘[对话记忆指令]’、‘[特定工具连接]’(如‘GitHub MCP server’),两侧有对话框写着‘动态变化,因人/项目/对话而异’,整体配以齿轮、锤子、人物头像等小图标点缀。

看到中间那条粗线了吗?那就是 Claude Code 在 System Prompt 中插入的分割标记 __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__

分割线之上的内容,对所有用户都完全一样。  不管你是北京的 Java 工程师还是纽约的 Python 开发者,你看到的「角色定义」「行为准则」「Git 安全协议」这些内容是一模一样的。

分割线之下的内容,每个用户都不同。  你的工作目录、你的 CLAUDE.md、你的记忆文件、你连接的 MCP 服务,这些是因人而异的。

一张关于Claude Code的 System Prompt 组装与三级缓存体系的架构示意图,整体采用卡通风格。左上角标题为'Claude Code 的 System Prompt 组装与三级缓存体系'。图中包含多个模块:左侧机器人与齿轮、积木等象征开发流程;中间核心区域展示'动态分割线(_SYSTEM_PROMPT_DYNAMIC_BOUNDARY_)'将提示词分为上下两层——上层面向所有用户统一配置(Section [角色定义]、[行为准则]、[Git 安全]、[工具使用]),下层面向不同用户角色定制(环境信息、CLAUDEx.md、记忆、MCP指令),并标注'因人而异';右侧展示缓存体系:全局缓存接收跨组织用户共享数据,下方三个组织缓存(组织A缓存、组织B缓存、组织B缓存)支持同一组织内会话共享;顶部强调'Prompt 前缀复用,费用降低90%'及'所有用户完全一样';底部说明'每一个都在帮API省钱'、'会话内计算一次'、'同一个Section一次会话计算一次';图中还标注了北京Java工程师、纽约Python开发者、MCP指令工程师等角色及其交互路径;右上角显示组织A/B/C建筑图标及用户头像;整体布局清晰,色彩丰富,以绿色、蓝色、橙色为主色调,辅以箭头指示数据流向。

为什么要这么分?因为 Claude API 有一个 Prompt Cache 机制:如果两次请求的 Prompt 前缀完全相同,API 会复用上次的计算结果,费用可以降低 90%。对于几万 Token 的 System Prompt 来说,缓存命中与否意味着每次请求几美分和几美元的差距。

分割线之上的内容对所有用户都一样,所以可以全球所有用户共享同一份缓存——你用的和东京的开发者用的是同一份。而分割线之下的内容因人而异,没法共享,只能实时生成。

这就是 Claude Code 的三级缓存体系:全局缓存(分割线之上,跨组织跨用户共享)→ 组织缓存(同一组织内跨会话共享)→ 会话缓存(同一个 Section 在一次会话内只计算一次)。每一级都在帮 API 省钱。

小结

回过头来看 Claude Code 的 System Prompt,你会发现它其实在做一件事:用最小的 Token 成本,给模型划出最清晰的行为边界

怎么划的呢?我总结了三个最值得抄作业的设计。

第一个是「先给范围再画红线」。比如安全约束那段,它不是一上来就说「不准做这不准做那」,而是先说「安全测试、CTF 挑战这些可以做」,然后再说「DoS、供应链攻击这些不能做」。这比你写十句「不准 XX」管用得多,因为模型拿到了判断标准,而不是一堆模糊的禁令。

一张手绘卡通风格的教育类信息图,主题为‘Claude Code System Prompt 抄作业设计(手绘卡通版)’,顶部有机器人和齿轮装饰。图像分为三个主要部分:1. 先范围,后红线——左侧绿色区域标‘可以做(范围)’,含安全测试、CTF挑战图标及机器人点赞手势;右侧红色区域标‘禁止做(红线)’,含DoS攻击、供应链攻击图标及机器人摇头拒绝手势;底部机器人对话框显示‘我有判断标准了!(不vague bans)’,旁边对比‘规则与则的判断’。2. 赋予原则与上下文——中间区域展示‘System Prompt’卷轴、大脑图标,强调‘最小Token,最清晰边界’;下方分‘清晰原则’(大脑带对勾)与‘模糊禁令’(大脑皱眉),分别对应‘有用上下文’与‘模糊禁令’,并注明‘提供判断依据,非模糊禁令’。3. 简洁精炼——右侧区域用铅笔和橡皮擦图标,对比‘好’与‘坏’:左侧‘System Prompt’卷轴标注‘Token:低’‘精减字数,保留核心’;右侧乱涂卷轴标注‘Token:高’‘System’字样。

第二个是「用两个维度把风险分出层次」。Claude Code 判断一个操作安不安全,不看它「看起来危不危险」,而是看两件事:这操作能撤回吗?会影响别人吗?改本地代码当然能撤回、只影响自己,直接放行。git push 撤不回来、别人能看到,那就得确认。这个思路比笼统的「危险/安全」二分法精细太多了。

一张关于用双维度看操作风险:Claude Code 的风险层次判断的双维度分析图,采用卡通风格设计。顶部横幅标题为‘用双维度看操作风险:Claude Code 的风险层次判断’。左侧区域标注‘笼统的二分法’,对比‘安全’(绿色对勾、笑脸)与‘危险’(红色叉号、爆炸图标),并有箭头指向中间的双轴坐标系。纵轴为‘能撤回吗?’(可逆/不可逆),横轴为‘影响别人吗?’(仅自己/影响他人)。右上象限为‘改本地代码’(可逆),对应‘直接放行’;右下象限为‘Git Push’(不可逆),需确认是否影响他人。图中包含多个机器人形象、代码窗口、服务器、人群等插画元素,底部大框强调核心观点:‘不看起来危不危险 → 看可撤回与影响范围’。

第三个是「静态内容和动态内容用分割线隔开」。那条分割线不是随便画的,它把所有用户都一样的部分和因人而异的部分切开了。这样做的好处是,分割线之上的内容可以被全球所有用户共享缓存,每次 API 调用能省 90% 的费用。一个看似简单的排版调整,背后是实打实的成本优化。

一张关于静态内容与动态内容对比的插画式信息图,顶部横幅标题为‘静态内容 vs. 动态内容:分割线节省成本!’。左侧展示‘静态内容(全球用户相同)’,配以卷轴、纸箱等图标,标注‘通用固定指令’‘代理行为与规范’‘安全红线’‘Git与操作规范’‘输出风格’;中间有机器人形象手持放大镜,下方标有‘巧妙分割线’,连接至‘动态内容(因人而异)’区域,含文件路径‘/Users/.../my-project’、TypeScript + Jest、GitHub MCP server等技术元素;右侧中央为云朵与地球组合图示,云朵内有双向箭头和盾牌,标注‘全球共享缓存!’;右上角显示‘-90% API 费用!’配以被划掉的美元符号;右下角钱袋与金币图案旁有对话框文字‘简单排版,实打实优化成本!’;整体风格为手绘卡通,色彩明亮,布局清晰呈现内容分层与成本节约逻辑。

五、记忆系统

每次启动 Claude Code 都是一个全新的会话,模型不记得上次对话的任何内容。但用户的偏好、项目背景、行为反馈,这些信息需要跨会话保持。

这个问题看起来简单,做起来却非常难。业界常见的方案是用向量数据库,把记忆存成 embedding,每次对话时做相似度检索。

一张关于Claude Code跨会话记忆秘籍的科普插画,整体为复古卷轴风格背景,包含多个卡通机器人形象和图文说明。左上角标题为'Claude Code 跨会话记忆秘籍',下方分三部分:左上部分讲述问题(每次启动全新会话)及必须记住的内容(用户偏好、项目背景、行为反馈),配以机器人思考表情;右上部分介绍业界常见方案——向量相似度检索,展示记忆存储为Embedding的过程;中间横幅标题'Claude Code 为什么不这么做?'下分左右两块:左侧机器人强调Agent需记住结构化行为指令(如不准mock数据库、始终使用TypeScript);右侧机器人面对漩涡状向量检索过程,指出其匹配效果差、存在DB类型/Schema/SQL等规则不匹配、拒千论、规则淹没等问题。底部横幅总结核心原则:Agents需要精确指令,而非相似片段。

但 Claude Code 没有这么做。为什么?

因为 Agent 需要记住的大部分不是「相似的文档片段」,而是「用户说过'不要 mock 数据库'」这种结构化的行为指令

用向量相似度去检索「不要 mock 数据库」这句话,效果其实很差,它可能匹配到一堆包含「数据库」关键词的无关内容,真正重要的行为反馈却被淹没了。

Claude Code 设计了一套完全不同的记忆系统,我们来一层一层拆解。

记什么:四类型分类

Claude Code 把记忆分成了四种明确的类型

export const MEMORY_TYPES = [
  'user',      // 用户画像:角色、偏好、知识水平
  'feedback',  // 行为反馈:该做什么、不该做什么
  'project',   // 项目动态:在做什么、截止日期、协作信息
  'reference', // 外部指针:哪里能找到什么信息
] as const

注意,只有这四种,不能随便加新的

一张关于Claude Code记忆分类的卡通风格信息图,整体采用手绘插画风格,边框为装饰性卷曲线条。顶部横幅写有‘Claude Code 记忆分类’。图中分为四个主要区域:左上‘用户 user’(蓝色背景),含用户画像图标及说明;右上‘反馈 feedback’(绿色背景),含规则书与点赞/点踩手势;左下‘项目 project’(橙色背景),含图纸、扳手、日历等工具图标;右下‘参考 reference’(米色背景),含书架、放大镜、链接图标。中间区域以数字‘4’为核心,展示两个决策场景:左侧为‘垃圾堆’配‘不受约束的记忆’与‘避免通用类型 ANY’警示;右侧为‘限定四种类型’的分类决策流程,含问号与四个编号文件夹。

为什么不搞一个通用的「any」类型什么都能存?因为无约束的记忆会迅速膨胀成垃圾堆。

限定四种类型,就是在逼 Agent 做分类决策。每存一条记忆,它必须想清楚「这到底属于哪一类」,而不是一股脑往里塞。

我逐个解释一下这四种类型的设计意图。

User(用户画像)是最个人化的一类,记住用户是谁、擅长什么、知识水平如何。比如用户说「我是一个写了十年 Go 的后端工程师,第一次接触 React」,Agent 就应该在解释前端概念时用后端的类比,而不是从零讲起。这类记忆让 Agent 的回答因人而异,而不是千篇一律。

一张关于Agent用户记忆秘诀的科普插画,标题为‘因人而异:Agent 的用户记忆秘诀’。左侧展示传统方式‘千篇一律’的缺点:机器人说‘那个一律的标准答案’,配红色叉号和‘千篇一律’字样;中间分三步流程图:① 用户(男孩+Gopher吉祥物)写十年Go后端 → ② Agent思考(机器人+大脑/ Goroutine等概念气泡)理解背景、避免从零开始 → ③ 灵活解释React概念(机器人用后端的类比解释React组件、Hook),最终输出‘定性用户爽的答案’。右侧展示Agent优势:具备身份记忆、技能掌握、知识水平、灵活调整四大能力,配绿色对勾与‘理解用户’标签。

Feedback(行为反馈)是最重要的一类,记住用户说过「不要做什么」和「做得好继续保持」。这类记忆的关键在于,它不仅记规则本身,还要求记录 Why(为什么)How to apply(怎么应用)

规则本身:集成测试必须使用真实数据库,不能用 mock
Why:上季度 mock 测试全部通过但生产环境迁移失败了
How to apply:在这个模块写测试时,始终连接真实数据库

为什么一定要记 Why?因为光记住「不要 mock 数据库」是不够的。如果遇到一个边缘情况,比如一个纯单元测试不涉及数据库迁移,Agent 需要根据 Why 来判断「这条规则在这个场景下是否适用」。没有 Why,Agent 只能盲目遵守,可能在不该用真实数据库的地方也强行连接。

一张关于‘行为反馈(Feedback)的秘诀’的卡通风格信息图,整体呈卷轴样式,背景为米白色。顶部中央是标题‘行为反馈(Feedback)的秘诀’,配有机器人、齿轮、铅笔、书本等插画元素。主体分为多个区域:左上角‘规则’部分对比‘集成测试×mock’与‘真实数据库’,用红色叉和绿色对勾标识;中间‘为什么?’部分说明‘上季度mock通过,迁移失败!’并配服务器与齿轮图标;右侧‘怎么做?’部分展示‘写此模块测试,连接真实数据库’及服务器连接图。下方分三栏:左侧‘没有Why?’强调‘盲目遵守!不该用的场景也强行连接’,配蒙眼机器人;中间‘有Why?’强调‘智能判断!根据场景适用规则’,配齿轮与对勾;右侧‘纯正单元测试’说明‘这个不用,可以mock!’,配持放大镜的机器人。

Project(项目动态)记的是「正在发生什么」,谁在做什么、截止日期是什么、有什么重要决策。这类记忆有一个特殊要求:必须把相对日期转成绝对日期。用户说「周四之前冻结合并」,Agent 要存成「2026-03-05 之前冻结合并」,因为「周四」过几天就没意义了,但「2026-03-05」永远准确。

一张关于项目动态(Project)信息记录与管理的卡通风格信息图,整体背景为米黄色,带有小图标装饰。顶部横幅标题为‘Project(项目动态):记的是「正在发生什么」’。左侧区域标注‘核心要素’,包含三个模块:‘谁’(图标为多人头像,文字‘Dev A developers’和‘Developers’),‘在做什么’(机器人与人类协作展示图表、火箭升空图标,文字‘Feature X launch’),‘截止日期’(闹钟图标,文字‘截止日期’),以及‘重要决策’(文件夹图标,内含‘Merge freeze’字样)。各模块通过红色箭头指向中央的‘Project 记忆卡’图标(类似存储芯片样式)。右侧区域分为两部分:上半部分‘特殊要求:日期转换’,说明相对日期(如‘周四’)无意义,需转换;配有人物对话气泡‘『周四之前冻结合并』’及机器人手持激光笔指向日历‘Mon 周一’与‘Thu 周四’;下半部分‘绝对日期(永远准确)’,展示机器人手持‘2026-03-05 之前冻结合并’的卡片,旁边是日历显示‘Thu 2026-03-05’,底部有黄色标签‘精确转换,永远有效’。

Reference(外部指针)记的是「去哪找什么信息」,Bug 在 Linear 的哪个项目里追踪、Grafana 看板的地址是什么、Slack 的哪个频道能问到相关的人。这类记忆的价值在于,Agent 不需要知道外部系统的具体内容,只需要知道去哪里找

一幅卡通风格的插画,中心是一个带‘AI’标识的蓝色机器人,手持指南针和地图,周围有多个信息来源指向它:左上角是Gopher形象代表‘Linear Bug 追踪’,其卷轴显示‘Destinations Linear 项目’并标注‘去追踪 Bug’;右上角是女性角色代表‘Grafana 看板’,卷轴显示‘Destinations Grafana 地址’并标注‘看板数据’;左下角是另一名女性角色代表‘Slack 问人’,卷轴显示‘Destinations Slack 频道’并标注‘找人问问题’;底部云朵中写有‘Agent 的价值:只要去哪里找’,并附有图标说明‘记“去哪里找”’和‘无需知道具体内容’;背景有齿轮、云朵、折线图等元素,整体表现AI代理自动从不同平台获取信息的能力。

不记什么:排除清单

Claude Code 明确规定了什么不应该存到记忆里,这个设计和「记什么」同样重要。

一张关于Claude Code记忆排除清单的卡通风格信息图,中央为一个带齿轮的脑部插画,象征AI思考过程。左侧绿色区域‘代码与结构’含git、grep图标及CLAUDEx.md文件;右上角‘Git历史记录’展示git log与git blame命令及时间戳列表;左下‘修复与上下文’用扳手图标和commit示例说明上下文修复;中下‘文件已有内容’强调避免重复;右下‘临时与会话级’含对话气泡与task清单,标注temp tags;顶部横幅写有‘Claude Code 记忆排除清单’,左右角标‘同样重要的排除设计’;中间文字说明‘可以从当前代码推导的信息,一律不存’。

首先是代码模式、项目架构和文件结构这些信息,通过 grepgitCLAUDE.md 就能获取,存在记忆里反而会导致记忆和代码实际状态不一致。

然后是 Git 历史和最近的改动,git loggit blame 才是权威来源,不需要记忆系统再来存一遍。调试方案和修复方法也不存,因为修复已经在代码里了,commit 消息已经记录了上下文。

CLAUDE.md 里已经写了的内容也不存,避免重复。最后是临时任务状态和当前对话上下文,这些是会话级的信息,不需要跨会话保持。

这个排除清单背后的核心原则是:可以从当前代码推导出来的信息,一律不存

因为代码是「活的」,它随时在变,但记忆是「死的」,它存下来就定格了。如果记忆说「AuthService 在 src/auth.ts 第 42 行」,但代码已经重构了,那这条记忆就变成了一个「权威的错误」,比没有记忆还糟糕。

怎么存:索引 + 独立文件

搞清楚了「记什么」和「不记什么」,接下来看「怎么存」。

每条记忆存为一个独立的 .md 文件,文件开头有一段 YAML 格式的元信息(你可以理解为这条记忆的「身份证」):

---
name: no-mock-database
description: 集成测试必须使用真实数据库,不能用 mock
type: feedback
---

集成测试必须使用真实数据库,不能用 mock。

**Why:** 上季度 mock 测试全部通过但生产环境迁移失败了。

**How to apply:** 在这个模块写测试时,始终连接真实数据库。

文件开头那段 YAML 格式的元信息里,三个字段各有用途:name 是人类可读的标识;description 是一句话摘要,专门用于检索时的相关性匹配(后面会讲到);type 标记四类型之一。

然后有一个 MEMORY.md 文件作为索引,它是一个不超过 200 行(25KB)的轻量目录:

- [No Mock Database](feedback_no_mock_db.md) — tests must use real DB
- [User Preferences](user_preferences.md) — prefers terse responses
- [Auth Rewrite](project_auth_rewrite.md) — driven by compliance, not tech debt

注意这个 200 行的硬性上限。为什么要限制?来看源码里的截断逻辑:

export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000  // 25KB

export function truncateEntrypointContent(raw: string): EntrypointTruncation {
  // 同时检查行数和字节数上限
  const wasLineTruncated = lineCount > MAX_ENTRYPOINT_LINES
  const wasByteTruncated = byteCount > MAX_ENTRYPOINT_BYTES

  if (wasLineTruncated || wasByteTruncated) {
    // 截断并附加警告
    return {
      content: truncated + '\n\n> WARNING: MEMORY.md 太大了...',
      // ...
    }
  }
}

它同时检查行数字节数两个维度。为什么要两个?因为有人可能写了 199 行,每行 500 字,行数没超但字节数爆了。双重检查堵住了这个漏洞。

现在来看整个存储架构的关键设计:MEMORY.md 索引始终被加载到 System Prompt 里,但独立记忆文件按需加载

一张流程图或依赖关系图,中心为一个粉红色矩形框,内含文字'MEMORY.md 索引 (200行 / 25KB) 始终加载到 System Prompt',从该框向右上方、右方、右下方延伸出五条带箭头的连线,分别指向五个浅紫色矩形框,自上而下依次为'feedback_no_mock.md'、'user_preferences.md'、'project_auth.md'、'reference_linear.md'、'...更多记忆文件',整体布局呈树状结构,背景为纯白色。

这解决了一个经典矛盾,如果把所有记忆都塞进 System Prompt,50 条记忆就可能占满上下文;如果完全不塞,Agent 又不知道有哪些记忆可用。

索引文件两全其美:Agent 看到索引就知道有哪些记忆,但只加载真正相关的那几条。

怎么召回:Sonnet 当秘书

存好了记忆,关键问题来了:每次对话时,怎么从几十条记忆里挑出最相关的那几条加载进来?

Claude Code 的做法非常巧妙,用一个廉价的小模型(Sonnet)来做记忆检索。

一幅关于Claude Code记忆检索:巧妙的挑选的卡通风格流程图,主题为Claude Code记忆检索:巧妙的挑选。画面顶部有绿色横幅标题,周围点缀星星、云朵、灯泡和齿轮等图标。左侧展示问题:对话中挑选相关记忆,配有两个装满文件的木箱,分别标注‘存好的记忆(几十条)’和‘项目A代码、用户偏好、任务B状态’,下方男孩提问‘这么多记忆,怎么挑选相关的?’,机器人回应。中间部分为解决方案:Sonnet廉价小模型检索,一个戴护目镜的机器人手持放大镜查看标有‘项目A代码’和‘API密钥’的文件,旁边有‘智能挑选相关记忆’箭头指向右侧。右侧为结果:加载精准记忆,一个机器人指向三卷标有‘API密钥’‘项目A代码’‘用户偏好’的卷轴,下方对话框显示‘当前对话上下文’‘API密钥’‘最近代码修改’,男孩与机器人互动。

整个召回流程分为三步:

第一步:扫描所有记忆文件的「头部信息」

export async function scanMemoryFiles(
  memoryDir: string,
  signal: AbortSignal,
): Promise<MemoryHeader[]> {
  const entries = await readdir(memoryDir, { recursive: true })
  const mdFiles = entries.filter(
    f => f.endsWith('.md') && basename(f) !== 'MEMORY.md',
  )
  // 只读每个文件的前 30 行(frontmatter 区域),不读全文
  const headers = await Promise.allSettled(
    mdFiles.map(async (relativePath) => {
      const { content, mtimeMs } = await readFileInRange(
        filePath,
        0,
        30,  // 只读前 30 行!
      )
      const { frontmatter } = parseFrontmatter(content)
      return {
        filename: relativePath,
        description: frontmatter.description || null,
        type: parseMemoryType(frontmatter.type),
        mtimeMs,  // 文件修改时间,用于后续的新旧度判断
      }
    }),
  )
  // 按修改时间倒序,最多 200 个
  return headers.sort((a, b) => b.mtimeMs - a.mtimeMs)
    .slice(0, 200)
}

注意它只读每个文件的前 30 行,足够提取文件开头那段元信息里的 name、description、type,但不会读取记忆的完整内容。这样即使有 200 个记忆文件,扫描开销也很小。

第二步:拼成清单,发给 Sonnet 做选择。

扫描完之后,所有记忆的「头部信息」被拼成一个文本清单:

- [feedback] feedback_no_mock.md (2026-03-28): 集成测试必须使用真实数据库
- [user] user_preferences.md (2026-03-25): 用户是后端工程师,偏好简洁回复
- [project] project_auth.md (2026-03-20): 认证模块重写由合规需求驱动

然后把这个清单连同用户当前的输入一起发给 Sonnet:

const result = await sideQuery({
  model: getDefaultSonnetModel(),
  system: '你是一个记忆选择器,从列表中选出最多 5 条与用户问题最相关的记忆...',
  messages: [{
    role: 'user',
    content: `用户问题: ${query}\n\n可用的记忆:\n${manifest}`,
  }],
  max_tokens: 256,  // 只需要返回文件名列表,非常短
})

Sonnet 返回的只是一个文件名列表(比如 ["feedback_no_mock.md", "project_auth.md"]),不是记忆内容本身。

第三步:加载选中记忆的完整内容,注入上下文。

拿到文件名列表后,系统才去读取这几条记忆的完整内容,作为 <system-reminder> 注入当前对话。

这里还有一个非常讲究的细节,记忆陈旧度检测。对于超过 1 天的记忆,系统会自动附加一段警告:

export function memoryFreshnessText(mtimeMs: number): string {
  const d = memoryAgeDays(mtimeMs)
  if (d <= 1) return '' // 今天或昨天的记忆不加警告
  return (
    `这条记忆已经有 ${d} 天了。` +
    `记忆是某个时间点的观察,不是实时状态——` +
    `其中关于代码行为或 file:line 引用的断言可能已经过时。` +
    `在当作事实引用之前,请先对照当前代码验证。`
  )
}

为什么需要这个?因为用户可能 30 天前存了一条记忆说「AuthService 在 src/auth.ts 第 42 行使用了 JWT」,但代码早就改了。如果模型盲目相信这条记忆,就会给出错误的建议。陈旧度警告提醒模型「这个信息可能过时了,先验证再引用」。

性能优化:并行预取

最后一个值得学习的设计:记忆召回的执行时机

Sonnet 侧查询不是在主模型需要时才触发的,而是在用户提交消息后立刻就开始了,和主模型的 API 调用并行执行:

// query.ts 中的调用——在进入主循环之前就启动记忆预取
using pendingMemoryPrefetch = startRelevantMemoryPrefetch(
  state.messages,
  state.toolUseContext,
)

时序大概是这样的:

一张系统流程图,展示了用户与多个组件交互的序列流程。从左到右有四个垂直生命线:用户、query()循环、Sonnet(记忆选择)、主模型API。图中包含实线箭头(同步调用)和虚线箭头(异步返回),标注了各步骤的操作内容,如‘提交消息’、‘启动记忆预取(后台)’、‘调用主模型API(前台)’、‘返回5条相关记忆(先完成)’、‘返回模型响应+工具调用’、‘将记忆注入下一轮的上下文’,并在query()循环处有一个自循环箭头表示迭代过程。

Sonnet 比 Opus 快得多(延迟通常只有几百毫秒),所以等主模型的响应回来时,记忆选择早就完成了。整个记忆召回过程几乎不增加任何额外延迟

还有一个小优化:如果用户当前正在使用某些工具(比如正在调用某个 MCP 工具),Sonnet 选择器会自动过滤掉该工具的使用文档类记忆,因为模型已经在用这个工具了,它的用法文档此刻是噪声,不是信号。

但「该工具的已知 bug 和注意事项」类记忆仍然会被选中,正在用的时候,恰恰是最需要知道坑在哪里的时候

小结

回顾一下 Claude Code 的记忆系统,它的核心设计哲学可以用三句话概括。

一张关于Claude Code记忆系统核心设计哲学的三栏式信息图,整体为手绘风格,背景米黄,边角有卷页效果。左栏标题‘1. 记该记的,不记能推导的’,含机器人形象、输入数据(用户偏好、项目背景等)、垃圾堆图标、四类型封闭集合+排除清单、关键目标/规则/状态变化、控制记忆膨胀等元素;中栏标题‘2. 存索引,按需加载详情’,展示System Prompt(常正书/MEMORY.md)→书架(多种偏录详情)→用到时加载→管理时和小的详细文件→System Prompt上下文,强调避免撑爆上下文;右栏标题‘3. 用小模型做秘书,大模型做决策’,含Sonnet秘书(快速预拉t多个内存文件并执行)、Opus决策(只做筛选的文件用于思考和行动)、陈旧度检测,底部标注‘零延迟、低成本、高可靠’。顶部有‘Claude Code 记忆系统核心设计哲学’标题及齿轮、对话气泡、机器人头像装饰。

  • 第一句是「记该记的,不记能推导的」。通过四类型封闭集合加上排除清单,把记忆控制在有价值的范围内,防止它膨胀成一个什么都往里塞的垃圾堆。

  • 第二句是「存索引,按需加载详情」。MEMORY.md 作为轻量索引始终常驻在 System Prompt 里,但每条记忆的具体内容是独立文件,用到的时候才加载。这样既让 Agent 知道有哪些记忆可用,又不会撑爆上下文。

  • 第三句是「用小模型做秘书,大模型做决策」。Sonnet 负责并行预取和选择记忆,Opus 只管做决策,加上陈旧度检测机制,实现了零延迟、低成本、高可靠。

六、上下文窗口管理

这可能是整个 Claude Code 里最复杂也最精妙的部分。

大模型有上下文窗口限制。即使是 200K Token 的窗口,一次复杂的编程任务(读了几十个文件、执行了几十条命令)很容易就塞满了。

一张大模型上下文限制与应对策略的科普插画。画面中央是卡通机器人形象,上方标题为‘大模型上下文限制’。左侧展示‘做法1:简单截断’——用剪刀剪掉旧消息(编号20、21、N等),标注‘保留最近的消息,旧消息扔掉’,下方指出致命缺点‘丢失关键信息,犯低级错误’;右侧为‘做法2:全量摘要’——对话文本输入压缩机,输出‘摘要’,标注‘昂贵:需API调用’‘信息损失’。顶部说明‘限制:200K Token 窗口塞满!’,并配图‘读文件’→‘执行命令’流程,强调复杂任务消耗大量Token。

业界常见的做法是「简单截断」,只保留最近的 N 条消息,旧的扔掉。但这对于编程 Agent 来说是灾难性的:你可能 20 轮前读过一个关键配置文件,现在要改代码时那个文件的信息已经被截掉了,Agent 就会犯低级错误。

另一种做法是「全量摘要」,把整段对话总结成一段摘要。但这很贵(摘要本身就是一次 API 调用),而且有信息损失。

压缩五步走

Claude Code 的核心理念是:压缩一定有信息损失,所以能不压就不压,必须压的时候从最轻的手段开始

它设计了五个从轻到重的压缩手段,就像医院的分诊制度一样:先试最温和的,不行再上猛药。在每次 API 调用前依次尝试:

一张流程图,背景为白色,包含多个紫色矩形框和菱形判断框,通过黑色箭头连接。顶部为‘即将调用 API’矩形框,向下指向一个菱形判断框‘上下文快满了?’,其两个分支分别标注‘快满了’(向右)和‘没满’(向左)。‘快满了’分支依次经过‘第1层:Tool Result Budget(超大工具结果存磁盘)’、‘第2层:Snip(移除远古消息)’、‘第3层:Micro-Compact(时间衰减裁剪工具结果)’、‘第4层:Context Collapse(读时投影,延迟压缩)’,再进入一个菱形判断框‘还是太大?’,其两个分支分别为‘可以了’(向下)和‘还是太大’(向右),后者指向‘第5层:Auto-Compact(全量摘要 + 压缩后恢复)’。所有路径最终汇聚至底部的‘直接调用 API’矩形框。

为什么要分五步,而不是一步到位做全量摘要?

因为每一步的「代价」是递增的

  • 第 1 层几乎没有信息损失,完整内容还在磁盘上,只是不在上下文里了。

  • 第 2、3 层有少量信息损失,丢掉了老的工具输出,但模型随时可以重新获取。

  • 第 4 层有中等信息损失,对话细节被分段压缩了。

  • 第 5 层信息损失最大,整段对话变成一段摘要。

所以 Claude Code 的策略是:先用代价最小的手段,实在不行再升级

大部分情况下,前三层就够用了,根本不需要触发昂贵的全量摘要。

接下来我们一层一层拆解。

第 1 步:大结果存磁盘

问题是什么?  想象一下,你让 Agent 读一个 10MB 的日志文件。Read 工具忠实地返回了全部内容,一下子就吃掉了几万 Token。更夸张的是,如果模型同时读了 3 个大文件,一条消息就可能占掉大半个上下文窗口。

一幅关于AI代理读取大文件的插画,中央是一个标有'上下文窗口'的圆柱形容器,上方有一个表情疲惫、带耳机的机器人,正被从左右两侧涌来的大量数据流包围。左侧是一叠标有'LOG'的文件,配有放大镜,标注'10MB 日志'和'&gt; 10MB 日志',并有箭头指向机器人,标注'>数万 Token'和'Read';右侧是多叠文件,标注'同时读3大文件'、'>数十万 Token'和'Read';底部从容器中延伸出警示标志,提示'占满大半!(>50%)',下方还有两个图标:红色禁止符号配文字'错误决策',以及下降折线图配文字'性能下降';画面中散布齿轮、星星等装饰元素,整体风格为手绘卡通,背景为米色。

Claude Code 怎么做?  它在工具结果进入消息列表之前,就先做一道「体检」:

async function maybePersistLargeToolResult(
  toolResultBlock: ToolResultBlockParam,
  toolName: string,
): Promise<ToolResultBlockParam> {
  const size = contentSize(content)
  // 单个工具结果超过阈值(默认约 50KB)?
  if (size <= threshold) {
    return toolResultBlock  // 没超,原样通过
  }
  // 超了!把完整内容存到磁盘文件
  const result = await persistToolResult(content, toolUseId)
  // 用一个 2KB 的预览替换原内容
  const preview = buildLargeToolResultMessage(result)
  return { ...toolResultBlock, content: preview }
}

它的逻辑很简单:如果单个工具的结果超过约 50KB,就把完整内容写到磁盘上,在消息里只留一个 2KB 的预览摘要。这样模型还是能看到文件的大概内容(前 2KB),但不会撑爆上下文。

一张关于Claude Code处理大型工具输出流程的中文信息图,顶部标题为‘Claude Code 处理大型工具输出’。图中展示从Bash、文件、文件夹、搜索、设置等输入源进入‘体检站’(一个带波形图的圆柱体),随后分两条路径:上方‘单个检测’路径判断‘单个工具结果 &gt; 约50KB?’,若满足则保留前2KB预览摘要并存入磁盘防止撑爆上下文;下方‘消息总量控制’路径判断‘同一消息总量 &gt; 200KB?’,若满足则将同一条消息所有工具结果总大小限制在200KB内,并挑出最大结果存磁盘,其余存入消息列表。底部‘精妙之处’区域说明完整内容未丢,在磁盘上,后续可调用Read工具读取特定行范围。

除了单个工具的限制,还有一个消息级的总量控制,同一条消息里所有工具结果的总大小不能超过 200KB。如果超了,系统会挑出最大的那几个结果存磁盘,直到总量降到限制以内。

这一层的精妙之处在于:完整内容并没有丢,它还在磁盘上。如果模型后面真的需要那个大文件的某个片段,它可以再次调用 Read 工具去读取特定的行范围。

第 2 步:砍掉远古消息

问题是什么?  一次长对话可能有上百轮。对话开头那几轮的内容,比如用户最初的探索性提问、模型早期的试探性回答,到了后面几乎完全没用了。但它们仍然占着宝贵的上下文空间。

一张手绘风格的科普插画,主题为‘长对话:被占用的宝贵上下文空间’。画面中央是一条卷轴状的‘上下文空间’,由多张堆叠的纸页组成,部分纸页布满蜘蛛网并标注‘陈旧前期内容后期几乎无用’,右侧有机器人指出‘后期这些没用了!’。左侧是用户与试探模型的互动流程,包含‘[初期内容]’、‘[初期探索]’、‘[试探回答]’等标签,箭头指向中间的‘上百轮对话’卷轴。下方有红色箭头标注‘[上百轮对话]’和‘[已满] 上下文空间告急’,右下角黄色爆炸框强调‘但仍占宝贵上下文空间’,旁边是表情焦虑的‘目前模型’机器人。

Claude Code 怎么做?  Snip 是最「粗暴」但也最高效的一层,直接把对话开头的一批老消息移除掉,然后插入一个边界标记告诉模型「这之前的内容已经被清理了」。

if (feature('HISTORY_SNIP')) {
  const snipResult = snipModule.snipCompactIfNeeded(messagesForQuery)
  messagesForQuery = snipResult.messages
  snipTokensFreed = snipResult.tokensFreed

  if (snipResult.boundaryMessage) {
    yield snipResult.boundaryMessage  // 插入边界标记
  }
}

它不做任何摘要,不总结「前面聊了什么」,直接砍掉。听起来很暴力,但对于那些确实已经完全过时的消息来说,这是代价最低的做法,因为它不需要额外调用大模型来生成摘要,零 API 开销。

一张关于Claude Code Snip功能的三步流程图,主题为'高效清理'。图中包含三个编号区域:1. 移除对话开头老消息,配有一个机器人用剪刀剪掉多个彩色对话气泡的插画,下方有标牌写着'边界标记: 之前内容已清理';2. 零API开销,代价最低,展示剪刀、带叉的API图标、钱袋和大模型文档,文字说明'不需摘要,不总纠结'、'需要调用大模型';3. 细节:传递Token数,描绘一个机器人在第5层Auto-Compact处理数据流,数据流中有文件、金币等元素,标注'传递释放的Token数(snippetTokensFreed)'、'空间足够,不压缩'、'Auto-Compact 不触发,避免无谓压缩'。

还有一个重要的细节:Snip 会把「我释放了多少 Token」这个数字(snipTokensFreed)传给后面的第 5 层 Auto-Compact。

为什么?因为 Auto-Compact 是根据「当前上下文占了多少 Token」来决定是否触发的。如果 Snip 已经释放了足够的空间,Auto-Compact 就不需要触发了,避免两层同时做无谓的压缩

第 3 步:裁剪老的工具输出

问题是什么?  经过前两层之后,上下文里剩下的都是「不太老但也不太新」的消息。

这些消息不能直接砍掉(可能还有用),但里面大量的工具输出其实已经过时了,比如 30 分钟前读的一个文件,现在那个文件可能已经被改过了。

一张关于消息处理策略的插画式信息图,主题为‘处理『不太老也不太新』的消息’。画面中央是一条展开的卷轴,左侧标注‘较旧的消息’,包含多个对话气泡和文件图标,下方有红色剪刀图标(标注‘不能直接砍掉’)及绿色对勾(标注‘保留消息’),并注明‘有用’;中间区域为‘『不太老也不太新』的消息’,以时间线形式展示消息状态变化:30分钟前‘文件 (Read)’→现在‘可能已修改’,并配有‘Read 文件’标签与计时器图标;右侧标注‘最新’,含蓝色、黄色对话气泡、带对勾的文件图标,下方标有‘重要’;右下角有一个机器人形象,手指向上指;画面中还出现‘大量工具输出过时’的红色印章字样;顶部横幅标题为‘处理『不太老也不太新』的消息’,周围点缀齿轮、灯泡、星星等小图标。

Claude Code 怎么做?  Micro-Compact 的核心思想是时间衰减:越老的工具结果越不重要,可以被裁剪。但是,不是所有工具的结果都能裁剪:

const COMPACTABLE_TOOLS = new Set([
  FILE_READ_TOOL_NAME,    // 读文件 → 可以重新读
  ...SHELL_TOOL_NAMES,    // 执行命令 → 可以重新执行
  GREP_TOOL_NAME,         // 搜索 → 可以重新搜
  GLOB_TOOL_NAME,         // 查找文件 → 可以重新查
  WEB_SEARCH_TOOL_NAME,   // 搜索网页 → 可以重新搜
  FILE_EDIT_TOOL_NAME,    // 编辑文件 → 结果可裁剪
  FILE_WRITE_TOOL_NAME,   // 写文件 → 结果可裁剪
])

看到规律了吗?可以被裁剪的,都是「可重新获取」的工具,Read 的结果可以再读一次,Bash 的输出可以再执行一次,搜索结果可以再搜一次。

但 AgentTool(子 Agent 的输出)、TaskTool(任务状态)这类工具的结果永远不会被裁剪,因为子 Agent 的推理过程是不可重复的,砍掉就真的丢了。

一张关于Claude Code微压缩技术的科普信息图,主题为‘基于时间衰减的裁剪’。图中包含多个模块:顶部黄色标题栏;左上角时钟图标与‘时间衰减’曲线图;中间分三块区域——绿色‘可裁剪:可重新获取工具’(含Read、Bash、搜索图标及说明)、红色‘不可裁剪:不可重复工具’(含AgentTool、TaskTool机器人图标及说明)、蓝色‘模型自主决定’(含机器人思考气泡与重读/重执行流程);下方展示‘老消息(Tailorable)’裁剪前后列表对比,标注‘保留最近N个’‘清理其余的’‘替换成标记’等操作;整体采用手绘风格,配色鲜明,有齿轮、星星、灯泡等装饰元素。

具体裁剪逻辑是「保留最近 N 个,清理其余的」:

// 收集所有可裁剪工具的结果 ID
const compactableIds = collectCompactableToolIds(messages)
// 保留最近 5 个,其余全部清理
const keepRecent = Math.max(1, config.keepRecent)  // 至少保留 1 个
const keepSet = new Set(compactableIds.slice(-keepRecent))
const clearSet = compactableIds.filter(id => !keepSet.has(id))

被裁剪的工具结果会被替换成一个标记:

export const TIME_BASED_MC_CLEARED_MESSAGE =
  '[Old tool result content cleared]'

这样模型看到这个标记就知道「这里原来有内容但被清理了」。

如果它后面还需要这些信息,它可以自己决定重新读文件或重新执行命令。

为什么叫「时间衰减」?
因为它的触发条件跟时间有关,当距离上一次 API 调用超过一定时间(默认约 60 分钟),说明大模型 API 端的 Prompt Cache 大概率已经过期了。
既然缓存已经没了,那清理旧的工具结果也不会浪费之前的缓存投入。

第 4 步:读时投影

问题是什么?  经过前三层后,如果上下文还是太大,下一步就得做全量摘要了。

但全量摘要代价很高(要额外调一次 API),而且会把整段对话的细节全部丢掉。有没有一个「中间态」,比全量摘要轻,但比 Micro-Compact 重?

一张关于智能管理上下文的科普信息图,主题为‘智能管理上下文:告别臃肿,保留细节’。图中包含多个卡通机器人角色、图标和流程说明。左上角机器人手持剪刀处理文档,标注‘写时压缩(轻)’,含‘Snip’‘移除/替换(Snip)’等文字;中间黄色框标出‘中间态:更优 Context Collapse’;右侧机器人将文件倒入漏斗,标注‘全量摘要(重)’,强调‘高代价,丢细节’;中部绿色框为‘核心:读时投影’,展示‘压缩视图’‘API 调用’‘保留细节’等流程;底部横条形图示‘上下文窗口’,从0%到100%,分段标注‘老哨息压缩’‘预留缓冲区’‘留足 API 响应空间’,并有安全性和阈值提示(90%阈值:主动压缩;95%阈值:紧急压缩),右端红色区域标‘上下文已满!禁止写入’。

Claude Code 怎么做?  Context Collapse 引入了一个非常巧妙的概念,读时投影(Read-Time Projection)

什么意思呢?前面三层都是「写时压缩」,直接修改消息列表,把内容替换掉或删掉。但 Context Collapse 不修改原始消息,它只在调用 API 的那一刻,动态计算一个「压缩视图」给模型看。

// 这是 query.ts 中的调用
// 注意:这是一个"读时投影"——不修改 REPL 的完整历史,
// 只在发送给 API 时计算压缩视图
if (feature('CONTEXT_COLLAPSE') && contextCollapse) {
  const collapseResult = await contextCollapse.applyCollapsesIfNeeded(
    messagesForQuery,
    toolUseContext,
    querySource,
  )
  messagesForQuery = collapseResult.messages
}

它的触发有两级阈值:

  • 90% 上下文窗口:主动开始分段压缩旧消息(预留缓冲区)  
  • 95% 上下文窗口:紧急压缩更多内容(留足 API 响应空间)

这个设计最精妙的地方是它和第 5 层的配合:Context Collapse 运行在 Auto-Compact 之前

如果 Context Collapse 已经通过「读时投影」把上下文压到了阈值以下,Auto-Compact 就完全不需要触发了。这样模型保留了更多的细节上下文,而不是被一段粗糙的全量摘要替代。

第 5 步:全量摘要

问题是什么?  当前面四层都不够用,上下文实在太大了,必须做一次彻底的压缩。这是代价最高但效果最强的一层。

什么时候触发?  Claude Code 用一个公式计算触发阈值:

function getAutoCompactThreshold(model: string): number {
  const effectiveContextWindow = getEffectiveContextWindowSize(model)
  // 有效窗口 - 13K 缓冲区 = 触发阈值
  return effectiveContextWindow - 13_000
}

以 200K Token 的模型为例:有效窗口大约 180K(预留 20K 给输出),减去 13K 缓冲区,当上下文达到 167K Token 时触发

一张关于Claude Code全量摘要触发计算的科普插画,整体风格为卡通手绘风。顶部横幅标题为'Claude Code 全量摘要触发计算'。左侧是一个标有'MEMORY'的玻璃瓶,内含蓝色液体和多个带笑脸的金币状Token,瓶身标注'200K Token 模型案例';瓶内从上至下分为三个区域:总窗口(200K Token)、预留输出(20 Token)、有效窗口(180 Token),并配有机器人小助手指向各部分。右侧是一个木制卷轴板,标题为'公式与计算',展示计算过程:有效窗口(13K缓冲区)减去触发阈值,得出180K - 13K = 167K,下方有闪电图标强调计算结果;底部右下角显示当上下文达到167K时触发全量摘要,配有一个带警报灯的机器人和一个显示'167K'的数字屏,旁边还有另一个机器人竖起大拇指表示确认。

触发后做了什么? 三步走:

第一步:生成摘要。调用大模型,把整段对话总结成一段结构化摘要。这个摘要不是随便写的,Claude Code 用一个精心设计的 Prompt 要求模型按多个维度来总结:用户的主要请求和意图、关键技术概念、涉及的文件和代码片段、遇到的错误和修复方案、问题解决过程、用户的所有消息(不能遗漏任何一条)、待完成的任务、当前工作状态、建议的下一步。

一张关于Claude Code结构化摘要生成流程的插画式信息图。顶部中央为标题‘Claude Code 结构化摘要生成’,横幅样式。左侧是大量彩色对话气泡堆叠,标注‘对话内容(对话 Token 堆积)’,一个带放大镜和齿轮的机器人形象位于下方,标注‘Claude Code’;中间区域显示‘调用 LLM’与‘大模型(LLM)’,有卷轴箭头指向服务器机柜上的大脑图标,下方标注‘精心设计 Prompt’;右侧为‘生成结构化摘要(多个维度)’模块,包含六个小图标及说明:用户请求 & 意图、关键技术概念、涉及文件 & 代码片段、遇到错误 & 修复方案、问题解决过程;底部横向展开一卷轴,含四个图标及说明:所有用户消息(全)、待完成任务、当前工作状态、建议下一步;画面中还有多个小机器人、星星、图表等装饰元素,整体风格为卡通手绘风,色彩明亮。

为什么要这么细?因为压缩后模型要靠这段摘要来「恢复记忆」。如果摘要漏掉了关键信息(比如「用户还有一个待完成的任务」),模型就会忘记这件事。

第二步:替换旧消息。把压缩边界之前的所有消息删掉,替换为刚才生成的摘要。同时插入一条边界标记消息,记录压缩前的 Token 数,方便后续追踪。

一张手绘风格的流程图,主题为‘替换旧消息与摘要替换流程’,顶部有卷轴标题。画面左侧展示多个破碎、缠绕蜘蛛网的对话气泡,象征旧消息;中间偏左一个机器人手持剪刀,旁有‘删掉所有旧消息’和‘压缩边界之前’字样,下方箭头指向一堆被丢弃的纸片,标注‘丢弃’;中央有两个卷轴,分别写着‘生成的摘要 替换旧消息’,由不同机器人操作;右侧区域有三个机器人:上方机器人举着卷轴;中间机器人记录‘边界标记消息 Token 数’并附图表;右下角机器人持放大镜观察数据图表;画面散布齿轮、灯泡、云朵等装饰元素,整体色调柔和,以米黄为底色,呈现AI处理消息与摘要的流程示意。

第三步:Post-Compact Restoration(压缩后恢复)。这是整个流程中最关键的一步,压缩完不是就完了,还要主动恢复最重要的上下文

export const POST_COMPACT_MAX_FILES_TO_RESTORE = 5
export const POST_COMPACT_TOKEN_BUDGET = 50_000
export const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000
export const POST_COMPACT_SKILLS_TOKEN_BUDGET = 25_000

系统会从文件状态缓存(fileStateCache)中找出最近访问过的文件,按最后访问时间排序,挑选最多 5 个、总共不超过 50K Token 的文件内容重新注入。同时恢复活跃的 Skill(不超过 25K Token),如果有进行中的 Plan 也会恢复 Plan 文件。

一张关于‘压缩后恢复:关键上下文重建’的流程图,背景为米色纸张风格。左侧是一个带绿色对勾的机器人图标,标注‘1. 压缩结束’,从其分出三条路径:上方指向‘文件状态缓存 (fileStateCache)’(含宝箱、齿轮等图标),中间指向‘活跃技能’(含代码、齿轮、扳手图标),下方指向‘进行中的计划’(含蓝图、进度条、检查标记)。中间区域展示恢复流程:‘挑选最近访问’(四个带时钟和闪电图标的卡片)→‘最多5个文件 总Token ≤ 50K’→‘重新注入’(漏斗中放入文档、笔、齿轮等元素)→‘模型上下文’(大脑图形)→右侧机器人形象,头部显示脑内多个图标(如电池、文档、齿轮),标注‘关键上下文已恢复 工作继续’;同时在漏斗上方有‘重新注入’字样,右上角另标‘关键上下文已恢复’。

为什么要做恢复?因为压缩后模型「失忆」了,它不记得刚才读过的文件内容了。

如果不恢复,模型的第一反应就是「让我重新读一下文件」,白白浪费一轮工具调用。主动恢复最近的文件内容,可以让模型无缝继续工作,体验上几乎感觉不到压缩发生过。

还有一个兜底机制:如果全量摘要连续失败 3 次(比如 API 超时),系统会自动放弃,不会无限重试,这就是熔断器 模式,防止一个失败的压缩操作拖垮整个 Agent。

小结

回顾一下这五步压缩策略,它们体现了一个核心设计哲学:能轻则轻,逐步加码

层级 手段 信息损失 API 开销 触发条件
第 1 层 大结果存磁盘 几乎为零 工具结果超 50KB
第 2 层 砍掉远古消息 消息过时
第 3 层 清理老工具输出 中低 缓存过期/数量超限
第 4 层 读时投影压缩 上下文达 90%
第 5 层 全量摘要 高(一次 API 调用) 上下文达 ~93%

越往下代价越高,但效果也越强。

大部分场景下前三层就足够了,它们完全不需要额外的 API 调用,只是「搬运」和「裁剪」数据。只有在极端情况下,才需要触发昂贵的全量摘要。

这种设计的另一个好处是各层相互协调

第 2 层 Snip 会告诉第 5 层「我已经释放了多少 Token」,避免重复压缩。第 4 层 Context Collapse 在第 5 层之前运行,如果它够用了,第 5 层就不触发。

每一层都在为下一层「减负」。

写在最后

说真的,啃完这 51 万行源码,我整个人是有点懵的。

Claude Code 源码里有太多优秀的 Agent 技术落地方案。

比如压缩上下文要分五步走、记忆要分四种类型存、System Prompt 设计……

每一件事单拿出来都算不上什么黑科技,但全串在一起,就是一套能把一匹野马驯成耕牛的缰绳系统。

这给我一个很大的启发:做 Agent,别老盯着模型发呆。模型是发动机,但一辆车能不能安全上路,靠的是刹车、方向盘、安全带。这些「不起眼」的东西,才是真正决定成败的。

最后调侃一句,Claude Code 源码泄漏这波操作,无疑将极大地缩短国内外 AI Agent 的信息差,全面利好国产 Agent 的爆发式发展!

好了,今天就聊到这。

一张聊天界面截图,左侧为蓝色圆形头像(内有黄色猫形图案及粉色文字'xuolin'),其右侧白色气泡中显示文字'clau de code 源码下载:'及一个链接'https://pan.quark.cn/s/50a248f9542',右侧绿色气泡中显示'cc'字样及一张蓝色调人物背影小图(人物穿浅色上衣,背景为蓝调风景)。




上一篇:24 万 Star AI Agent OpenClaw 国内接入方案与模型成本控制指南
下一篇:电感下方铺铜:基于三类电感特性与实测的PCB设计指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-7 15:21 , Processed in 0.601400 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表