在动手之前,先爬到山顶,把整座山的轮廓看清楚。
0.1 我们要构建什么
一个 Coding Agent 是一种能够:
- 接受人类用自然语言描述的编程任务
- 自主地分析代码库、编写代码、执行命令、调试错误
- 将任务完成情况持续反馈给人类
的软件系统。它不是一个聊天机器人,而是一个能够在真实操作系统上产生真实副作用(读写文件、执行程序)的自主代理(Agent)。
Claude Code 就是这样的系统。它的代码库包含约 50 万行 TypeScript,支撑着超过 40 种内置工具、60 余条斜杠命令、7 种任务类型、以及完整的 IDE 远程控制协议。
本系列的目标是:从 0 开始,逐层复刻出一个功能完备的 Coding Agent。
0.2 十六层抽象
Claude Code 的实现可以分解为 16 个相互堆叠的抽象层,从最底层的进程 I/O,到最顶层的后台智能调度。
┌─────────────────────────────────────────────────────┐ 第 16 章(选修)
│ 后台智能框架(forked Agent / Magic Docs) │
├─────────────────────────────────────────────────────┤ 第 15 章
│ 完整 Coding Agent(集成) │
├─────────────────────────────────────────────────────┤ 第 14 章
│ 插件系统 & MCP Server 集成 │
├────────────────────────────┬────────────────────────┤ 第 13 章
│ 多 Agent 派生 │ 任务调度器 │
├────────────────────────────┴────────────────────────┤ 第 12 章
│ 斜杠命令系统(/help /cost …) │
├─────────────────────────────────────────────────────┤ 第 11 章
│ 会话持久化 & 历史回放 │
├────────────────────────────┬────────────────────────┤ 第 10 章
│ 记忆系统(MEMORY.md) │ 文件历史快照(--rewind)│
├────────────────────────────┴────────────────────────┤ 第 9 章
│ 系统提示(System Prompt)& 上下文感知(CLAUDE.md) │
├─────────────────────────────────────────────────────┤ 第 8 章
│ 权限与安全仲裁层 │
├─────────────────────────────────────────────────────┤ 第 7 章
│ Agentic Loop(LLM ↔ 工具 反复执行) │
├────────────────────────────┬────────────────────────┤ 第 6 章
│ 对话管理(多轮历史) │ 消息格式化 │
├────────────────────────────┴────────────────────────┤ 第 5 章
│ LLM API 客户端(流式) │
├─────────────────────────────────────────────────────┤ 第 4 章
│ 工具抽象 & 工具执行引擎 │
├────────────────────────────┬────────────────────────┤ 第 3 章
│ CLI 命令路由(Commander) │ 子命令调度 │
├────────────────────────────┴────────────────────────┤ 第 2 章
│ 终端 UI 外壳(Ink + React) │
├─────────────────────────────────────────────────────┤ 第 1 章
│ 进程 & 标准 I/O │
└─────────────────────────────────────────────────────┘
硬件 / 操作系统(Node.js 运行时)
每一层:
- 依赖下面各层提供的能力
- 向上暴露更高级别的抽象
- 对应 Claude Code 源码中的一个或多个模块
0.3 一次完整请求的数据流
当用户在终端输入 "请帮我修复 main.ts 中的类型错误" 并回车,发生了以下事情:
用户按下 Enter
│
▼
[第 2 层 终端 UI] ─── Ink/React 捕获键盘事件,收集输入字符串
│
▼
[第 12 层 斜杠命令] ─── 检查是否是 /xxx 命令,不是则继续
│
▼
[第 9 层 上下文] ─── 读取 CLAUDE.md、git log、目录结构,构造 system prompt
│ ↑ [第 10 层 记忆系统] 将 MEMORY.md 注入此处的 system prompt
▼
[第 6 层 对话管理] ─── 将本次输入与历史消息拼接,组成 messages 数组
│
▼
[第 5 层 LLM 客户端] ── 流式调用 Claude API,收到第一个 token 时开始渲染
│
▼ LLM 返回 tool_use(要执行工具)
│
▼
[第 7 层 Agentic Loop]
│ ┌──────────────────────────────────────┐
│ │ 取出 tool_use 请求 │
│ │ │ │
│ │ ▼ │
│ │ [第 8 层 权限层] ── 需要确认? ──→ 用户确认弹窗
│ │ │ 通过 │
│ │ ▼ │
│ │ [第 4 层 工具引擎] ── 执行工具,得到结果│
│ │ │ 若 tool = AgentTool → [第 13 层 多 Agent 调度]
│ │ │ 若 tool = MCP 工具 → [第 14 层 插件/MCP]
│ │ ▼ │
│ │ 将 tool_result 追加到 messages │
│ │ │ │
│ └──────┘ (再次调用 LLM,循环直到完成)
│
▼ (postSamplingHook 异步触发)
[第 16 层 后台智能] ── fork 子 Agent 提炼记忆 / 更新 Magic Docs(不阻塞主线程)
│
▼
[第 11 层 持久化] ─── 将本轮完整对话写入磁盘
│
▼
[第 2 层 终端 UI] ─── 渲染最终回复,等待下一次输入
未出现在此流图中的章节:第 1 章(进程 I/O)和第 3 章(CLI 路由)属于启动期逻辑,仅在进程初始化时执行一次;第 15 章是集成章,无独立运行时层。
0.4 Claude Code 项目的物理结构
src/
├── main.tsx ← 程序入口(第 1、3 章)
├── bootstrap/state.ts ← 全局单例状态(第 1 章)
├── ink.ts / replLauncher.tsx← Ink 启动器(第 2 章)
├── components/ ← 200+ 个 React/Ink 组件(第 2 章)
├── cli/ ← CLI 子命令处理(第 3 章)
├── Tool.ts / tools.ts ← 工具接口 & 注册表(第 4 章)
├── tools/ ← 40+ 具体工具实现(第 4 章)
├── services/api/claude.ts ← Anthropic SDK 封装(第 5 章)
├── query/ ← Token 预算 / 停止条件(第 5、7 章)
├── history.ts ← 消息历史管理(第 6 章)
├── assistant/sessionHistory.ts← 会话历史(第 6 章)
├── QueryEngine.ts / query.ts← Agentic Loop 核心(第 7 章)
├── types/permissions.ts ← 权限模型(第 8 章)
├── utils/permissions/ ← 权限检查实现(第 8 章)
├── context.ts ← 系统上下文收集(第 9 章)
├── context/ ← Context React Hooks(第 9 章)
├── setup.ts ← 项目初始化 & CLAUDE.md(第 9 章)
├── memdir/ ← 记忆目录 & 三层 Memory(第 10 章)
├── utils/fileHistory.ts ← 文件历史快照 & --rewind(第 10 章)
├── services/autoDream/ ← autoDream 记忆整合(第 10 章)
├── projectOnboardingState.ts← 会话元数据(第 11 章)
├── commands.ts ← 斜杠命令路由(第 12 章)
├── commands/ ← 具体命令实现(第 12 章)
├── Task.ts / tasks/ ← 任务类型 & 状态机(第 13 章)
├── coordinator/ ← 多 Agent 调度(第 13 章)
├── plugins/ ← 插件加载(第 14 章)
├── services/ ← MCP、成本追踪等(第 14 章)
├── utils/forkedAgent.ts ← forked Agent 模式核心(第 16 章)
├── services/MagicDocs/ ← Magic Docs 自维护(第 16 章)
├── services/awaySummary.ts ← Away Summary(第 16 章)
├── services/PromptSuggestion/speculation.ts ← Speculation 预测执行(第 16 章)
└── state/ ← 不可变中心状态树(贯穿全部)
0.5 关键设计决策
在深入每一层之前,有 5 个贯穿全书的设计决策值得先记住:
决策 1:不可变中心状态
Claude Code 使用单一不可变状态树(AppState),所有更新都通过纯函数 (prev) => next 进行。
这与 Redux 的思想一致,但没有使用 Redux,而是用了 useSyncExternalStore + 自定义 store。
// 所有状态变更的样板
setState((prev: AppState): AppState => ({
...prev,
messages: [...prev.messages, newMessage],
}))
决策 2:权限优先
每个工具调用都要经过权限仲裁。权限规则来自 6 个来源(CLI参数 > 会话 > 企业策略 > 项目 > 用户偏好 > 默认值),优先级从左到右递减。
决策 3:流式优先
LLM 响应从第一个 token 起就开始渲染。工具调用在流中的 content_block_stop 事件处同步执行,而不是等到全部响应完成。
决策 4:Ink = 终端里的 React
所有 UI 使用 React 组件编写,由 Ink 将 React 虚拟 DOM 渲染为终端控制序列(ANSI 转义码)。
这意味着你可以在终端里用 useState、useEffect、Flexbox……
决策 5:工具即接口
每个工具是一个满足 Tool 接口的对象:包含 input_schema(Zod schema)、权限声明、以及 call() 方法。
LLM 在调用工具时只传 JSON,工具运行时负责校验和执行。
0.6 本章小结
| 概念 | 要记住的要点 |
|---|---|
| Coding Agent | 能在真实系统上产生副作用的自主代理 |
| 16 层抽象 | 从进程 I/O 到后台智能调度,逐层堆叠 |
| 数据流 | 用户输入 → 上下文 → LLM → 工具循环 → UI 渲染 |
| 不可变状态 | 所有状态变更是纯函数,防止状态污染 |
| 权限优先 | 任何副作用都需经过 6 级权限规则链仲裁 |
下一章
本章没有可运行产出物;从第 1 章开始,每章都有一个 chapters/N/ 目录,里面是可以直接运行的最小实现。