ClaudeCode 源码深度剖析:从零读懂 Agent 架构与 MVP 最小骨架实现
ClaudeCode 源码深度剖析:从零读懂 Agent 架构与 MVP 最小骨架实现
很多开发者学习 Agent 框架源码时,都会陷入一个误区:直接逐文件啃代码、逐行读逻辑,最终只会陷入零散的函数调用中,看不懂整体架构、摸不清模块依赖,更无法落地复用。
Claude Code 作为 Anthropic 官方的代码智能 Agent,其架构设计简洁、分层清晰、低耦合高可扩展,是入门 Agent 框架开发的绝佳范本。
本文将采用**「架构先行 → 链路贯通 → 源码落地」的实战思路,带大家系统拆解 ClaudeCode 源码:先梳理整体分层架构与核心设计约束,搞懂框架的底层设计思想;再通过真实业务场景贯通完整运行链路,理解模块协作逻辑;最后落地官方 examples/mvp 6 个核心文件,手写最简可运行骨架,真正实现读懂、吃透、可复用**。
一、阅读前置与受众定位
1.1 适合人群
本文面向所有想要入门 Agent 框架底层开发的工程师,尤其适合:
- 想要从源码层面理解 Claude Code、通用 LLM Agent 架构设计的开发者
- 自研 Agent 时,频繁在消息流转、多轮循环、工具调用、状态管理环节踩坑,需要标准架构参考的从业者
- 具备基础 Node.js/TS 能力,想要从零实现轻量化 Agent 骨架的技术学习者
1.2 前置技术储备
阅读本文无需熟悉 Claude Code 底层源码与 Anthropic 接口,仅需掌握基础技术栈:
- TypeScript 基础语法、联合类型、
async/await、异步生成器async generator - Node.js 原生模块:
fs/promises文件读写、readline/promises终端交互 - 基础异步编程思想,了解 LLM 对话多轮交互逻辑即可
二、核心架构拆解:六层分层设计与全局约束
优秀框架的核心一定是分层架构与单向依赖。ClaudeCode 摒弃了混乱的模块耦合设计,将整套系统拆分为六大层级,各层级职责单一、边界清晰,仅与相邻层级通信,彻底规避跨层依赖、循环依赖问题,这也是其可扩展、易维护的核心原因。
2.1 六层架构层级全解析
从用户交互到底层数据持久化,自上而下完整层级职责如下,所有业务逻辑、工具调用、模型交互均围绕该架构运转:
- 入口层(UI 交互层):核心文件
index.ts,系统唯一交互入口。负责接收用户终端输入、实时回显运行事件、维护全局会话消息队列,不处理任何业务逻辑与模型调度。 - 编排层(核心调度层):核心文件
query.ts,全局唯一状态机,是整个 Agent 的大脑。管控多轮对话循环、模型调用调度、工具执行触发、异常容错、事件产出,所有核心流转逻辑均收敛于此。 - 模型层(LLM 适配层):核心文件
modelClient.ts、fakeModel.ts、config.ts。封装统一的模型调用入口,通过配置文件实现模拟模型/真实 Anthropic LLM API 无缝切换,彻底解耦业务与具体模型实现。 - 工具层(能力执行层):核心文件
tools.ts。统一所有工具的执行入口,内置权限校验、本地 IO 操作、执行结果封装、异常错误兜底,所有工具调用均通过该层统一调度。 - 消息协议层(数据地基层):核心文件
messages.ts。定义系统全量消息类型、提供标准化消息工厂函数,是所有模块的数据依赖基础,全局统一消息格式,保证流转一致性。 - 持久化层(扩展能力层):核心文件
compress.ts、session.ts,可选扩展层级。实现对话历史压缩、会话数据保存与恢复,MVP 最小骨架可舍弃,不影响核心运行。
2.2 四条不可突破的架构硬约束
分层只是表象,真正支撑框架稳定性和扩展性的是依赖约束规则。这 4 条核心原则是 ClaudeCode 架构设计的精髓,所有源码开发、二次迭代都必须严格遵守,一旦打破架构直接失效:
- 编排层与模型实现完全解耦:
query.ts仅通过通用模型响应类型做逻辑判断,不依赖任何具体模型(模拟/真实 API),切换 LLM 服务商无需修改调度核心代码。 - 模型层统一路由枢纽:
modelClient.ts是唯一的模型切换入口,所有模型配置、接口路由、环境切换均收敛于此,改动成本极低。 - 工具层独立可复用:工具执行逻辑不依赖编排层,上层通过统一方法间接调用工具,工具能力可被任意业务场景复用,不绑定对话调度逻辑。
- 消息协议全局唯一地基:
messages.ts被所有模块依赖,自身不依赖任何业务代码,全局消息格式统一,修改该文件会影响全系统,需谨慎迭代。
2.3 架构避坑准则
基于以上约束,可快速校验代码合理性,规避经典架构问题:
- 禁止在编排层直接引入具体模型实现 → 破坏解耦设计,无法快速切换 LLM
- 禁止在工具层反向调用编排层方法 → 形成循环依赖,导致系统调度混乱
- 禁止拆分全局消息类型定义 → 破坏数据统一性,全模块类型适配失效
2.4 全局架构依赖全景图
为直观展现六大层级之间的依赖关系,下面给出整套系统的分层架构图,所有箭头方向严格遵循上述 4 条核心约束:
三、业务链路贯通:一次文件读取的完整 Agent 运行流程
看懂静态架构后,需要通过真实业务链路贯通所有模块,理解模块如何协作、数据如何流转、状态如何更新。我们以最经典的 read package.json(读取项目配置文件)场景,拆解完整的两轮模型调度+工具执行链路,还原 Agent 核心运行心跳。
3.0 完整链路时序图
3.1 完整链路流程拆解
整体流程分为模型决策工具调用、工具执行处理、模型汇总返回三个核心阶段,闭环完成一次用户请求:
- 用户输入与消息初始化:用户在终端输入
read package.json,入口层接收输入,通过消息工厂生成标准化用户消息,推入全局会话消息队列,触发编排层调度。 - 第一轮模型调度(工具决策):编排层调用模型路由入口,模型识别用户「读取文件」意图,不直接返回文本,而是输出结构化
tool_use工具调用指令,指定调用文件读取工具、传入文件路径参数。 - 工具权限校验与执行:编排层接收模型指令,触发工具执行函数,先进行用户权限询问,校验通过后通过 Node.js 原生 API 读取本地
package.json文件内容。 - 工具结果封装回传:文件读取完成后,工具层将执行结果(成功内容/失败信息)统一封装为
tool_result消息,更新至全局消息队列,回传给编排层。 - 第二轮模型调度(结果汇总):模型接收包含工具执行结果的完整消息队列,解析文件内容,生成自然语言总结回复。
- 会话结束与结果回显:编排层接收最终模型回复,终止多轮循环,入口层将结果渲染至终端,完成一次完整对话。
3.2 链路核心设计亮点
- 结构化交互:模型不输出自由文本,通过结构化指令驱动工具执行,可控性、可扩展性极强
- 统一异常链路:工具执行成功、权限拒绝、读取失败、未知工具,全部走统一结果封装逻辑,无散乱异常
- 消息驱动状态:整个 Agent 无全局状态变量,所有决策、状态流转完全依赖消息队列,解耦彻底
四、MVP 最小骨架落地:6 个核心文件源码逐析
完整的 ClaudeCode 源码包含会话持久化、历史压缩、多工具池、路径沙箱、API 限流容错等冗余扩展能力。而官方 examples/mvp 剔除了所有非核心能力,仅保留能跑通完整对话+工具调用链路的 6 个核心文件,完美复刻原版架构设计,是入门落地的最佳模板。
6 个文件严格遵循六层架构与四条核心约束,依赖关系清晰、无冗余代码,下面逐文件拆解源码、核心逻辑与设计思想。
4.0 6 个核心文件的实现层依赖图
将前面那张全景图"剪裁"到 examples/mvp,依赖箭头方向与 4 条硬约束保持完全一致:
对照规则:当你在 6 个文件里看到任何不符合这张图的依赖关系,都是反例。
4.1 消息协议层:message.ts(全局数据地基)
该文件是整个项目的类型与数据核心,定义了 Agent 对话的所有消息类型,提供标准化创建函数,所有模块均依赖该文件,自身无任何业务依赖,实现「消息即状态」的核心设计。
export type UserMessage = {
role: 'user'
content: string
}
export type AssistantMessage = {
role: 'assistant'
content: string
}
export type ToolUseMessage = {
role: 'tool_use'
id: string
toolName: string
input: Record<string, unknown>
}
export type ToolResultMessage = {
role: 'tool_result'
toolUseId: string
toolName: string
content: string
isError?: boolean
}
// 全局消息联合类型,统一所有对话数据格式
export type Message =
| UserMessage
| AssistantMessage
| ToolUseMessage
| ToolResultMessage
let nextToolUseNumber = 1
// 各类消息标准化工厂函数
export function createUserMessage(content: string): UserMessage {
return { role: 'user', content }
}
export function createAssistantMessage(content: string): AssistantMessage {
return { role: 'assistant', content }
}
export function createToolUseMessage(
toolName: string,
<string, unknown>,
): ToolUseMessage {
return {
role: 'tool_use',
id: `toolu_${nextToolUseNumber++}`,
toolName,
input,
}
}
export function createToolResultMessage(
toolUse: ToolUseMessage,
content: string,
isError = false,
): ToolResultMessage {
return {
role: 'tool_result',
toolUseId: toolUse.id,
toolName: toolUse.toolName,
content,
...(isError ? { isError } : {}),
}
}
// 工具方法:获取最新消息、最新用户消息
export function lastMessage(messages: Message[]): Message | undefined {
return messages.at(-1)
}
export function lastUserMessage(messages: Message[]): UserMessage | undefined {
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i]
if (message?.role === 'user') return message
}
return undefined
}
核心设计要点:通过联合类型实现 TypeScript 自动类型收窄,无需类型断言;自增 ID 实现工具调用与结果精准配对;按需挂载 isError 字段,精简消息结构,让模型语义更清晰。
4.2 模型路由层:modelClient.ts(统一调用入口)
作为模型层的统一枢纽,该文件彻底解耦业务与具体模型实现,对外暴露唯一的 callModel 方法,为后续切换真实 LLM API 预留完整扩展能力。
import { fakeModel } from "./fakeModel.ts";
import { Message, ToolResultMessage, UserMessage } from "./message.ts";
// 模型统一响应类型,仅两种输出结果
export type ModelResponse =
| { type: 'assistant'; content: string }
| { type: 'tool_use'; toolName: string<string, unknown> }
// 允许传入模型的消息类型
export type ModelMessage = UserMessage | ToolResultMessage
// 全局唯一模型调用入口
export async function callModel(messages: Message<ModelResponse> {
return await fakeModel(messages)
}
核心设计要点:固定模型输出契约,仅支持「文本回复/工具调用」两种结果;极简路由逻辑,后续切换 Anthropic 真实 API 仅需修改当前文件,业务层完全无感。
4.3 模拟模型层:fakeModel.ts(无状态规则模拟)
MVP 骨架无需依赖真实 LLM,通过规则模拟模型决策逻辑,复刻 LLM「意图识别-工具调用-结果汇总」的完整能力,无状态设计,完全依赖消息队列判断对话轮次。
import { lastMessage, Message, ToolResultMessage, UserMessage } from "./message.ts";
import { ModelResponse } from "./modelClient.ts";
export async function fakeModel(messages:<ModelResponse> {
const latest = lastMessage(messages)
// 处理用户最新输入:识别工具调用意图
if (latest?.role === 'user') {
const lowerText = latest.content.toLocaleLowerCase()
if (shouldReadFile(latest.content, lowerText)) {
return {
type: 'tool_use',
toolName: `Read`,
input: {
filename: extractPath(latest.content)
}
}
} else {
return {
type: 'assistant',
content: `普通回答:${latest?.content}`
}
}
}
// 处理工具执行结果:汇总生成最终回复
else if (latest?.role === 'tool_result') {
if (latest.isError) {
return {
type: 'assistant',
content: `工具 ${latest.toolName} 执行失败:${latest.content}`
}
}
return {
type: 'assistant',
content: `Read package.json:${latest?.content}`
}
}
return {
type: 'assistant',
content: `无法处理消息:${JSON.stringify(latest)}`
}
}
// 匹配文件读取意图(中英文关键词兼容)
function shouldReadFile(text: string, lowerText: string): boolean {
return (
text.includes('读') ||
text.includes('打开') ||
lowerText.includes('read ') ||
lowerText.includes('show file')
)
}
// 启发式提取文件路径
function extractPath(text: string): string | undefined {
const quoted = text.match(/["'`](.+?)["'`])?.[1]
if (quoted) return quoted
const tokens = text.split(/\s+/).filter(Boolean)
const pathLikeToken = tokens.find(token =>
/[./\\]|\.json$|\.md$|\.ts$|\.txt$/i.test(token),
)
if (pathLikeToken) return pathLikeToken
const commandWords = new Set([
'读', '读取', '打开', '列出', '目录',
'read', 'show', 'file', 'list', 'ls',
])
return tokens.find(token => !commandWords.has(token.toLowerCase()))
}
核心设计要点:无状态设计,仅通过最新消息角色判断对话轮次;中英文意图识别+路径智能提取;统一处理工具成功/异常场景,保证循环不卡死。
4.4 工具执行层:tools.ts(权限+执行+异常统一封装)
统一工具执行入口,整合路径解析、权限校验、IO 执行、结果封装能力,所有工具执行逻辑收敛于此,上层无需关注底层实现。
import { readFile } from 'fs/promises'
import { createToolResultMessage, ToolResultMessage, ToolUseMessage } from "./message.ts";
import path from 'node:path';
import { RuntimeOption } from './query.ts';
// 统一工具执行入口
export async function executeToolUse(tool_use: ToolUseMessage, runtime:<ToolResultMessage> {
if (tool_use.toolName === 'Read') {
// 解析绝对路径
const readPath = path.resolve(runtime.toolContext.rootDir, tool_use.input.filename as string)
// 交互式权限校验
const hasAccess = await runtime.askUser(`申请访问:${readPath}`)
if (!hasAccess) return createToolResultMessage(tool_use, "访问被拒绝", false)
// 读取文件并返回标准化结果
const content = await readFile(readPath, 'utf-8')
return createToolResultMessage(tool_use, content)
}
// 未知工具统一异常返回
return createToolResultMessage(tool_use, "未知工具调用失败", false)
}
核心设计要点:路径根目录可控,为后续沙箱白名单扩展预留能力;权限询问与业务逻辑解耦;所有执行结果走统一消息通道,无散落异常。
4.5 编排调度层:query.ts(全局状态机核心)
整个 Agent 的核心调度中枢,基于异步生成器实现多轮循环,管控模型调用、工具执行、事件产出、轮次兜底,是唯一掌控「对话轮次」的模块。
import { createAssistantMessage, createToolUseMessage, Message, ToolResultMessage, ToolUseMessage } from "./message.ts"
import { callModel } from "./modelClient.ts";
import { executeToolUse } from "./tools.ts";
// 对外事件类型:统一UI渲染数据源
export type QueryEvent =
| { type: 'assistant'; message: Message }
| { type: 'tool_use'; message: Message }
| { type: 'tool_result'; message: Message }
// 运行时配置:根目录+用户权限询问方法
export type RuntimeOption = {
toolContext: { rootDir: string },
askUser: (p: any) => any
}
// 核心调度异步生成器
export async function* query(messages: Message[], runtime: RuntimeOption): AsyncIterable<QueryEvent, void> {
// 最大轮次兜底,防止死循环
const maxToolRounds = 5
for (let round =< maxToolRounds; round++) {
// 调用模型获取决策
const response = await callModel(messages)
// 模型直接返回文本:结束对话
if (response.type === 'assistant') {
yield { type: 'assistant', message: createAssistantMessage(response.content) }
return
}
// 模型触发工具调用:执行工具流程
const toolUse = createToolUseMessage(response.toolName, response.input)
messages.push(toolUse);
yield { type: 'tool_use', message: toolUse }
// 执行工具并获取结果
const toolResult = await executeToolUse(toolUse, runtime)
messages.push(toolResult);
yield { type: 'tool_result', message: toolResult }
}
// 超出最大轮次,强制终止
const failed = createAssistantMessage(
`工具循环超过 ${maxToolRounds} 轮,已停止。`,
)
messages.push(failed)
yield { type: 'assistant', message: failed }
}
核心设计要点:异步生成器实现流式事件产出,UI 可实时渲染中间状态;最大轮次防呆兜底;纯消息驱动循环,无硬编码终止条件;异常无需 try/catch,全部由工具层封装。
4.6 入口交互层:index.ts(可替换 REPL 终端)
系统交互入口,负责终端输入输出、会话消息维护、事件渲染,与核心调度逻辑完全解耦,可直接替换为 HTTP、WebSocket、GUI 交互。
import { cwd, stdin, stdout } from "node:process"
import { createInterface } from "node:readline/promises"
import type { Message } from "./src/message.ts";
import { createUserMessage } from "./src/message.ts";
import { query, QueryEvent } from "./src/query.ts";
const rl = createInterface({
input: stdin,
output: stdout
})
const lineIterator = rl[Symbol.asyncIterator]()
// 全局会话消息状态
const messages: Message[] = []
// 持续监听用户输入
while (true) {
const answer = await ask('user:\n')
if (answer == null) break;
const input = answer.trim();
if (input === "") continue;
messages.push(createUserMessage(input))
// 消费调度事件,实时渲染结果
for await (const event of query(messages, {
toolContext: { rootDir: cwd() },
askUser: async question => {
const answer = await ask(`${question} [y/N] `);
if (answer === null) return false;
return answer.trim().toLowerCase() === "y";
}
})) {
renderEvent(event)
}
}
rl.close();
// 终端提问工具方法
async function ask(prompt: string) {
stdout.write(prompt);
const next = await lineIterator.next()
return next.done ? null : next.value
}
// 统一事件渲染方法
function renderEvent(event: QueryEvent) {
const message = event.message
if (message.role === 'assistant') {
console.log(`assistant:\n ${message.content}`)
return
}
if (message.role === 'tool_use') {
console.log(`tool_use:\n ${JSON.stringify(message.input)}`)
return
}
if (message.role === 'tool_result') {
const status = message.isError ? 'error' : 'ok'
const preview =
message.content.length > 500
? `${message.content.slice(0, 500)}\n...`
: message.content
console.log(`tool_result(${status}): ${message.toolName}\n${preview}`)
}
}
核心设计要点:会话状态由入口层维护,调度层仅更新状态;UI 与核心业务完全解耦,支持无缝替换交互场景;事件流式渲染,实时展示工具调用、执行结果、最终回复。
4.7 架构图与源码端到端对照
第三部分的时序图描述了"完整图被点亮一次"的全过程,本节给出"完整图被落地为 6 个文件"的对照清单。把时序图中的每一拍和源码核到一起,可以验证"骨架"和"实现"是完全一致的:
| 阶段 | 调用方 | 被调函数 | 在哪个文件 |
|---|---|---|---|
| 用户输入 | index.ts | ask('user:\n') |
index.ts |
| 消息入栈 | index.ts | createUserMessage(input) |
message.ts |
| 编排开启 | index.ts | query(messages, runtime) |
query.ts |
| 模型决策 | query.ts | callModel(messages) |
modelClient.ts |
| 模拟模型 | modelClient.ts | fakeModel(messages) |
fakeModel.ts |
| 工具调用包装 | query.ts | createToolUseMessage(...) |
message.ts |
| 工具执行 | query.ts | executeToolUse(toolUse, runtime) |
tools.ts |
| 真实读取 | tools.ts | readFile(readPath, 'utf-8') |
Node.js fs |
| 结果包装 | tools.ts | createToolResultMessage(...) |
message.ts |
| 收尾 | query.ts | yield ... + return |
query.ts |
| 渲染 | index.ts | renderEvent(event) |
index.ts |
核心设计要点:消息流是双向的,但所有数据都是同一套类型。模型不返回字符串,返回结构化 ModelResponse;工具不抛异常,返回结构化 ToolResultMessage;UI 不直接传字符串,传 QueryEvent。三层之间的接口都是"结构 + 类型",没有 any 漏到主路径。
五、MVP 骨架能力复盘与扩展方向
本文拆解的 6 文件最简骨架,已经完整跑通「用户输入→模型决策→工具执行→结果汇总」的标准 Agent 核心链路,完全复刻 ClaudeCode 原版架构设计思想。同时该骨架预留了完整扩展接口,可基于现有架构快速迭代完善能力。
5.1 当前骨架缺失的高阶能力(扩展方向)
- 真实 LLM API 对接:在
modelClient.ts新增 Anthropic 真实接口路由 - 会话持久化:新增历史压缩、会话保存恢复能力
- 安全沙箱:新增路径白名单、越权访问拦截机制
- 异常容错:补充模型超时、网络异常、API 限流捕获逻辑
- 多工具扩展:基于统一工具入口,新增文件夹读取、代码修改、命令执行等工具
- 权限精细化:完善 allow/ask/deny 三态权限策略
六、总结
读懂 ClaudeCode 源码的核心,不在于熟记每一行代码,而在于吃透其分层架构、单向依赖、消息驱动的设计思想:
- 六层分层架构实现职责单一、边界清晰,从根源规避耦合问题;
- 四条核心依赖原则,保障框架的可扩展性与可维护性;
- 消息驱动的无状态设计,是 LLM Agent 多轮交互、工具调度的最优范式;
- MVP 最简骨架以最小成本复刻了官方核心能力,可直接作为自研 Agent 的基础模板。
掌握这套架构逻辑后,不仅能彻底理解 Claude Code 的运行原理,更能快速迁移到任意通用 Agent 框架的开发与二次迭代中。
延伸阅读
- 基础篇:四层架构全景与组件选型
- 记忆篇:CLAUDE.md 记忆系统深潜
- 官方文档:https://docs.anthropic.com/en/docs/claude-code/overview
更多推荐

所有评论(0)