如果把 Claude Code 理解成“终端里调用模型的聊天程序”,源码会显得杂乱:CLI、Ink UI、工具、MCP、hooks、skills、subagents 都在抢注意力。换一个角度就清楚了:Claude Code 的产品表面是 CLI,工程本体是一条受治理的 Agent 执行管线。

本文以 anthropics/claude-code 当前主线和 v2.1.281 发布状态为参照,结合官方对 skills、hooks、rules 和 subagents 的说明,重新组织源码阅读路径。由于仓库会持续发布构建产物和内部实现,文中的文件名用于解释稳定概念,具体版本应以仓库为准。

01 先给心智模型:它是什么,不是什么

工具系统架构

Claude Code 不是“模型 + 一组函数”。更准确的抽象是:

用户输入
  -> Session / Transcript
  -> Context Builder
  -> Model Stream
  -> Tool Call
  -> Permission / Sandbox / Hook
  -> Tool Result
  -> Event + Persistence
  -> 继续循环或结束

这条链上的每个环节都可能改变下一次模型调用:权限系统可以拒绝工具,hook 可以补充上下文,工具结果会进入 transcript,压缩器可以把旧历史投影成摘要,subagent 可以把一段工作移到独立会话。

因此它不是只靠 system prompt 驱动的聊天机器人,也不是只靠 while true 的脚本,更不是把所有外部能力无条件暴露给模型的插件容器。它是一套“模型决策、宿主治理、执行环境、持久化事件”协同工作的 harness。模型负责提出下一步,宿主负责判断这一步能否执行,环境负责产生事实,状态层负责让下一步可恢复。

02 启动流程:最先建立的不是模型,而是信任边界

启动流程

启动可以分成六个概念阶段:

  1. 解析命令行和运行模式,决定交互式、print、resume 或 SDK 路径。
  2. 确定工作目录、配置目录、项目级设置和会话身份。
  3. 读取规则、CLAUDE.md、skills、plugins 和 MCP 声明,但不把所有内容都直接塞进 prompt。
  4. 创建 UI 或 headless transport,并安装事件订阅者。
  5. 装配工具、权限策略、sandbox、hooks 和成本追踪器。
  6. 创建 query/agent loop,直到收到第一条用户消息才真正发起模型请求。

这里有一个容易忽略的设计:配置加载本身也是安全边界。项目目录中的规则可以影响模型,但不应无条件获得宿主权限;插件可以注册能力,但其 hook 和工具仍然要经过宿主的策略层。当前版本的插件文档也把 managed settings、插件和用户配置明确区分,防止低信任来源覆盖组织策略。

03 Agent Loop:模型只负责提出下一步

Agent 循环核心流程

把一次 turn 简化成伪代码:

while (!turnFinished) {
  const context = buildContext(session, pendingMessages, toolRegistry)
  const stream = await model.stream(context)

  for await (const event of stream) {
    publish(event)
    if (event.type !== "tool_call") continue

    const decision = await policy.authorize(event.call)
    if (!decision.allowed) {
      appendToolResult(event.call, decision.reason)
      continue
    }
    const result = await execute(event.call, decision.environment)
    appendToolResult(event.call, result)
  }
}

真实实现比这复杂,但责任边界基本一致:模型流是输入,tool call 是意图,权限层是决策点,工具执行是副作用,transcript 是事实来源。工具执行完成后不能只更新 UI,还必须把结果写回会话,否则 resume、压缩和审计都会失去依据。

当前工程最重要的变化,是“循环”不再是一个孤立函数。它被 hooks、subagents、计划模式、MCP、resume、成本限制和后台任务包围,逐渐成为事件驱动的运行时。

04 工具执行:意图和副作用之间必须有闸门

流式工具执行

工具调用至少经历四步:schema 校验、权限决策、环境执行、结果归档。

4.1 Schema 校验不是安全策略

schema 只能说明参数形状正确,不能说明操作安全。例如 bash 的参数是合法字符串,不代表它可以删除文件;Write 的路径是合法字符串,也不代表它属于当前工作区。因此 schema、权限规则和 sandbox 必须分别存在。

4.2 Permission 是策略管线

策略通常综合工具名、参数、路径、当前 permission mode、用户规则、组织规则和执行环境。拒绝不是异常,而是合法的工具结果;模型收到拒绝后可以改变计划。

4.3 Hook 是生命周期拦截器

当前文档中的 hook 已不只是 shell 脚本,可以在 SessionStart、PreToolUse、PostToolUse、Stop 和 SubagentStop 等事件上运行 command、prompt、HTTP 或 MCP tool。它们适合做格式化、审计、注入项目上下文和阻断高风险操作,但不应被当成唯一安全边界。

