Claude Code 内部按”页面层、核心层、安全/行动层、状态层、后端层”五层拆分,每层只关心自己的职责,向上提供接口、向下委托工作。这种分层并不是教科书式的过设计,而是 50 万行 TypeScript 代码维持可维护性的现实选择。下文把每一层的职责、关键文件、解决的工程问题拆开讲。
一、五层架构的职责对照
| 层级 | 核心模块 | 解决的关键工程问题 |
|---|---|---|
| 页面层 | src/screens/、src/components/、src/entrypoints/ |
多种入口(交互 CLI、无头 CLI、IDE、SDK、桌面)复用同一内核 |
| 核心层 | queryLoop() 异步生成器 + 五步压缩流水线 |
模型调用、工具分发、上下文压力的迭代节拍 |
| 安全/行动层 | 权限系统、Hook 流水线、工具池、沙箱、子代理 | 任何工具调用都先过安全闸,避免”模型即执行” |
| 状态层 | 上下文装配、运行时状态、JSONL 持久化、CLAUDE.md |
让会话可恢复、可回放、可分叉 |
| 后端层 | Shell 沙箱、远程执行、MCP 多传输 | 把”工具能跑到哪”和”工具是什么”解耦 |
二、页面层:入口多、渲染统一
页面层负责”用户怎么接触 Claude Code”。交互式 CLI、无头 CLI(claude -p)、Agent SDK、IDE 扩展、桌面应用、浏览器入口都从这里出。UI 主体用 React + Ink 写——把 React 的组件化搬到终端,配合 Chalk 做颜色,得以让权限弹窗、进度条、流式输出统一响应式更新。
解决的实际问题是”多入口统一内核”。如果每种入口都自带一套交互逻辑,几万行重复代码跑不掉;抽到页面层之后,所有入口最终都进入同一个 query() 路径,差异化只剩”渲染什么”和”如何把用户输入喂进去”。
三、核心层:节拍器与压缩流水线
核心层只做两件事:驱动模型调用,以及在每次调用前压一次上下文。
驱动调用的实现是 queryLoop() 异步生成器。它消费状态层装配好的上下文,把模型输出提交给安全/行动层,工具结果以 tool_result 形式回到循环里。交互式 CLI 和无头 CLI 共享同一条 query() 路径,这才能保证”交互里能跑的事,脚本里也能跑”。
压缩流水线在每次模型调用前跑,按五步顺序缩减:预算缩减 → 截段(Snip)→ 微压缩(Microcompact)→ 上下文塌缩(Context Collapse)→ 自动压缩(Auto-Compact)。它的存在不是因为模型有”洁癖”,而是因为上下文窗口是硬约束、且压缩本身不可关闭——把压缩做成流水线,比让模型临时挤窗口更稳。
四、安全/行动层:默认拒绝的多层防御
安全/行动层是 Claude Code 区别于普通聊天客户端的关键。模型发出的每一次工具调用,先经过权限规则评估,再经过 Hook 流水线,再经工具自身的输入校验,最后才进入沙箱执行。任何一个环节拒绝,循环就拿到拒绝原因回灌给模型,让它重选动作。
// 权限规则求值的简化示意(概念代码)
function evaluate(toolCall, rules) {
for (const rule of rules) {
if (matches(rule, toolCall)) {
if (rule.action === "deny") return { decision: "deny", rule };
if (rule.action === "ask") return { decision: "ask", rule };
if (rule.action === "allow") return { decision: "allow", rule };
}
}
return { decision: "ask" }; // 未识别动作升级给人
}
解决的实际问题有三个:
- “模型即执行”风险。工具调用从根上就不直接落到环境,必须经一道闸;
- 沙箱边界。Shell 命令可以强制走沙箱后端,避免在主环境里裸跑;
- 子代理隔离。Agent 工具派生的子代理在独立窗口里跑,防止主会话被一次性噪声打满。
五、状态层:可回放、可分叉、可恢复
状态层把”这次会话到底发生过什么”完整记录。sessionStorage.ts 维护追加式 JSONL 转录,history.ts 维护全局提示历史,子代理另开 sidechain 文件互不污染。CLAUDE.md 与长期记忆按文件加载,跨会话延续但不进实时窗口。
这套机制让”昨天那次调试到底读没读过 auth.ts“变成可查的事实,也让 /rewind 回到任意消息、/resume 续上挂掉的会话成为可能。配合”可恢复 + 可分叉”,Claude Code 才有底气把无人值守的 8 小时长任务列为可支持的形态。
六、后端层:把”工具能跑哪”解耦
后端层统一管理 Shell、文件系统、Web 抓取、MCP 多传输(stdio / SSE / HTTP)以及远程执行环境。工具定义写在 tools/ 目录里通过 assembleToolPool() 组装,而”这些工具跑在本地机器、容器、还是远端服务器”由后端层决定。
这样做的工程价值是”工具描述和执行环境解耦”——同一份工具实现,可以跑在开发机、CI 容器、企业远程沙箱里,只需切换后端配置,不用改工具代码。
七、各层之间的协作节拍
以”修一个 bug”为例,看看一次循环里五层怎么联动:
- 页面层把用户输入喂进
query(); - 核心层组装上下文(含
CLAUDE.md、历史、状态层产物),跑一次压缩; - 模型给出动作(如”读取
payment.ts“),动作送到安全/行动层; - 安全/行动层先按规则评估(读取通常在默认允许里),工具读取文件,把结果以
tool_result回到核心层; - 状态层把这一轮写入 JSONL;
- 回到第 2 步,循环直到模型给出最终回复或被用户中断。
八、五层分解的工程价值
- 复用:交互 CLI、SDK、IDE 共享同一内核,少写一遍循环;
- 可测试:每层都可以独立 mock,权限评估、压缩策略、状态持久化都能离线验证;
- 可演进:在某层加新能力(如换沙箱实现、新增 Hook)不会波及其它层;
- 可治理:审计/合规要看的是状态层 JSONL;性能调优要看核心层压缩与状态层装配;安全审计则盯紧安全/行动层。
常见问题(FAQ)
Q1:五层划分和”七个高层组件”是什么关系?
组件是按”功能角色”切(用户、接口、循环、权限、工具、状态、执行环境),五层是按”工程位置”切,两者交叉映射。
Q2:QueryEngine 是核心引擎吗?
严格说不是,它是无头 / SDK 路径的会话包装类,共享代码路径是 query() 与内部 queryLoop()。
Q3:新增一个工具要改几层?
一般只需在 tools/ 加实现、在 tools.ts 注册;权限规则、Hook、沙箱边界在配置层声明,工具本体与具体层解耦。