Claude Code源码解析:Agent Harness核心机制与工具调用链路

Claude Code源码解析:Agent Harness核心机制与工具调用链路 Claude Code 这类工具真正值得研究的地方不在“模型有多强”而在于它把模型、工具、上下文、权限和日志组织成了一个可以稳定运行的“外壳”。这个外壳在源码语境里通常叫 Agent Harness也就是智能体的执行框架。我一开始读这个项目时下意识去找模型请求那部分代码后来才发现决定一个 Agent 能不能在实际目录里干活、能不能稳定处理批量任务的恰恰是 Harness 这层。这篇文章围绕 Claude Code 源码把 Agent Harness 从入口到工具调用链路拆开讲一遍适合两类人一类是已经在用 Claude Code想给它加自定义工具、调整行为另一类是没深入过 Agent 框架想借一个真实项目理解 Agent 到底是什么。1. 先搞清楚 Agent Harness 到底管哪些事1.1 Harness 不是模型也不是提示词很多人一提到 Agent第一反应是“让模型自己决定怎么办”。这句话没问题但光有模型远远不够。模型只能接收文本、返回文本它没法直接读你电脑上的文件也没法执行命令。真正的执行者其实是“外壳”也就是 Agent Harness。Harness 这个词原意是马具、挽具或者某种控制装置。放在 Agent 场景里可以理解成“把模型装进一个能干活的外壳”。这个外壳至少要处理六件事模型接入连接哪个模型服务、用什么鉴权、请求参数怎么组装。会话上下文多轮对话里历史消息、系统提示、工具返回结果怎么组织。工具注册与执行哪些指令可以被调用参数怎么校验执行结果怎么回传。权限控制哪些命令允许跑、哪些目录允许读写、哪些操作需要用户确认。输入输出交互命令行界面、非交互模式、日志输出。状态恢复任务中断后下次启动能不能接着跑。Claude Code 作为一款命令行 Agent 工具上面这些能力不是散落在零散函数里的而是被一个相对完整的执行框架承担下来。这个执行框架就是标题里说的 Agent Harness。1.2 裸 API 调用和 Harness 的差距在哪为了说明 Harness 到底增加了什么可以对比一下两种场景。如果只是调用模型 API只需要一个函数输入文本得到文本。但真实编程场景里模型输出往往不是最终答案而是一个“行动意图”。比如它想查看文件列表它会输出一个类似 tool_use 的结构里面包含工具名称和参数。这时候如果没有 Harness你还要手动做这些事解析模型输出里的工具调用。判断这个工具是否合法。执行本地命令或函数。把执行结果拼回消息历史。再次调用模型让模型基于工具结果生成下一步。这一整套循环才是 Agent 的核心。Claude Code 的源码里最值得读的也正是这条循环。读懂了它你就理解了 Agent 不是“模型会思考”而是“模型被包装在一个能反复调用工具、能回填结果、能控制风险的系统里”。1.3 为什么从源码层面理解这件事更重要现在网上关于 Claude Code 的使用教程很多安装、配置、使用、接入第三方模型都有。但绝大多数教程停留在“怎么用”。如果你只是想当一个终端用户读源码不是必须的。但如果你想做这几件事源码就变得重要想新增一个 Claude Code 不认识的自定义工具。想搞清楚某个工具调用失败是权限问题、参数问题还是模型问题。想在自己的项目里实现一个类似 Agent Harness 的轻量框架。想在离线或受限环境里部署需要提前知道依赖和配置边界。读源码不是为了背代码而是为了获得一种判断力当 Agent 行为不符合预期时你能定位到具体是哪一层出了问题。2. 读源码别从头啃先用一条任务定位入口2.1 准备环境时重点看哪些信息在开始读之前先补一句Claude Code 通常以 Node 包形式分发源码主体是 TypeScript。不同版本的结构可能有差异所以不要死记任何一个函数名或文件路径重点是建立自己的定位方法。我建议准备这些条件一个 Node.js 环境版本以项目 README 要求为准。一个趁手的编辑器VSCode 就够用。能访问代码仓库和 npm 包的普通开发环境。拿到源码之后不要急着按文件顺序读。先打开仓库根目录看 package.json 或 tsconfig.json 这类文件确认入口脚本、依赖关系和构建命令。然后全局搜这几个关键词tool工具定义都在哪些文件里。permission权限校验逻辑在哪。session会话和状态管理在哪。model / api模型接入封装在哪。这套搜索动作比从头看到尾高效得多。2.2 用一条具体任务定位主流程如果不知道从哪里开始我一般会找一个最小的真实任务然后沿着它的调用过程去读。比如用户输入这样一句话“帮我列出当前目录下的所有文件并按名称排序。”这句话执行时在 Harness 内部大致会走一遍这样的流程命令行入口解析参数创建新的会话或恢复旧会话。组装请求把系统提示、历史消息和可用工具描述发给模型。模型返回结果。结果可能是纯文本也可能包含工具调用意图。如果是工具调用Harness 先做参数校验和权限校验。校验通过后真正执行本地命令或脚本。执行结果被格式化成 tool_result追加到消息历史。再次调用模型让模型基于执行结果生成最终回答。这条链路里第 4、5、6 步是 Agent Harness 最核心的东西。你在源码里找不到一个叫 agent harness 的类也没有关系只要你能把这七步对应的位置找出来就已经抓住主框架了。2.3 用断点代替瞎猜读源码最容易犯的错是盯着代码猜“这一步大概会执行到这里”。更好的方式是让代码自己告诉你。具体做法在疑似入口的位置打断点。运行一条最小任务比如“列出当前目录文件”。观察调用栈看每一步真正进了哪些函数。在模型响应解析、工具执行、结果回填这几个位置额外观察变量值。这样读一遍比翻十遍文档都有用。尤其适合理解工具调用链路。因为模型返回的 tool_use 结构长什么样、工具执行结果怎么被拼回去只有实际断点才能看得最清楚。VSCode 里配置调试任务时只需要保证启动命令能传入用户输入即可。第一次调试不建议直接开批量任务日志会很多不利于定位主链路。3. 核心机制拆解工具注册、权限确认和消息循环3.1 工具从哪来注册表思维Claude Code 里不会把每个能力都写死在对话逻辑里。更常见的做法是先把所有可用工具集中注册到一个列表里每个工具有名称、描述、参数 schema 和执行函数模型请求时Harness 把这些工具描述塞进系统提示或请求参数模型想调用某个工具时输出对应的工具名称和参数。这个设计本质上是一个注册表。好处很明显新增工具时不需要改对话主流程。模型只需要看描述就能决定什么时候调用工具。Harness 可以根据 schema 校验参数避免错误输入传给真实命令。你读源码时可以重点找“工具注册”相关代码看一个工具最少需要提供哪些字段。大概率会看到 name、description、input_schema 这类结构。3.2 一次工具调用的完整生命周期一次工具调用不是“模型说了算”而是要经过一道完整通道。我把它拆成几个阶段解析从模型输出中识别出要调用哪个工具参数是什么。校验用工具声明的参数 schema 校验参数完整性。授权检查这个操作是否在允许范围内。能执行不代表可以直接执行有些操作需要用户确认。执行把参数传给真实函数或命令。回填把执行结果转换成模型可读的文本结构。继续把回填结果追加到消息历史触发下一轮模型调用。任何一步出问题Agent 都可能表现为“没有反应”“报错”或“结果不对”。所以排查时不要只盯模型先看日志里工具调用走到了哪一步。3.3 权限确认是安全边界也是自定义工具的第一道关卡Claude Code 作为能读写文件、执行命令的 Agent权限设计非常关键。你大概率会在源码里看到这样几类机制命令白名单或黑名单。目录白名单限制能读写的路径。交互式确认高危操作需要用户点击确认。理解权限机制对想自定义工具的人尤其重要。很多人加了一个脚本工具结果发现 Harness 根本不调用它或者调用了但没执行。这个时候先别怀疑工具注册先看权限配置脚本是否有可执行权限、是否在允许目录、命令是否被拦截。注意自定义工具再方便也不建议绕过权限确认。因为权限是 Agent 的最后一道安全边界一旦放开模型一旦被恶意输入诱导风险会成倍放大。同一个工具在不同环境里的权限表现也可能不一样。比如本机开发时一切正常到了 CI 或服务器上就调不动大概率是运行账号、环境变量或路径白名单不一致导致的。读源码时重点观察权限校验模块的参数来源它到底读的是配置文件、环境变量还是用户交互确认。4. 从能跑到看得懂最小任务和参数观察4.1 先用最小任务验证环境读源码之前或之后我都建议先跑一次最小任务。跑通之后再看日志和调用链会更容易对应上。先确认命令本身可用claude --version如果这条命令都不认识先检查安装是否完成不用继续往下排查。确认命令可用之后再跑一个最小任务。最小任务不求复杂只需要满足三个条件输入简单一句话能说清。会触发至少一次工具调用。输出结果稳定方便你对比。比如“列出当前目录下的文件”就很好。它足够简单又一定会触发文件和命令类工具。如果这一步都失败说明前置环境有问题而不是后面那些高级功能的问题。4.2 常见启动期问题要按顺序排查我在实际使用和读代码时遇到过几类高频问题这里按排查顺序列一下。第一类启动报认证失败或者返回 403。大多不是代码问题而是账号、token 或网络环境问题。先把登录态确认好再确认开发机有没有访问模型服务的网络条件。第二类报某个模型名不被当前版本识别。这在接入第三方模型时很常见。比如配置里写着“deepseek-v4-pro”但当前版本的 Claude Code 只识别自己维护的模型列表这时就会抛类似提示deepseek-v4-pro is not a model this version of claude code recognizes遇到这种提示先去改模型配置不要急着改源码。模型名、接口地址、鉴权方式任何一个对不上都会卡在这里。配置项通常从环境变量或配置文件读取确认你改的位置是实际生效的位置。第三类运行时报 529 或类似的限流状态码。说明模型服务端负载过高、额度不足或触发限流。和本地代码关系不大。我一般会等一段时间再试或者检查账号额度而不是反复重启进程。4.3 低配置机器怎么判断能不能跑Claude Code 本质是一个本地命令行外壳很多计算发生在模型服务端。但本地也不是完全没有资源消耗。会话历史过长、工具输出巨大、并行任务过多都会消耗内存和磁盘。如果你的机器配置偏低我建议第一次测试时这样控制不用超长上下文。不用大量文件并行处理。不一次性堆多个任务。先观察内存占用和日志量再决定要不要扩大规模。低配置能跑通单条任务不代表能扛住批量任务。批量场景要单独看资源占用、失败重试和输出目录。4.4 关于本地部署和离线环境的提醒如果你要把 Claude Code 部署到内网或离线环境前置条件会比一般使用更严格。你需要提前准备好完整依赖、模型服务的本地或内网实例、模型名称列表和 token 配置。CLI 本身只是一个前端外壳真正决定能不能工作的是它能不能连上你指定的模型服务。所以不要把本地部署理解成“装完就能用”。应该先确认三件事模型服务是否可用、当前版本支持哪些模型名、鉴权配置是否正确。这三件事没准备好源码读得再多也跑不起来。网上有很多 VSCode 配置 Claude Code 的教程这些配置本质上就是给 Harness 提供正确的启动参数和模型参数。如果配置不对报错无论如何也绕不过去。所以我在排查问题时不先改代码而是先看配置是否和当前环境匹配。5. 看懂 Harness 的输入输出和状态恢复边界5.1 Harness 的输入输出长什么样理解了主链路之后可以把 Agent Harness 看成一个转换器。它的输入是一堆混合内容它的输出是模型最终生成的文本或动作序列。一个典型的输入集合包括用户最新指令。系统提示告诉模型它是什么角色、能干什么。可用工具定义。历史对话消息。工具执行后的结果。输出则包括面向用户的最终文本。面向系统的工具调用请求。日志和状态事件。当你在源码里看到某个模块把上面这些信息拼接、格式化、传递给模型那基本就是 Harness 的主流程部分。读源码时不需要纠结每个字段叫什么名字先搞清楚谁是谁、在哪一步被拼进去、在哪一步被取出来就足够了。5.2 状态管理决定批量任务能不能跑稳单条任务能跑通不代表批量任务没问题。原因在于批量任务需要额外的状态管理能力。我最常看的三个点会话状态存到了哪里是本地文件还是临时目录。每条任务的输入输出是否独立文件名会不会冲突。任务失败后手动重跑和断点续跑是否支持。如果你要自己在 Claude Code 上层搭批量任务建议先想清楚这些问题。不要只看“能跑”要看“跑挂了之后容不容易恢复”。如果每条任务都写到同一个输出文件跑第二轮时大概率会互相覆盖。5.3 支持某功能不等于所有场景都稳定源码里能看到支持某些工具或格式但“支持”和“稳定”是两回事。真正常见的坑工具支持某个命令但该命令在某些系统上不存在。工具支持读取文件但文件编码不是 UTF-8解析会乱。工具支持解析代码但遇到超大文件性能和内存会翻车。遇到这类问题先检查输入格式和运行环境再判断是不是功能边界。我的经验是很多看起来像工具能力不够的问题最后都出在输入材料预处理上。你把文件格式、编码、路径先处理干净