Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

README.md

00 — 全局架构概览

先看大图景:Claude Code 是“模型 + 本地编排层 + 安全约束”的组合体

本章适合谁

如果你刚接触这个项目,或者你已经打开了很多源码文件但不知道它们彼此是什么关系,先读这一章。

这章的目标只有一个:让你先建立 Claude Code 的整体心智模型,再去看后面的 Prompt、工具、权限和上下文细节。

注:本章当前仍属于 Wave 1 draft。主链和关键锚点已经比较稳定,但 evidence 与段落映射还在继续补细。

先说结论

如果要用一句话概括 Claude Code 的架构,可以这样理解:

一个持续运行的 agent loop,外面包着 prompt 组装、工具系统、权限系统、上下文管理和扩展层。

但这句话容易让人误解。很多人会以为"核心 loop 简单,所以整个系统也简单"——恰恰相反。

真正的情况是:

核心循环确实可以保持直接(调模型 → 执行工具 → 回传结果 → 继续),但正是因为它保持简单,外围的工程化设施才必须足够厚实。这些外围层不是"可有可无的包装",而是让这个简单循环能在真实环境里稳定运行的必要条件。

打个比方:一台汽车的核心是发动机,但如果没有刹车、方向盘、安全气囊、冷却系统,这台车就只是一个会爆炸的铁盒子。Claude Code 的设计哲学也是如此——让核心保持可理解,让约束在外围层生效。

如果你是新手,先分清 3 个角色

1. 模型

模型负责理解需求、决定下一步做什么、生成文字或工具调用请求。

2. Claude Code

Claude Code 负责把模型放进一个可控的本地运行环境里。它会准备 prompt、提供工具、执行权限检查、管理上下文、渲染终端 UI。

3. 你的电脑

文件系统、Shell、Git、MCP 服务器、IDE、网络等能力都在本地环境里。模型本身不能直接碰这些能力,必须通过 Claude Code 暴露出来的工具间接访问。

最小心智模型

用户输入
  ↓
Claude Code 收集上下文
  ↓
Claude Code 组装 prompt 和工具列表
  ↓
模型返回:
  - 直接文字
  - 或工具调用请求
  ↓
Claude Code 判断能不能执行该工具
  ↓
执行工具,把结果回送给模型
  ↓
重复,直到模型返回最终文本

所以它不是“模型一次性回答”,而是“模型和本地运行时之间的多轮协作”。

系统分层

终端 UI 层
  React + Ink
  负责输入、输出、流式渲染、权限弹窗、状态展示

CLI 与启动层
  src/main.tsx
  负责参数解析、初始化配置、装配运行时

对话编排层
  src/QueryEngine.ts
  负责一次会话中每一轮请求的状态管理和参数准备

执行循环层
  src/query.ts
  负责真正的模型调用、工具执行、结果回传循环

基础能力层
  Prompt、Tools、Permissions、Context、Compaction、MCP

集成服务层
  API、OAuth、LSP、Analytics、Remote、Plugins、Skills

本章最重要的源码入口

