Claude Code 源码架构深度解析(二):Claude Code 最核心的 1729 行:一个 Agent Runtime 是怎么运转的

news2026/4/4 8:24:33
一个请求进来到底发生了什么上一篇我们建立了一个认知Claude Code 不是 CLI 工具而是 Agent Operating System。但知道它是什么还不够。这一篇我们要打开它的引擎盖看看里面到底怎么转的。当你在 Claude Code 里输入一句帮我重构这个函数从你按下回车到模型输出结果中间到底经历了什么答案不是把你说的话拼到 prompt 里调一下 API把结果打出来。真实的链路是这样的cli.tsx经过 fast-path 分发加载main.tsxmain.tsx初始化状态、注册工具、构造 ToolUseContext你的输入进入query()函数query()调用queryLoop()这是一个while(true)主循环每次循环迭代压缩上下文 → 组装 system prompt → 调用模型 API → 流式处理响应 → 执行工具调用 → 注入附加信息 → 决定是否下一轮一个看起来简单的对话交互背后跑着一台精密的状态机。一、query.ts1729 行的状态机为什么不用递归query.ts是整个 Claude Code 的心脏。理解了它就理解了 Agent Runtime 的核心运行逻辑。为什么是状态机早期版本的 query 用的是递归模型返回了工具调用 → 执行工具 → 把结果拼回消息列表 → 再次调用 query()。这在短会话里没问题但在长会话里递归会爆栈。一个复杂的编码任务可能需要几十轮甚至上百轮的模型调用。每次递归都会压一层调用栈。在 Node.js 默认的栈大小下这是一个真实存在的问题。所以现在改成了while(true) state 对象的设计。先看query.ts里的状态定义和主循环入口// src/query.ts// 跨迭代携带的可变状态typeState{messages:Message[]toolUseContext:ToolUseContext autoCompactTracking:AutoCompactTrackingState|undefinedmaxOutputTokensRecoveryCount:numberhasAttemptedReactiveCompact:booleanmaxOutputTokensOverride:number|undefinedpendingToolUseSummary:PromiseToolUseSummaryMessage|null|undefinedstopHookActive:boolean|undefinedturnCount:number// Why the previous iteration continued. Undefined on first iteration.transition:Continue|undefined}asyncfunction*queryLoop(params:QueryParams,consumedCommandUuids:string[]){// Mutable cross-iteration state.letstate:State{messages:params.messages,toolUseContext:params.toolUseContext,maxOutputTokensOverride:params.maxOutputTokensOverride,autoCompactTracking:undefined,stopHookActive:undefined,maxOutputTokensRecoveryCount:0,hasAttemptedReactiveCompact:false,turnCount:1,pendingToolUseSummary:undefined,transition:undefined,}// eslint-disable-next-line no-constant-conditionwhile(true){let{toolUseContext}stateconst{messages,turnCount,hasAttemptedReactiveCompact,...}state// ... 每一轮循环的处理逻辑}}query.ts是一个 async generator内部是死循环通过 state 对象在迭代之间传递状态。每次continue就是一个 state transition。代码里有9 个不同的 continue 点每个对应一种为什么要再跑一轮的原因Continue 原因触发场景next_turn正常的工具调用后继续reactive_compactAPI 返回 413 后紧急压缩重试max_output_tokens模型输出超限注入恢复消息继续stop_hookstop hook 要求模型继续token_budgettoken 预算未用完继续工作context_collapse_drain上下文折叠后重试fallback_model模型降级后重试……每轮循环做什么每次循环迭代的执行步骤按顺序第一步上下文预处理四道压缩机制依次执行snip compact → micro compact → context collapse → auto compact。目的是在有限的上下文窗口里塞进最有用的信息。这部分后面第五篇会详细讲。第二步Token 预算检查如果 auto compact 被关了检查是否接近硬限制。第三步组装 system prompt把静态区和动态区的内容拼接成完整的 system prompt。后面会详细讲这个组装过程。第四步调用模型 API把消息列表、system prompt、工具定义一起发给模型。第四步流式处理响应模型的输出是流式的。关键在于如果流中出现了 tool_use block不等模型说完就开始执行工具。这就是 Streaming Tool Execution下面会详细讲。第五步错误恢复这一步的分支非常多prompt 太长先试 context collapse drain再试 reactive compact输出 token 超限注入恢复消息让模型继续模型降级切 fallback model第六步Stop hooks模型停止输出后运行 stop hooks决定要不要让模型继续。第七步工具执行批量执行本轮所有工具调用。第八步附件注入工具执行完后注入 memory attachments、skill discovery 结果、排队中的命令。第九步决定下一轮把结果组装成新的消息列表回到循环开头。我的理解很多 Agent 框架包括 LangChain、AutoGen 的早期版本用的还是递归或者简单的 for 循环。在 demo 场景下无所谓但一旦要支持长时间运行的复杂任务你就必须面对状态管理问题。while(true) state 对象的好处是不会爆栈每个 continue 点都有明确语义方便调试状态可序列化理论上可以做断点续传错误恢复逻辑可以在循环内自然实现不需要在递归层之间传递异常这不是什么高深的技术就是工程成熟度的体现。不过话说回来很多成熟的工程方案事后看都不难难的是在一开始就意识到需要这样设计。二、Streaming Tool Execution边收边跑传统做法等模型完整输出 → 收齐所有 tool_use block → 批量执行工具。Claude Code 做了一个明显的优化StreamingToolExecutor。模型还在输出的时候已经完成的 tool_use block 就开始执行了。看看StreamingToolExecutor的核心并发控制逻辑src/services/tools/StreamingToolExecutor.ts/** * Executes tools as they stream in with concurrency control. * - Concurrent-safe tools can execute in parallel with other concurrent-safe tools * - Non-concurrent tools must execute alone (exclusive access) * - Results are buffered and emitted in the order tools were received */exportclassStreamingToolExecutor{privatetools:TrackedTool[][]privatehasErroredfalseprivatesiblingAbortController:AbortController/** * Check if a tool can execute based on current concurrency state */privatecanExecuteTool(isConcurrencySafe:boolean):boolean{constexecutingToolsthis.tools.filter(tt.statusexecuting)return(executingTools.length0||(isConcurrencySafeexecutingTools.every(tt.isConcurrencySafe)))}// ...}注意canExecuteTool的判断逻辑只有当所有正在执行的工具都是并发安全的并且新工具也是并发安全的才允许并行执行。否则就排队等。这个判断依赖的就是前面Tool.ts里的isConcurrencySafe标记。为什么这很重要想象一个场景模型在一次响应里决定调用 5 个工具——读 3 个文件、搜索一下代码、看一下 git log。传统做法等模型生成完所有 5 个 tool_use可能需要 5-30 秒然后串行或并行执行这 5 个工具Streaming 做法模型生成完第 1 个 tool_use → 立刻执行读文件模型还在生成第 2 个 tool_use → 第 1 个文件已经读完了模型生成完第 3 个 tool_use → 前 2 个工具可能都跑完了…总体延迟可以减少一半以上。这个优化看起来简单实现起来不容易。你需要流式解析模型输出来判断一个 tool_use block 是否已经完整需要管理并发执行的工具的生命周期需要处理工具执行出错时的回滚还要确保结果按正确顺序拼回消息列表。很多团队做 Agent 的时候精力全花在让模型更聪明上。但用户感知到的快很多时候不是模型推理快而是工程层面的并发优化做得好。三、Prompt 组装不是一段文本是一台拼装机器看到很多人讨论 prompt engineering讨论的还是措辞层面用什么语气、加什么 few-shot 例子、怎么写 CoT。Claude Code 的 prompt 工程已经远远超出了措辞范畴。它是一套系统化的组装流程。静态区与动态区prompts.ts里的getSystemPrompt()返回的是一个字符串数组每个元素对应一个 section。整个 prompt 分成两大块。直接看组装代码src/constants/prompts.ts// Boundary 定义/** * WARNING: Do not remove or reorder this marker without updating cache logic in: * - src/utils/api.ts (splitSysPromptPrefix) * - src/services/api/claude.ts (buildSystemPromptBlocks) */exportconstSYSTEM_PROMPT_DYNAMIC_BOUNDARY__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__// getSystemPrompt() 的返回值return[// --- Static content (cacheable) ---getSimpleIntroSection(outputStyleConfig),getSimpleSystemSection(),getSimpleDoingTasksSection(),getActionsSection(),getUsingYourToolsSection(enabledTools),getSimpleToneAndStyleSection(),getOutputEfficiencySection(),// BOUNDARY MARKER - DO NOT MOVE OR REMOVE ...(shouldUseGlobalCacheScope()?[SYSTEM_PROMPT_DYNAMIC_BOUNDARY]:[]),// --- Dynamic content (registry-managed) ---...resolvedDynamicSections,].filter(ss!null)结构一目了然——boundary 上面全是固定内容下面全是动态内容。注释写着DO NOT MOVE OR REMOVE因为移了就破坏缓存。静态部分不随会话变化可缓存Section作用getSimpleIntroSection身份定位“你是Claude”getSimpleSystemSection系统运行规范getSimpleDoingTasksSection做任务的行为规范getActionsSection风险动作规范getUsingYourToolsSection工具使用语法getSimpleToneAndStyleSection语气风格getOutputEfficiencySection输出效率动态部分随会话状态变化Session guidance当前启用了哪些工具MemoryCLAUDE.md 内容环境信息OS、shell、cwd、模型名称语言偏好、输出风格MCP server instructionsToken budget 说明…中间用一个SYSTEM_PROMPT_DYNAMIC_BOUNDARY标记隔开。为什么要分成两块这不是代码组织美学这是真金白银的成本优化。Anthropic 的 API 支持 system prompt 的前缀缓存。如果两次 API 请求的 system prompt 前缀完全一样字节级一致第二次请求可以跳过对前缀部分的处理节省计算成本。所以把不变的内容放前面静态区→ 缓存命中率高把会变的内容放后面动态区→ 不影响前缀匹配源码注释里写得很直白不要随意修改 boundary 之前的内容否则会破坏缓存。Section Registry不是每次都重算动态区也不是每次都从头计算。systemPromptSections.ts里有一个section registry用systemPromptSection()创建的 section 会被缓存直到/clear或/compact只有用DANGEROUS_uncachedSystemPromptSection()创建的才会每次重算什么东西需要每次重算MCP instructions。因为 MCP server 可能在两个 turn 之间连接或断开它的状态是真正动态的。源码里的用法src/constants/prompts.tsDANGEROUS_uncachedSystemPromptSection(mcp_instructions,()isMcpInstructionsDeltaEnabled()?null:getMcpInstructionsSection(mcpClients),MCP servers connect/disconnect between turns,// 说明为什么不能缓存),这个函数名里带DANGEROUS_前缀暗示开发者你确定需要每次重算吗如果不确定就用缓存版本。说到这里我想坦诚一点我刚看到SYSTEM_PROMPT_DYNAMIC_BOUNDARY这个设计的时候第一反应是至于吗就为了省点缓存。但仔细算一笔账就明白了——假设一个 system prompt 有 3000 tokens缓存命中省的是每次请求对这 3000 tokens 的处理成本。日调百万次这就不是小数字了。Prompt 工程走到后期拼的真不是文案。它拼的是分层哪些静态哪些动态、缓存怎么最大化命中率、组装怎么根据状态精确拼出这一轮的 prompt、预算每个 section 占多少 token。Prompt 已经从写作变成了工程。四、行为约束怎么让 AI 工程师不乱来getSimpleDoingTasksSection()这个函数可能是整个 prompt 里最有价值的部分。它做的事情就一件告诉模型什么该做、什么不该做。我把源码里的规则归纳成三类你会发现它们指向同一个核心目标——克制克制好心办坏事的冲动用户让你改一行你就改一行。不要顺便加个你觉得应该有的功能不要看到代码不够优雅就忍不住重构不要觉得没有错误处理不专业就到处加 try-catch。三行重复代码在很多场景下比一个提前抽象出来的 helper 更好维护。克制偷懒走捷径的倾向改代码之前必须先读代码不能凭记忆或猜测直接动手。执行失败了要分析原因不能换个姿势盲目重试但也不能试一次就宣布放弃。不要给出时间估计——因为你估不准而用户会当真。克制包装结果的本能跑没跑测试、改没改成功、有没有验证过如实说。模型有一种天然倾向是把事情说得比实际更好这条规则就是在对抗这种倾向。为什么需要这些规则用过任何一个 coding agent 的人应该都遇到过这些问题你让它改个 bug它顺手重构了半个文件你让它加一个功能它加了三层抽象和五个你没要求的错误处理你让它读一个文件它说我已经检查过了看起来没问题——但它根本没跑你让它改一行代码它顺手给所有函数都加了 docstring这些问题的根源不是模型不够聪明。恰恰相反是模型太聪明了——它知道好的代码应该有错误处理、“好的代码应该有文档”、“好的代码应该抽象”所以它会主动去做这些事情。但在帮用户完成一个具体任务的场景下这种好心反而是干扰。Claude Code 的做法写成制度Claude Code 的解决方案不是希望模型自觉而是把行为规范写成制度。这些规则不是建议而是指令。写在 system prompt 里每次调用都会发给模型。这个设计理念可以用一句话总结不要指望一个 LLM 每次都想到该怎么做。制度化的行为比临场发挥稳定得多。从 coding standards 到 prompt standards这让我联想到软件工程里的 coding standards。没有人指望每个程序员都自觉遵守代码规范。你需要写成文档ESLint config、在 CI 里强制执行、违反了就报错。Claude Code 对模型的管理思路也是一样的写成 prompt 规则、在运行时强制执行工具治理 pipeline、违反了有兜底权限系统、Hook 系统。管理一个 AI Agent本质上和管理一个工程团队没有那么大区别——区别在于人可以通过文化来约束模型只能通过制度来约束。五、整体架构图一个请求的完整旅程把上面所有内容串起来一个请求在 Claude Code 内部的完整旅程是用户输入 ↓ cli.tsx (fast-path 分发) ↓ main.tsx (初始化状态、注册工具) ↓ query() → queryLoop() [while(true)] ↓ ┌─────────────────────────────────────────────┐ │ 1. 四道上下文压缩 │ │ 2. Token 预算检查 │ │ 3. 组装 system prompt (静态区 动态区) │ │ 4. 调用模型 API │ │ 5. 流式处理响应 Streaming Tool Execution │ │ 6. 错误恢复 (413/超限/降级) │ │ 7. Stop hooks 检查 │ │ 8. 批量工具执行 │ │ 9. 附件注入 (memory/skill/commands) │ │ 10. 决定是否 continue → 回到第 1 步 │ └─────────────────────────────────────────────┘ ↓ (模型输出 end_turn无工具调用) 输出结果给用户每一轮循环都可能因为 9 种不同的原因继续下一轮。每一轮都有四道压缩保护上下文不溢出。每一次模型调用都有 prompt 缓存优化控制成本。这就是一个 Agent Runtime 的引擎。这篇讲了很多技术细节最后收三个我觉得最值得记住的点Runtime 稳定性决定了 Agent 上限。很多 Agent 产品失败不是模型不够强是 runtime 不够稳定——长会话爆栈、上下文溢出没兜底、工具执行超时没处理。9 种 continue reason 和多层错误恢复就是在解决这类问题。Streaming 不只是体验优化。它改变了工具执行的调度模型从批量串行变成流式并发带动了工具并发安全标记、消息乱序组装、部分失败处理一整条链路的升级。Prompt 缓存命中率值得认真对待。百万级请求量下命中率每提升 1% 都意味着可观的成本节省。这不是锦上添花是决定商业模型能不能跑通的因素之一。下一篇预告主循环解决了怎么跑的问题。但模型跑起来之后要动手干活——调用工具读文件、写代码、执行命令。问题来了模型说要调用一个工具就真的直接调吗不是。在 Claude Code 里从模型说我要调用 BashTool到 BashTool 真正执行中间有一条14 步的治理流水线。输入校验、权限检查、风险预判、Hook 策略、用户交互——每一步都有可能拦住这次调用。42 个工具1745 行的执行逻辑。这不是给模型暴露几个函数那么简单。下一篇我们聊工具系统。Claude Code 源码架构深度解析三工具多不等于 Agent 强Claude Code 是怎么治理 42 个工具的本系列共 5 篇源码来自 Anthropic 泄露的 npm 包中的 source map 还原。内容为个人理解与工程分析不代表 Anthropic 官方观点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2481614.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…