AgentHarness 课程

第二章:架构全景

三层架构、9阶段启动、查询循环状态机

学习时间: 4 小时 | 难度: ⭐⭐⭐ | 前置: 第一章


学习目标

完成本章后,学员将能够:

  • 绘制 Claude Code 的三层架构图
  • 描述启动流程的 9 个阶段及其优化策略
  • 解释查询循环状态机的 7 种 Continue 类型
  • 定位关键源文件并理解其职责

1. 三层架构

Claude Code 采用清晰的三层架构,每层职责分明:

┌─────────────────────────────────────────────────────────────┐
│ Layer 1: 启动引导层                                          │
│ cli.tsx (320行) → feature flag polyfill + 13条快速路径       │
│ → main.tsx (4683行) → Commander.js + 并行预取 + 初始化       │
└──────────────────────────────┬──────────────────────────────┘
                               │
┌──────────────────────────────▼──────────────────────────────┐
│ Layer 2: CLI 框架层                                          │
│ REPL.tsx (5009行) → React/Ink 终端 UI                       │
│ QueryEngine.ts (1320行) → 会话编排                           │
│ query.ts (1732行) → 7状态查询循环                            │
└──────────────────────────────┬──────────────────────────────┘
                               │
┌──────────────────────────────▼──────────────────────────────┐
│ Layer 3: 工具与运行层                                        │
│ 54 个内置工具 + MCP 外部工具                                  │
│ claude.ts (3420行) → API 流式通信                            │
│ mcp/client.ts (3351行) → MCP 协议                            │
│ bootstrap/state.ts (1758行) → 全局状态                       │
└─────────────────────────────────────────────────────────────┘

1.1 Layer 1: 启动引导层

核心文件: cli.tsx (320行) + main.tsx (4683行)

启动引导层的首要目标是极致的启动性能。它通过以下策略实现:

策略实现效果
快速路径13 条 --version--daemon 等直接短路零模块加载
Feature Flag DCEfeature('FLAG') 构建时替换为 false死代码消除
并行预取MDM + Keychain 与模块加载并行I/O 重叠
延迟加载首帧渲染后才加载非关键数据首屏更快
// cli.tsx 快速路径示例
if (args.version) {
  process.stdout.write(`${MACRO.VERSION}\n`)  // 零模块加载,直接输出
  return
}

// Feature Flag polyfill (反编译版)
// 在正式版中,feature() 由 bun:bundle 在构建时内联
function feature(flag: string): boolean {
  return false  // 所有内部功能禁用
}

1.2 Layer 2: CLI 框架层

核心文件: REPL.tsx (5009行) + QueryEngine.ts (1320行) + query.ts (1732行)

CLI 框架层基于 React + Ink 实现终端 UI,采用组件化架构:

REPL.tsx (主屏幕)
├── PromptInput (用户输入处理)
├── Messages (消息列表 + 虚拟滚动)
├── PermissionDialog (权限确认弹窗)
└── TaskPanel (后台任务面板)

QueryEngine 是会话编排器,负责:

  • 管理对话状态(消息列表、工具列表)
  • 触发自动压缩(token 接近限制时)
  • 维护文件状态快照(readFileState)
  • 调用 query() 函数执行查询循环

1.3 Layer 3: 工具与运行层

核心文件: claude.ts (3420行) + mcp/client.ts (3351行) + bootstrap/state.ts (1758行)

这一层包含所有运行时基础设施:

  • 54 个内置工具:BashTool、FileEditTool、GrepTool、AgentTool 等
  • MCP 客户端:8 种传输协议的统一管理
  • 全局状态:进程级单例,管理 session ID、CWD、token 计数等

2. 启动流程(9 阶段)

Claude Code 的启动流程经过精心优化,分为 9 个阶段:

阶段 1: cli.tsx 入口
  → polyfill 注入 (feature/MACRO/BUILD_TARGET)
  → 确保运行时环境正确

阶段 2: 快速路径检查
  → 13 条快速路径 (--version, --daemon, --bridge, --worktree 等)
  → 命中则直接返回,不加载 main.tsx

阶段 3: main.tsx → Commander.js 参数解析
  → 解析命令行参数
  → 确定运行模式 (交互/管道/守护进程)

阶段 4: 并行预取
  → startMdmRawRead()        — MDM 子进程并行读取
  → startKeychainPrefetch()   — macOS keychain 并行预取
  → 与模块加载并行执行

阶段 5: init() 初始化
  → 配置加载 + 环境变量
  → 优雅关闭信号注册

阶段 6: 信任对话框
  → 首次使用时显示安全提示
  → 用户确认后写入配置

阶段 7: 迁移脚本
  → 12 个 migrations/ 脚本
  → 处理配置格式升级

阶段 8: 服务初始化
  → analytics + GrowthBook + 策略限制 + MCP
  → 初始化所有运行时服务

阶段 9: 模式分支
  → 管道模式 (-p): 读 stdin,输出结果
  → 交互模式: launchRepl() 启动 REPL

2.1 关键优化:并行预取