文件 作用 为什么值得先看
src/main.tsx 程序入口与启动装配 看懂 CLI 从哪开始把系统拼起来
src/QueryEngine.ts 一轮会话请求的组织者 看懂一次 turn 是怎么被准备出来的
src/query.ts 真正的 agent loop 看懂“模型 -> 工具 -> 模型”的循环
src/constants/prompts.ts System Prompt 组装 看懂模型为什么会被约束成现在这样
src/tools.ts 工具注册表 看懂 Claude Code 给模型开放了哪些能力
src/utils/permissions/* 权限与安全控制 看懂为什么它不会直接乱执行
src/services/compact/* 上下文压缩 看懂长对话为什么还能继续
src/services/mcp/* MCP 集成 看懂外部能力如何接进来

一次完整请求是怎么流动的

很多人第一次看 Agent 系统时,会以为流程是这样的:

用户输入 → 模型思考 → 返回结果 → 结束

但 Claude Code 的真实流程要复杂得多,也有趣得多。让我们跟随一次真实请求,看看它到底经历了什么。

第 1 步:启动阶段——不是”等输入”,而是”提前准备”

当你第一次启动 Claude Code 时,src/main.tsx 做的不是”等你输入后再慢慢想”,而是立刻开始一系列并行准备工作。

为什么要这样设计?

因为很多依赖项的初始化非常耗时——读取配置文件、连接 MCP 服务器、初始化权限上下文、加载工具定义。如果等到用户输入后再做这些事,每次响应都会有明显延迟。

所以 Claude Code 的策略是:在用户还在思考要问什么的时候,系统已经把后续需要的东西准备好了

具体来说,启动阶段会做这些事:

  • 并行预热关键 I/O:MDM、keychain 这类系统级依赖会被提前触发,避免后续阻塞
  • 裁定权限模式:不是等到真正执行工具时才临时决定,而是在启动时就根据 CLI 参数、配置文件、危险标志位归一化出最终的 permission mode
  • 构建工具池:内置工具、Feature Flag 控制的实验性工具、MCP 工具,都在这个阶段完成注册
  • 初始化外围能力:UI 渲染、Telemetry、LSP、Analytics 等模块也会在这时装配好

这不是过度优化,而是产品级 CLI 的必要准备。用户感知到的”启动即可用”,背后是这套并行预热机制在支撑。

第 2 步:QueryEngine 准备本轮请求——不是”转发输入”,而是”装配上下文”

当你输入一条消息后,系统不会直接把这句话扔给模型。src/QueryEngine.ts 会先做一轮精心的准备工作。

为什么需要这一层?

因为模型需要的不只是”用户说了什么”,还需要知道:

  • 现在应该遵守什么规则(System Prompt)
  • 当前项目有什么特殊要求(userContext)
  • 运行环境是什么状态(systemContext)
  • 有哪些工具可以调用(ToolUseContext)
  • 之前的对话历史是什么

这些信息不是”顺手拼一下”就行的,而是需要明确的分层和优先级。

QueryEngine 做的关键决策:

  1. 确定本轮模型:用户显式指定的模型 > 会话级模型 > 默认模型
  2. 装配三类上下文:并发获取 systemPromptuserContextsystemContext,而不是串行等待
  3. 包装权限检查:把 canUseTool 包一层,变成 wrappedCanUseTool,不只是判断允许/拒绝,还要记录哪些工具被拒绝了、参数是什么
  4. 维护会话状态:消息历史、文件状态缓存、token 用量、权限拒绝记录,这些都是会话级的持久状态

这一层的存在,让后续的核心循环可以专注于”调模型、执行工具、回传结果”,而不用操心”上下文从哪来、权限怎么判、状态怎么存”。

第 3 步:query() 进入真正的 agent loop——这才是系统的心脏

src/query.ts 是整个系统真正意义上的核心。但这里有一个很容易读错的细节:

query() 本身不是那条长期运转的循环,queryLoop() 才是。

这不是命名细节,而是理解整套系统的关键。query() 更像外层包装,负责暴露生成器接口、处理中断、管理流式输出。真正反复推进的是 queryLoop() 里的 while (true)

循环的核心逻辑:

1. 整理消息
   不是把内存里的所有消息原样发出去
   而是先做上下文裁切、压缩视图整理、API 协议归一化

2. 调用模型
   流式接收 assistant 输出
   不是等整条响应结束才处理,而是边收边解析

3. 识别 tool_use
   不是靠 API 的 stop_reason,而是直接扫描 assistant content block
   这样更具体,也更稳定

4. 执行工具
   找到对应工具定义 → 检查权限 → 真正执行 → 生成 tool_result
   这不是隐式发生的,而是本地显式重建成下一轮的 user-side message

5. 回流结果
   把 tool_result 追加回消息历史
   再次调用模型,让它基于新信息继续决策

6. 判断继续/停止/压缩
   不是简单的二叉判断,而是多层关卡串起来的结果
   可能继续、可能停止、也可能触发上下文压缩后继续

为什么这套 loop 很关键?

因为 Claude Code 的绝大多数聪明行为,都不是 CLI 自己规划出来的,而是模型在每一轮拿到新上下文后临时做的决定。

所以这套 loop 的首要任务不是”替模型思考”,而是:

  • 准确传递上下文
  • 正确执行工具
  • 稳定记录状态
  • 在每一轮之间保持一致性

第 4 步:工具、权限、上下文在循环中不断参与——三道护栏

这一层你可以把它理解成三道护栏,它们不是”循环结束后才检查”,而是在循环的每一步都在发挥作用:

第一道护栏:工具系统

决定”能做什么”。模型看到的不是”电脑上所有能力”,而是一组经过精心设计的工具接口。每个工具都有:

  • 清晰的描述(告诉模型什么时候该用它)
  • 参数约束(限制输入形状)
  • 权限语义(标记这是读操作还是写操作)

第二道护栏:权限系统

决定”现在允不允许做”。不是简单的 allow/deny,而是多层判定:

  • 先看当前权限模式(plan/default/auto)
  • 再看规则(deny/ask/allow)
  • 再分析参数(路径是否安全、命令是否危险)
  • 最后才决定是自动允许、提示确认,还是直接拒绝

第三道护栏:上下文系统

决定”模型这次能看到什么”。不是”把所有历史都发过去”,而是:

  • 裁掉 compact 边界之前不需要的历史
  • 处理过长的 tool result
  • 结合 snip / microcompact / context collapse 等机制
  • 在窗口接近上限前主动压缩

这三道护栏不是”循环外的附加模块”,而是循环内部持续参与的运行时机制。

为什么核心 loop 很简单,但代码还是很多

表面上看,主流程像这样:

while (模型还在调工具) {
  执行工具
  把结果发回模型
}

但产品级 CLI 不能只停在这一步,因为现实里还要解决:

  • 工具执行前是否有权限
  • 命令是否危险
  • 文件路径是否合法
  • 上下文是否快溢出
  • Prompt 是否会打爆缓存
  • MCP 服务器会不会动态变化
  • 多 Agent 怎么隔离上下文
  • UI 如何实时显示每一步状态

所以 Claude Code 不是“算法复杂”,而是“工程边界条件复杂”。

再往细看一步,至少还有这些真正会改变主链行为的实现细节:

  • tool_use 的识别不只靠 API 的 stop_reason
  • 工具池不是简单等于 getTools()
  • permission mode 是启动时先裁定,再进入后续执行链
  • auto compact 阈值会先预留 summary 输出空间,再决定何时主动压缩

Claude Code 的设计哲学

1. 尽量把决策交给模型

Claude Code 没有一个很重的外部任务规划器去替模型做决策。它更像是在给模型搭一个受控工作台。

2. 尽量把约束交给工程层

模型负责决策,不代表它拥有无限权力。Prompt、权限、沙箱、路径验证、模式切换,这些都由工程层硬性约束。

3. 让复杂集中在外围,而不是核心循环里

query.ts 的主循环仍然保持相对直接,这是一个非常重要的取舍。它让系统更容易调试,也更容易扩展。

4. 允许“运行时治理”反过来塑造架构

这点在 Prompt Cache、上下文压缩、MCP 动态信息和工具池构建里都很明显。
Claude Code 不是先把结构写死,再让优化来将就;它会反过来调整信息放置位置,让系统更稳定。

建议的源码阅读顺序

如果你已经准备打开源码,建议按这个顺序:

  1. src/main.tsx
  2. src/QueryEngine.ts
  3. src/query.ts
  4. src/constants/prompts.ts
  5. src/tools.ts
  6. src/utils/permissions/PermissionMode.ts
  7. src/services/compact/autoCompact.ts
  8. src/services/mcp/client.ts

不要一上来就钻进 utils/permissionsservices/mcp 的深层文件,那样很容易迷路。

本章小结

先把 Claude Code 看成一层“本地编排系统”,后面的章节就容易理解很多:

  • Prompt 决定模型怎么想
  • Tool 决定模型能做什么
  • Permission 决定工具是否能执行
  • Context 决定模型这次看到了什么
  • MCP 决定系统可以扩展到什么边界

而且从这轮补进去的源码证据看,更准确的整体印象应该是:

  • 启动阶段会先把 permission mode、权限上下文和部分外部依赖预热起来
  • 主循环真正的本体是 queryLoop(),不是外层包装函数
  • 工具、权限、上下文、压缩都不是外挂模块,而是主链中持续参与的运行时机制

下一步