4.4 结果要分清模型内容和 UI 详情

工具输出既要提供给模型,也要服务于 UI、日志和诊断。大输出需要截断,敏感字段需要脱敏,二进制内容需要单独处理;“屏幕上显示的文本”和“下一轮 prompt 里的文本”不应该天然相同。

05 上下文工程:压缩不是删除,而是重建可继续工作的状态

上下文管理

Claude Code 的上下文由多个来源组成:系统约束、项目规则、用户输入、工具 schema、当前 transcript、外部资源、技能指令和 subagent 结果。工程上的难点不是“再加一段 prompt”,而是控制优先级和生命周期。

可以把压缩理解成一个投影:

完整 transcript
  -> 保留未完成任务、约束、决策、文件变更和验证结果
  -> 摘要旧工具日志与已解决分支
  -> 恢复必要的工具状态
  -> 生成下一轮可消费的 context

好的压缩必须回答四个问题:任务目标是什么、已经做了什么、哪些假设被验证过、下一步不能忘记什么。只做摘要会丢掉“事实来源”,只保留原文又会让窗口被日志淹没。因此摘要、文件状态、git diff、工具结果和会话事件应有不同的保留策略。

Skill 也属于上下文工程。渐进式工具披露和按需加载可以降低初始 token 成本,让模型在需要时才获得某个领域的流程,而不是启动时把所有技能全文拼进系统消息。

06 Skill、MCP、Plugin、Subagent:四种扩展不要混为一谈

Skill 加载架构

扩展主要改变什么最适合解决的问题
Rule / CLAUDE.md改变长期约束和项目背景“在这个仓库里应该遵守什么”
Skill提供按需加载的流程和专业知识“遇到某类任务应该怎么做”
Hook / Plugin在生命周期节点运行逻辑“每次工具调用前后自动做什么”
MCP接入外部数据和动作服务“模型如何访问外部系统”
Subagent建立隔离的执行上下文“把一段工作交给专门角色”

这张表比功能清单更重要,因为它对应不同的信任和状态边界。Skill 通常是指令,MCP 是外部能力,hook 是控制流,subagent 是新的 session。把它们都叫 plugin 会导致权限和生命周期设计混乱。

多 Agent 的关键也不是同时启动很多模型,而是明确:父任务如何委派、子任务能看到什么、子 Agent 的工具权限如何收缩、结果如何回传、失败如何重试。官方材料提到 subagent 可嵌套,但越深的树越需要预算、并发、取消和结果合并策略。

07 持久化、恢复和成本:让 Agent 从一次回答变成可继续的工作

状态管理

会话存储至少需要记录用户消息、assistant 流、工具调用、工具结果、权限决定、压缩事件、subagent 关系和 usage。采用 append-oriented transcript 的好处是恢复简单、审计清楚,代价是需要索引、压缩和大输出管理。

恢复时不应只读取“最后一条文本”,而要重建:

session identity
  + transcript facts
  + current branch
  + pending tool / subagent state
  + permission and model configuration
  = resumable execution state

成本追踪同样应是事件层能力。输入 token、缓存命中、输出 token、工具耗时、模型重试和 subagent 成本必须能按 turn 聚合,否则用户无法解释一次任务为什么变贵,也无法为外层 goal 设置预算。

08 从源码阅读到工程复用

建议按这个顺序读:入口与 setup -> query/Agent loop -> Tool/permission -> context/history -> hooks/plugins -> MCP -> subagent -> UI 与 SDK。先建立一条请求生命周期,再回头读单个工具,效率远高于从目录逐个点开。

从 Claude Code 可以抽象出五条可复用原则:

  1. 模型输出是意图,不是授权。
  2. 工具结果和权限决定都必须成为持久化事实。
  3. 上下文压缩应保留“可继续执行的状态”,而非只保留摘要。
  4. 扩展机制必须区分指令、能力、控制流和隔离会话。
  5. 多 Agent 的价值来自边界和验证,不来自数量。

09 最后判断

Claude Code 最值得研究的地方,不是某个神奇 prompt,而是它把一个模型循环逐步工程化:有入口治理,有工具策略,有可观察事件,有可恢复会话,有按需上下文,也有可组合的扩展面。它的复杂度确实比一个 CLI wrapper 高,但这种复杂度正是长期运行、可审计和可嵌入的代价。

如果你要自己实现一个 coding agent,最先复制的应该不是 UI,而是这条边界:模型决定下一步,harness 决定这一步能否发生,事件系统决定这一步能否被解释和恢复。

参考资料