// main.tsx 中的并行预取(概念还原)
const [mdmData, keychainData] = await Promise.all([
  startMdmRawRead(),        // 企业 MDM 配置
  startKeychainPrefetch(),  // API key 缓存
  loadModules(),            // 模块加载
])

这种并行策略确保 I/O 密集操作不会阻塞启动流程。


3. 查询循环状态机

query.ts (1732行) 是整个系统的核心——它实现了 LLM 多轮对话的状态机。

3.1 循环流程

query() 调用
  ├─ 构建 system prompt (getSystemPrompt, ~900行)
  ├─ 准备消息 (normalizeMessagesForAPI)
  ├─ 流式 API 调用 (queryModelWithStreaming, claude.ts:753)
  │
  └─ 流式事件处理:
      ├─ message_start → 开始新消息
      ├─ content_block_start → 开始内容块
      ├─ delta → 增量文本/工具调用
      └─ stop → 检查 stop_reason
          │
          └─ 7 种 Continue 类型:
              ├─ tool_use → StreamingToolExecutor → 注入结果 → 循环
              ├─ end_turn → 返回最终结果
              ├─ max_output_tokens → 恢复尝试 (最多3次)
              ├─ prompt_too_long → context-collapse → reactive-compact
              ├─ error → 重试/报告
              └─ interrupt → 用户中断

3.2 7 种 Continue 类型详解

类型含义处理策略
tool_result工具执行完成注入结果到消息列表,继续循环
max_tokens_recovery输出被截断尝试恢复(最多 3 次)
prompt_too_long_recovery上下文过长触发压缩(截断→摘要→升级限制)
end_turn对话自然结束返回最终结果
tool_use需要执行工具交给 StreamingToolExecutor
errorAPI 错误重试或报告给用户
interrupt用户中断清理并返回

3.3 Withhold-then-Recover 错误处理

这是 Claude Code 最精妙的错误处理策略之一:

prompt_too_long 错误发生
  ↓
第一步: 抑制(不 surface 给用户)
  ↓
第二步: 尝试 context-collapse
  → 截断最旧的 API round
  → 最多尝试 3 次
  ↓
第三步: 尝试 reactive-compact
  → fork 子 agent 生成摘要
  → 替换截断的消息
  ↓
第四步: 升级 max_tokens
  ↓
第五步: 全部失败才 surface 错误给用户

设计意图: 用户不应该因为临时的上下文长度问题而看到错误消息。系统应该先尝试自动恢复,只有在所有恢复策略都失败时才告知用户。


4. 关键文件索引表

文件行数职责验证状态
src/entrypoints/cli.tsx320入口 + polyfill + 快速路径
src/main.tsx4683Commander.js CLI + 初始化
src/query.ts1732核心查询循环 (7状态机)
src/QueryEngine.ts1320会话编排器
src/Tool.ts792Tool 接口定义
src/tools.ts389工具注册表 (54个)
src/commands.ts754命令注册表 (~113个)
src/context.ts上下文收集
src/constants/prompts.ts~900System prompt 构建
src/screens/REPL.tsx5009主 REPL 屏幕
src/services/api/claude.ts3420API 客户端
src/services/mcp/client.ts3351MCP 客户端 (8种传输)
src/services/compact/~1700上下文压缩
src/services/tools/StreamingToolExecutor.ts530流式工具执行器
src/bootstrap/state.ts1758全局单例状态
src/tools/AgentTool/1397子 Agent 工具
src/utils/permissions/~1500权限系统
src/utils/claudemd.tsCLAUDE.md 加载
src/utils/hooks/~5121Hook 系统 (20+事件)
src/ink/104文件Ink 框架 (内部fork)

设计模式

本章涉及的模式:

模式定义应用位置
Fast-Path + DCE多路分发 + 构建时死代码消除cli.tsx 13条快速路径
State Machine状态转换追踪 + 错误恢复query.ts 7种Continue类型
Parallel PrefetchI/O 密集操作与模块加载并行main.tsx 并行预取
Withhold-then-Recover错误先抑制,多级恢复策略query.ts prompt_too_long 处理

源码验证

本章所有架构信息均经过源码验证:

  • ✅ 三层架构:cli.tsx / main.tsx / query.ts 职责划分确认
  • ✅ 9 阶段启动:逐阶段对照 main.tsx 源码确认
  • ✅ 7 种 Continue 类型:query.ts 状态机代码确认
  • ✅ 文件行数:所有文件行数误差 < 5%
  • ⚠️ 文档声称 main.tsx 500+ 行,实际 4683 行(9倍差距)

思考题

  1. 为什么 Claude Code 把 cli.tsxmain.tsx 分成两个文件? 这种分层对启动性能有什么影响?

  2. 查询循环中的 max_output_tokens 恢复最多尝试 3 次,为什么是 3 次而不是其他数字? 这个数字应该如何确定?

  3. Withhold-then-Recover 策略在什么场景下可能导致问题? 比如,如果错误信息对用户很重要,但被自动恢复掩盖了?

  4. 如果让你设计一个类似的 Agent 查询循环,你会增加哪些 Continue 类型? 比如「需要用户提供更多信息」?


01-课程导论 | 03-工具系统