Claude Code 架构设计深度剖析

Claude Code 架构设计深度剖析:从启动到多 Agent 扩展层

该文章由AI生成,仅供个人学习使用。
写在前面:这篇文章只做一件事——把 Claude Code 拆成几个真正决定系统质量的核心模块,逐层讲透其关键设计决策。不聊"怎么用",只聊"为什么这样设计"。


核心问题:为什么有些 Agent 一复杂就散架?

这两年大家都在写 Agent,但有一个尴尬的共识:Demo 阶段势如破竹,一旦加到三五个工具、几种运行模式、几类权限规则,系统就开始肉眼可见地变形——主循环越来越脏,工具一多就互相污染,后台任务和前台会话互相打架,扩展一接进来就满地特判。

模型能力固然重要,但真正决定一个 Agent 能不能长期活下去的,往往不是模型本身,而是围绕模型搭建起来的运行时(Runtime)

Claude Code 值得深入拆解,不是因为它"功能很多",而是因为它已经在真正承接复杂度。它的价值不止于竞品分析,更是一个现成的架构参照物:哪些复杂度应该前置,哪些应该制度化,哪些必须通过架构收敛——而不能继续靠 prompt 和人肉兜着

换句话说,这篇文章真正想回答的不是"Claude Code 有哪些功能",而是——"为什么有些 Agent 系统一复杂就散架,而它没有"。

总架构概览

在深入各层之前,先建立一个全局视角。Claude Code 的整体架构可以概括为 7 层运行时,每一层承接一种特定的复杂度:

graph TB subgraph "Claude Code 七层架构" L1["🚀 启动层<br/>入口分流 · 模式判断 · 边界定型"] L2["🖥️ REPL / UI 编排层<br/>能力面汇总 · 事件流归并 · 控制面拼装"] L3["🔄 Query Loop 层<br/>连续推理 · 上下文治理 · 失败恢复"] L4["🔧 Tool Runtime 层<br/>受控执行 · 并发调度 · 结果回灌"] L5["🔐 Permission 层<br/>规则判定 · 自动决策 · 执行隔离"] L6["📋 Task Runtime 层<br/>统一任务抽象 · 多Agent · 后台执行"] L7["🔌 扩展层<br/>MCP · Skills · Plugins · 能力收敛"] end L1 --> L2 L2 --> L3 L3 --> L4 L4 --> L5 L7 -.->|"能力注入"| L3 L7 -.->|"能力注入"| L4 L6 -.->|"任务调度"| L3 style L1 fill:#e8f5e9,stroke:#4caf50 style L2 fill:#e3f2fd,stroke:#2196f3 style L3 fill:#fff3e0,stroke:#ff9800 style L4 fill:#fce4ec,stroke:#e91e63 style L5 fill:#f3e5f5,stroke:#9c27b0 style L6 fill:#e0f2f1,stroke:#009688 style L7 fill:#fff8e1,stroke:#ffc107

接下来,我们逐层拆解。


1. 入口与启动链路:别急着拉起全世界

核心矛盾

一个成熟的 Agent 往往要同时支持本地交互、headless、SDK、remote、后台 session、会话恢复等多种运行模式。如果启动层不先把模式、边界、权限和上下文装配清楚,后面每个宿主都会偷偷长出自己的运行语义,最终系统会裂成几套互不兼容的分支。

很多系统的入口是一个越来越胖的 main,恨不得一上来就把全世界都拉起来。Claude Code 在这里反而很克制——它先做了一次非常关键的判断:先分流,再装配,最后才进入会话

Claude Code 的解法

从源码看,启动过程大致分为三段:

flowchart LR subgraph S1["① 入口分流"] A1["参数解析"] A2["模式判断"] A3["Fast Path 短路"] end subgraph S2["② 进程级初始化"] B1["配置加载"] B2["Telemetry"] B3["远程设置"] B4["清理回调"] end subgraph S3["③ 会话级准备"] C1["工作目录 / 会话身份"] C2["工具面 / 权限模式"] C3["扩展能力 / 系统约束"] C4["宿主选择"] end S1 -->|"确定运行模式"| S2 S2 -->|"环境就绪"| S3 S3 -->|"边界定型"| D["进入 REPL / Headless"] style S1 fill:#e8f5e9,stroke:#4caf50 style S2 fill:#e3f2fd,stroke:#2196f3 style S3 fill:#fff3e0,stroke:#ff9800 style D fill:#f3e5f5,stroke:#9c27b0

第一段:入口分流。系统不急着把整个运行时拉起来,而是先判断这次到底是什么启动——本地交互、无界面运行、远程接入、后台会话管理,还是极简 fast path。很多路径都用动态加载,说明 Claude Code 从一开始就在控制启动成本,而不是先把全世界加载进来再说。

第二段:进程级初始化。这一层处理的是运行环境:配置、telemetry、远程设置、清理回调等全局设施。关键在于它刻意不碰当前会话语义——回答的是"进程能不能跑",而非"这一轮 Agent 该怎么跑"。

第三段:会话级准备。到这里才开始确定工作目录、会话身份、工具面、权限模式、扩展能力、系统约束、恢复方式等信息,最后决定由交互式宿主还是无界面引擎来承载。

两个关键的设计细节

这中间有一个容易被忽略、但对架构至关重要的细节:Claude Code 把"进程状态"和"交互状态"分开了

cwdprojectRootsessionIdtelemetrytoken/cost 计数这类基础设施状态,沉在 bootstrap/state 一类全局状态里;而 tasksMCP clientsplugin 状态、permission context、界面选择状态这类控制面状态,才进入 AppState

这个分层让系统不会把 React state 误当成整套 runtime 的唯一真相,也不会把所有状态都做成不可控的全局变量。

为什么这样好

直接收益是:无界面运行、交互式运行、远程运行、后台运行可以共享同一套核心 runtime,而不是各自长一套逻辑。权限、工具、系统约束、扩展能力这些影响执行边界的要素,都能在第一轮请求前定型,后面的主循环不需要一边跑一边猜。

更深层的好处是:系统复杂度被前置了。很多项目把模式判断、权限边界、宿主差异拖到运行中解决,主循环因此越来越脏。Claude Code 把这类复杂度尽量压到启动层,运行时主链路反而更纯净。

我们怎么学

最值得吸收的不是"复杂入口",而是次序感——凡是会影响执行边界的东西,尽量在第一轮请求前定型。

如果还在单一交互模式阶段,不必照抄这么厚的入口层,但至少要先把三件事分开:启动模式、会话制度、宿主承载。如果已经同时有命令行、接口调用、后台任务、远程运行等多种方式,这一层就不能再含糊了——否则很快就会长成几套彼此不兼容的 Agent。

一个实用的起点:先把"启动时必须定型的边界"列成一张清单。


2. REPL / UI Orchestration:UI 不是传话筒

核心矛盾

很多团队把聊天 UI 理解成"显示消息的壳"——大模型吐什么,前端就渲染什么。Claude Code 明显不是这样。它的 REPL 本质上更像一个运行时控制台

一旦 Agent 不只是聊天,而开始执行工具、弹权限、跑后台任务、动态接入扩展,UI 面对的就不再是"如何显示回复",而是"如何把一个复杂 runtime 变成用户可操作、可理解、可干预的系统"。这一层做不好,所有关键状态都会变成黑箱。

Claude Code 的解法

REPL 的启动层很薄,说明它不是启动中心,而是一个被装配好的宿主。真正值得注意的是,REPL 把输入、消息流、权限确认、任务、MCP 连接、插件状态、远程状态、后台 session 等全部编排到同一个控制面里。

这意味着 Claude Code 的 UI 不是"模型回复展示器",而是 runtime 的 orchestrator。一次用户输入进入 REPL 后,不是直接丢给模型,而是先经历一条完整的预处理链路:

flowchart TB Input["👤 用户输入"] --> Check{"本地命令?<br/>快捷指令?"} Check -->|"是"| Local["直接执行,不进主循环"] Check -->|"否"| Ctx["组装本轮执行上下文"] Ctx --> Merge["合并能力面<br/>本地 Tools + MCP Tools<br/>+ Plugin Commands + 动态 Skills"] Merge --> Env["准备系统约束 / 用户环境"] Env --> Query["进入 query(...)"] Query --> Stream["流式事件输出"] Stream --> Events{"事件类型分发"} Events --> E1["💬 Assistant Message"] Events --> E2["🔧 Tool Progress"] Events --> E3["🔐 Permission Request"] Events --> E4["📋 Task Notification"] Events --> E5["⚠️ API Error"] E1 & E2 & E3 & E4 & E5 --> View["归并为统一会话视图"] style Input fill:#e3f2fd,stroke:#2196f3 style Query fill:#fff3e0,stroke:#ff9800 style View fill:#e8f5e9,stroke:#4caf50

也就是说,REPL 先把"这一轮在什么制度下运行"准备好,再把控制权交给推理循环

能力面汇总 + 事件流归并

更具体地说,REPL 真正负责的是"当前能力面"的汇总——把本地 tools、MCP tools、plugin commands、动态 skills、任务状态、权限确认队列、MCP 连接状态、remote session 信息汇在一起,在用户提交输入的那个瞬间,生成一个完整的 turn-scoped 执行上下文。

这也是 REPL 显得很"大"的原因:它并不是一个 view component,而是在做控制面拼装

另一层很重要的设计是,REPL 消费的不是纯文本,而是一串带语义的事件流。assistant message、tool progress、compact boundary、pending permission、task notification、API error——这些事件都会在这里重新归并成用户能理解的会话视图。

换句话说,REPL 既是 query 的入口,也是整套运行时事件的落点。

为什么这样好

最大的好处是"可控"。用户不是只看到一句模型回复,而是能看到系统正在执行什么、为什么停下来、当前有哪些能力、后台有哪些任务。对于需要权限确认、长时执行、工具调用的 Agent 来说,这种可控感往往比多一点模型智商更重要。

还有一个容易被忽略的收益:UI 不再只是消费文本,而是在消费统一事件协议。query、permission、tool runtime、task system 都可以通过结构化事件与 REPL 协作,而不是各自偷偷改 UI 状态。

我们怎么学

如果产品还停留在单轮问答,聊天 UI 足够。但只要系统进入了工具执行、权限确认、后台协作阶段,UI 就必须承担显式控制面的职责

最容易学错的地方,是把"控制面"理解成堆更多面板。真正该学的是显式化运行时关键状态:当前能力面、当前任务、当前权限状态、当前失败与恢复状态。不是界面越花越强,而是用户越能理解系统在做什么,越容易信任并驾驭它。

一个好的起点:先把后台任务和权限状态显式展示出来。


3. Query Loop / QueryEngine:把单轮对话升级成运行时

核心矛盾

如果说前两层是在搭台子,那么 Query Loop 才是真正决定一个 Agent 像不像成熟 runtime 的分水岭。很多团队的 Agent 到了这里才开始真正分出高下。

只要 Agent 开始连续运行,系统就会立刻碰到几个硬问题:长上下文劣化、工具调用打断推理、模型输出截断、失败后是否恢复、工具结果如何回灌下一轮。这些问题如果还被当成"模型调用细节",系统就会在复杂场景里迅速失稳。

Claude Code 的解法

先分清两个对象:

  • 无界面会话引擎:不是主循环本身,更像会话外壳
  • Query Loop 本体:真正的 agent turn 内核

无界面会话引擎的职责很有代表性:先拉系统上下文,处理输入和命令,把用户消息写进会话记录,暴露当前能力面,然后才把处理后的消息流交给主循环。这说明 Claude Code 不把 query 理解成"收到输入就打一次模型",而是理解成"会话侧状态都准备好之后的最后一步"。

真正的核心在 Query Loop 本体。从状态组织方式就能看出来,这里维护的不是一次性请求参数,而是一组跨迭代的运行时状态:消息集、执行上下文、上下文压缩状态、输出恢复计数、轮数预算、任务预算等。

如果把源码里的状态骨架压到最小,会更容易看出它为什么已经是 runtime,而不是"模型调用封装":

state = {
  messages,
  toolUseContext,
  maxOutputTokensOverride,
  autoCompactTracking,
  maxOutputTokensRecoveryCount,
  hasAttemptedReactiveCompact,
  turnCount,
  pendingToolUseSummary,
  transition,
}

这段骨架非常关键。一个普通 orchestrator 不会长期维护 autoCompactTrackingmaxOutputTokensRecoveryCountpendingToolUseSummary 这类对象;一旦这些状态都进入主循环,说明系统已经承认:一次 agent turn 会被压缩、恢复、工具回灌、预算和中断反复改写

主循环的真实面貌

下图展示了 Query Loop 每一轮迭代的完整流程——从预取到流式推理,再到工具执行和结果回灌:

flowchart TB Start(["🔄 进入 Query Loop"]) --> Prefetch["预取 Memory & Skills<br/>(异步,利用空隙)"] Prefetch --> Budget["应用上下文预算<br/>applyBudget(messages)"] Budget --> Compact["上下文治理<br/>snip · microcompact · collapse · autocompact"] Compact --> Stream["🤖 流式模型推理<br/>streamModel(messages)"] Stream --> HasTool{"模型输出<br/>包含工具调用?"} HasTool -->|"否"| Finish(["✅ 结束本轮 Turn"]) HasTool -->|"是"| ToolExec["🔧 执行工具调用<br/>runToolUse(toolUse, context)"] ToolExec --> WriteBack["结果回灌<br/>writeBack(messages, assistant, toolResult)"] WriteBack --> CheckBudget{"轮数/预算<br/>是否耗尽?"} CheckBudget -->|"否"| Prefetch CheckBudget -->|"是"| Finish Stream -.->|"输出截断"| Recovery["失败恢复<br/>reactive compact<br/>max output recovery<br/>fallback model"] Recovery --> Compact style Start fill:#fff3e0,stroke:#ff9800 style Finish fill:#e8f5e9,stroke:#4caf50 style Stream fill:#e3f2fd,stroke:#2196f3 style ToolExec fill:#fce4ec,stroke:#e91e63 style Recovery fill:#ffebee,stroke:#f44336

从运行顺序看,Claude Code 对 query 的理解非常"系统化":先处理记忆预取、能力发现、上下文预算、各种压缩与折叠,再进入流式采样;一旦模型发出工具调用,runtime 就接管执行,把结果整理成结构化反馈、附加材料和后续提示,再重新送回下一轮推理。

如果把主循环压成最小骨架:

while (true) {
  prefetchMemoryAndSkills()
  messagesForQuery = applyBudget(messages)
  messagesForQuery = snipAndCompact(messagesForQuery)

  assistant = streamModel(messagesForQuery)
  if (!assistant.hasToolUse) return finishTurn(assistant)

  toolResult = runToolUse(assistant.toolUse, toolUseContext)
  state.messages = writeBack(messages, assistant, toolResult)
}

这和很多团队熟悉的"拿历史消息调一次模型,拿到结果就结束"完全不是一个层级。Claude Code 真正高明的点不是 while loop 本身,而是它把 prefetch、budget、compact、tool result write-back 这些原来容易散落在各处的逻辑,全部拉回了主循环正中央。

四个关键认知升级

Claude Code 在这一层最强的地方,是它把"一次请求"理解成一段运行,而不是一问一答。于是很多本该是运行时问题的东西,都被提升成了正式的 runtime 机制:

问题域 传统做法 Claude Code 的做法
长上下文治理 靠提示词硬扛 snip · microcompact · collapse · autocompact
失败恢复 报错即停 reactive compact · max output recovery · fallback model
工具结果 视为终点 视为下一轮输入
Memory / Skill 发现 同步阻塞 异步预取,藏在流式和工具执行的空隙里

还有一个很有工程味的细节:Claude Code 会尽量把耗时工作藏在空隙里。memory prefetch 和 skill discovery 不是每轮都同步阻塞,而是尽量叠在流式输出和工具执行期间。这种设计看起来只是 latency 优化,实际上传达的是更成熟的运行时观念——Agent 的体验不只由模型速度决定,还取决于你能不能把未来大概率要用到的信息提前安排好。

我们怎么学

最值得学的不是每一个 compact/recovery 细节,而是判断升级:当系统开始"连续运行"时,query loop 就不该只是模型调用封装,而应该被单独当成一层系统设计。

如果还在单轮问答阶段,不必马上复制完整恢复体系;但一旦进入多工具、多轮续跑、长上下文阶段,就要尽快把上下文治理、失败恢复、工具回灌提升为 runtime 机制。

别学错的地方,是把一堆恢复分支硬塞进业务代码;真正该复制的是"把失败路径也当主路径设计"的态度。团队动作上,可以先把"上下文治理"和"失败恢复"从 prompt 层挪到运行时层。


4. Tool Runtime:把野生工具变成系统调用

核心矛盾

很多 Agent 项目在工具层的默认心智是"给模型挂几个函数"。Claude Code 明显不是——它把工具层做成了一套受控执行协议。不是在"接函数",而是在设计"行动系统"。

工具一旦开始碰文件、命令、网络和副作用,问题就不再是"模型会不会调用函数",而是"这次行动是否合法、能否并发、如何上报进度、失败怎样表达、结果怎样回灌给模型"。这些问题一旦散落在每个工具里,系统很快就会失控。

Claude Code 的解法

Tool 在 Claude Code 里不是一个简单函数,而是一个带完整运行时语义的对象——有 schema、有输入校验、有权限关联、有并发安全声明、有中断语义、有结果回填规则。

真正的工具执行主链路,做的不是"找到函数然后调一下",而是四段式受控执行

flowchart LR subgraph S1["① 解析"] A1["解析真实 Tool"] A2["未知工具兜底"] end subgraph S2["② 校验"] B1["Schema 校验"] B2["输入预处理"] end subgraph S3["③ 授权 & 执行"] C1["Permission 决策"] C2["并发安全判断"] C3["真正执行"] end subgraph S4["④ 结果归一化"] D1["结构化反馈"] D2["进度上报"] D3["附加材料"] end S1 --> S2 --> S3 --> S4 --> E["回灌至下一轮推理"] style S1 fill:#e3f2fd,stroke:#2196f3 style S2 fill:#fff3e0,stroke:#ff9800 style S3 fill:#fce4ec,stroke:#e91e63 style S4 fill:#e8f5e9,stroke:#4caf50 style E fill:#f3e5f5,stroke:#9c27b0

这说明 Claude Code 对工具层的理解很成熟:工具不是模型的外挂函数,而是 runtime 的受治理执行单元

如果把 Tool 抽象的差别落到更微观的骨架上,对比会更直观:

// 很多团队的 Tool 抽象
type Tool = (input: unknown) => Promise<string>

// Claude Code 更接近的
interface Tool {
  name: string
  inputSchema: Schema
  canRunInParallel: boolean
  validate(input): ValidationResult
  execute(input, context): AsyncIterable<ToolEvent>
  toModelResult(output): StructuredResult
}

真正的差别不在 TypeScript 写法,而在系统观。前一种只是"模型能调一个函数",后一种才是"运行时知道这个动作该怎么被约束、观测、并发和回灌"。

两个关键工程判断

这层还有两个容易被低估的点:

第一:并发策略不由模型决定,而由工具语义决定。工具调度层不会简单 Promise.all,而是根据 isConcurrencySafe 区分哪些工具可并发、哪些必须串行——只读工具和有副作用的工具,本来就不应该用同一套并发策略。

第二:流式工具执行必须被认真建模。Claude Code 允许模型还在流式输出时工具就先开始执行,但又通过状态跟踪、结果缓冲、取消管理来保证正确性。它优化的不是"更快一点"这么简单,而是"边生成边行动"时系统如何不乱。

为什么这样好

最大的好处是:工具数量增长时,复杂度不会爆在调用点,而是沉到统一 runtime 里。参数校验、权限、并发、错误归一化、结果回填这些横切关注点,不再被每个工具重新发明一遍。系统因此既更稳定,也更适合团队协作。

流式工具执行则让 Claude Code 把"边生成边行动"做成了工程能力,而非 demo 特效——模型还在说话时,工具已经开始干活,但最终结果仍能按正确顺序、正确语义回到主循环。

我们怎么学

最值得借鉴的不是"工具很多",而是工具有制度

如果现在只有少量只读工具,可以先保持轻量;但一旦工具开始碰文件、命令、网络、副作用,就应该尽快建设统一 Tool Runtime。别照抄过度抽象本身,真正该学的是把横切复杂度沉到公共层,让新增工具不再重复制造新的风险模型。

一个务实的起点:先把"校验、授权、结果格式"三件事统一起来。


5. Permission System:不是弹个框就完事了

核心矛盾

Claude Code 的权限系统值得研究,不是因为它更谨慎,而是因为它更像一条完整的执行链,而不是一个确认框。很多系统把权限做成了 UX 组件,Claude Code 则把它做成了运行时机制

Agent 的权限问题从来不只是"要不要弹个确认框",而是四件事同时存在:逻辑上是否允许、自动化能否消化、用户何时必须参与、即使允许了进程边界到底被限制在哪。把这些问题揉成一个 yes/no,系统不是过度打断用户,就是安全形同虚设。

Claude Code 的解法

Claude Code 把权限拆成了四层,形成一条从规则到隔离的完整决策链:

flowchart TB ToolCall["🔧 工具调用请求"] --> Rules subgraph L1["① 规则层"] Rules["规则匹配<br/>allow / deny / ask"] Reason["保留来源 & 理由"] end subgraph L2["② 运行时判定层"] Classifier["Classifier 自动分类"] Hooks["Hooks 拦截"] Coordinator["Coordinator 协调"] end subgraph L3["③ 交互层"] Ask["用户确认<br/>附带 suggestions & blockedPath"] end subgraph L4["④ 执行隔离层"] Sandbox["Sandbox 边界压实<br/>文件路径 · 网络域名 · 命令范围"] end L1 -->|"待确认"| L2 L1 -->|"直接允许/拒绝"| Decision L2 -->|"自动决策"| Decision L2 -->|"需人工"| L3 L3 --> Decision Decision{"最终决策"} -->|"allow"| L4 Decision -->|"deny"| Denied(["🚫 拒绝执行"]) L4 --> Execute(["✅ 受控执行"]) style L1 fill:#e3f2fd,stroke:#2196f3 style L2 fill:#fff3e0,stroke:#ff9800 style L3 fill:#fce4ec,stroke:#e91e63 style L4 fill:#e8f5e9,stroke:#4caf50

这条链路最重要的地方,是它把两个常被混淆的问题分开了:

  • 逻辑上允不允许(规则层 + 判定层)
  • 进程层面到底能做到什么(执行隔离层)

很多 Agent 系统只做前者,权限像提示框;也有些系统只做后者,沙箱像硬隔离。Claude Code 的成熟之处在于,这两层是打通的。

如果把权限决策对象压成最小骨架:

type PermissionDecision =
  | { behavior: 'allow'; updatedInput?; decisionReason? }
  | {
      behavior: 'ask'
      message: string
      suggestions?: PermissionUpdate[]
      blockedPath?: string
      pendingClassifierCheck?: PendingClassifierCheck
    }
  | { behavior: 'deny'; message: string; decisionReason: string }

很多团队的权限系统只有 boolean,最多再加一个"是否弹窗";Claude Code 则把 decisionReasonsuggestionsblockedPathpendingClassifierCheck 都提升成正式字段。也就是说,权限不再只是"过不过",而是"为什么过、卡在哪、下一步怎么处理、能不能先自动判一轮"

两个现实主义细节

第一,Claude Code 会对 auto mode 做危险能力裁剪——不是默认"自动模式就尽量多放行",而是主动剔除过宽的 Bash、PowerShell、agent wildcard 等规则。

第二,沙箱不是独立附属物,而是 permission 的落地点:文件路径、网络域名、设置目录等真正执行边界,都会在这里被压实。前者解决"逻辑上别放太宽",后者解决"即使放行了,进程也别飞出去"。

为什么这样好

好处不只是更安全,而是更可解释。用户知道为什么被允许、为什么被拒绝、为什么需要确认;系统内部也知道这次决策来自规则、模式、classifier 还是显式同意。只有这样,权限才能成为可调试、可审计、可优化的系统,而不是一堆历史特判。

Claude Code 对 auto mode 的处理也很值得学——它没有把自动化理解成"尽量少问用户",而是理解成"在收紧危险能力后尽量自动"。真正能让用户信任自动化的,不是它敢做更多,而是它在边界内做得更稳。

我们怎么学

最值得学的是把权限设计成可解释的执行链,而不是弹窗机制

不一定每个团队都要立刻上复杂 classifier,但至少应该尽快把"决策、理由、来源"建模出来,并把逻辑授权和执行隔离分开。别把"更安全"理解成"多弹几次框"——真正成熟的权限系统,是让自动化、用户体验和风险控制走同一条链,而不是互相打架。

团队动作上,先别急着优化弹窗,先把权限决策对象化


6. Task / 多 Agent / 后台执行:多 Agent 的核心不是 prompt 分工

核心矛盾

Claude Code 的多 Agent 设计很克制。它不是先搞 manager-agent、worker-agent 那套叙事,而是先定义了统一的任务抽象。这个顺序看起来朴素,其实比大多数"智能体分工图"都更有工程含量。

多 Agent 真正难的地方,从来不是 prompt 怎么分工,而是系统里一旦出现多个可持续执行的执行体,状态怎么管理、进度怎么观察、结果怎么回流、上下文怎么隔离、失败怎么恢复。没有统一执行抽象,多 Agent 只会是一堆黑盒同时跑。

Claude Code 的解法

Claude Code 用 Task 这一统一抽象,表达了很多看起来截然不同的执行体:

graph TB subgraph TaskAbstraction["📋 统一任务抽象 (Task)"] direction TB TA["统一的生命周期<br/>状态 · 进度 · 通知 · 恢复"] end T1["🖥️ 主会话后台化"] --> TaskAbstraction T2["🤖 本地 SubAgent"] --> TaskAbstraction T3["👥 In-process Teammate"] --> TaskAbstraction T4["🌐 Remote Agent"] --> TaskAbstraction TaskAbstraction --> R1["状态管理"] TaskAbstraction --> R2["进度观察"] TaskAbstraction --> R3["结果回流"] TaskAbstraction --> R4["上下文隔离"] TaskAbstraction --> R5["失败恢复"] style TaskAbstraction fill:#e0f2f1,stroke:#009688 style T1 fill:#e3f2fd,stroke:#2196f3 style T2 fill:#fff3e0,stroke:#ff9800 style T3 fill:#fce4ec,stroke:#e91e63 style T4 fill:#f3e5f5,stroke:#9c27b0

这说明它对多 Agent 的理解不是"模型调模型",而是"系统里出现了多个可持续执行的执行体",所以先要有统一任务语义。

本地子 Agent 的实现很能说明问题。Claude Code 不是偷偷新开一轮 query 就算一个子 Agent,而是先把它注册成正式任务,再给它状态、进度、待处理消息、会话记录、前后台语义。换句话说,子 Agent 先是任务对象,才是智能体

如果把本地子 Agent 的任务状态压成最小骨架,会看到它根本不是一个 Promise<string>

type LocalAgentTaskState = {
  agentId: string
  prompt: string
  progress?: AgentProgress
  error?: string
  result?: AgentToolResult
  messages?: Message[]
  isBackgrounded: boolean
  pendingMessages: string[]
  retain: boolean
  diskLoaded: boolean
  evictAfter?: number
}

这几个字段特别能说明 Claude Code 的取舍:

  • pendingMessages:子 Agent 不是一次性调用,而是带邮箱的执行体
  • isBackgrounded:前后台切换是正式语义,不是 hack
  • retain / diskLoaded / evictAfter:系统已经在认真处理"UI 是否还握着它、磁盘记录是否已回灌、什么时候该回收"这些长期运行才会出现的问题

为什么这样好

最大的好处是:多 Agent 不会把系统撕裂。不同执行体共享同一套生命周期、通知模型和回流协议,前台和后台的差别被降成了调度与可见性差异,而不是两套世界观。

Claude Code 在这里真正成熟的地方,是它把最容易被忽略的工程问题先做了:上下文隔离、结果回流、失败恢复、可观察性。这些问题做不好,manager/worker 架构再漂亮也只是 demo。

我们怎么学

如果系统开始出现后台执行、子任务协作、远程执行,就先把统一任务抽象做对,再谈 fancy 的多 Agent 结构。

最容易踩的坑是把多 Agent 理解成 prompt 分工问题。很多系统最后不是死在智能不够,而是死在没有状态、没有恢复、没有结果回流。Claude Code 这一层最值得吸收的,不是"支持更多 agent",而是先把任何可持续执行的事情都纳入统一执行体

团队动作上:先把后台任务、子任务和远程执行统一到一张任务状态表上。


7. MCP / Skills / Plugins 扩展层:外部可以热闹,内部必须收敛

核心矛盾

Claude Code 的扩展层真正厉害的地方,不是支持了很多扩展来源,而是它努力把所有外部能力都收敛进少数内部对象。平台做到后面,拼的往往不是接入得快不快,而是收敛得稳不稳。

Agent 一旦走向平台化,扩展来源一定会变多:外部协议、用户自定义能力、插件市场、内建能力包。真正危险的不是来源多,而是每多一种来源,主系统就多一套能力模型、权限模型和 UI 暴露方式,最后 special case 爆炸。

Claude Code 的解法

先看 MCP。Claude Code 不是把 MCP 原生对象直接塞进系统,而是尽量翻译成已有的运行时模型:MCP prompt → Command,MCP tool → Tool,MCP resource → 资源/资源工具体系,必要时还会注入 auth tool。也就是说,MCP 接入的重点不是"连上服务器",而是"把外部能力吸收到本地运行时模型里"

Skills 也不是简单的 prompt snippet。它的声明信息可以覆盖描述、适用场景、允许使用的工具、模型偏好、推理力度、hooks、执行上下文、代理身份等维度——已经接近一种轻量能力声明,而不只是提示词片段。

如果把 skills 实际解析的结构压成最小骨架:

type SkillDescriptor = {
  description: string
  allowedTools: string[]
  whenToUse?: string
  model?: Model
  effort?: Effort
  hooks?: Hooks
  executionContext?: 'fork'
  agent?: string
}

这段骨架直接说明了一件事:Claude Code 里的 skill 不是"顺手塞一段提示词",而是能约束工具权限、触发条件、执行上下文、模型偏好、推理力度,甚至决定是否 fork 执行。

外部动态,内部稳定

Claude Code 在扩展层上坚持的核心原则是"动态能力面,稳定内部对象":

flowchart LR subgraph External["🌐 外部来源(动态)"] E1["MCP Server<br/>prompt · tool · resource"] E2["Skills<br/>用户 · 项目 · 插件"] E3["Plugins<br/>市场 · 会话级 · 内建"] end subgraph Converge["🔄 能力收敛层"] C1["翻译 / 映射"] C2["解析 / 验证"] C3["加载 / 校验"] end subgraph Internal["🏠 内部对象(稳定)"] I1["Tool"] I2["Command"] I3["SkillDescriptor"] end E1 --> C1 --> I1 & I2 E2 --> C2 --> I3 E3 --> C3 --> I1 & I3 Internal --> Consumers["Query Loop · Permission<br/>Task System · UI"] style External fill:#fff3e0,stroke:#ff9800 style Converge fill:#e3f2fd,stroke:#2196f3 style Internal fill:#e8f5e9,stroke:#4caf50 style Consumers fill:#f3e5f5,stroke:#9c27b0

外部连接可以随时重连、启停、刷新能力列表;插件可以来自市场、会话级目录、内建包;能力单元也可以来自用户、项目、插件和外部来源。外部世界动态而杂乱,但内部始终收敛到少数对象上。这种收敛,才是平台继续长大时不崩的根本。

为什么这样好

好处是平台可以长大而不碎。UI、permission、query loop、task system 都只需要理解少数内部对象,不需要为每种扩展来源重写一套适配逻辑。越到后期,这种内部收敛越像护城河——它决定了系统还能不能继续演进。

另一个重要收益是动态能力面终于可控了。MCP 连接可以变化、plugin 可以启停、skills 可以来自很多地方,但这些变化对主系统来说仍然是可解释、可管理的,因为底层对象没有失控。

我们怎么学

对想做 Agent 平台化的团队来说,这一层最值得学的不是"也去上更多扩展体系",而是先吸收一条更底层的原则:外部世界可以复杂,内部世界必须收敛

最容易学偏的地方是先去堆更多扩展类型,而没有守住内部抽象数量。真正应该先做的是约束内部对象,再决定支持哪些外部来源。只有这样,扩展性才不是系统失控的开始,而是系统增长的方式。

团队动作上:先画出"系统内部只允许存在的那几种能力对象"。


8. 总结与总架构:把复杂度放在对的位置上

核心问题

为什么很多 Agent 项目功能点不少,但一进入真实复杂场景就开始散架?因为复杂度没有被放到正确的位置——边界问题混进主循环,权限问题混进工具调用,多 Agent 问题混进 prompt,扩展问题直接渗透到系统内部。

Claude Code 真正强的不是某个单点功能,而是它对复杂度的安放位置非常清楚:

层级 承接的复杂度 核心问题
启动层 会话边界问题 这次运行是什么模式、什么边界?
REPL 人机协作问题 用户如何理解和干预系统?
Query Loop 连续运行问题 长上下文、失败、工具回灌怎么办?
Tool Runtime 行动协议问题 动作如何被约束、并发、回灌?
Permission 风险治理问题 什么能做、谁来决定、边界在哪?
Task Runtime 长时执行和并发问题 多个执行体如何共存不乱?
扩展层 平台增长问题 外部能力如何不撕裂内部?

三条主干链路

如果把这套设计再压缩一层,会发现 Claude Code 一直在做同一件事:把不稳定、概率化、容易失误的模型能力,装进一套稳定、可恢复、可解释、可扩展的工程运行时

它不是靠某个神奇 prompt 变强的,而是靠一层层 runtime 把真实复杂度接住了。

Claude Code 的总架构可以收敛成三条主干链路,扩展层则横向为三条主链持续注入能力:

graph LR subgraph Control["🎯 控制链"] direction LR CC1["启动层"] --> CC2["REPL"] --> CC3["Query Loop"] end subgraph Execute["⚡ 执行链"] direction LR EC1["Tool Runtime"] --> EC2["Permission"] --> EC3["Sandbox / Tool Call"] end subgraph Task["📋 任务链"] direction LR TC1["Task Runtime"] --> TC2["后台执行 / 多Agent"] --> TC3["结果回流"] end CC3 -->|"模型决定行动"| EC1 CC3 -->|"需要分身/持续"| TC1 TC3 -->|"结果回灌"| CC3 Extension["🔌 扩展层<br/>MCP · Skills · Plugins"] Extension -.->|"能力注入"| CC3 Extension -.->|"能力注入"| EC1 Extension -.->|"能力注入"| TC1 style Control fill:#e3f2fd,stroke:#2196f3 style Execute fill:#fce4ec,stroke:#e91e63 style Task fill:#e0f2f1,stroke:#009688 style Extension fill:#fff8e1,stroke:#ffc107
  • 控制链(启动层 → REPL → Query Loop):回答"这一轮到底在什么制度下运行"
  • 执行链(Tool Runtime → Permission → Sandbox):回答"模型一旦决定行动,系统如何把动作稳稳落到真实世界里"
  • 任务链(Task Runtime → 后台执行/多 Agent → 回流):回答"如果这件事不是一口气做完,而要持续运行、分身执行,系统如何不乱"

为什么这套架构成立

这套架构成立,不是因为层数多,而是因为每一层都有明确职责,且职责之间的边界相对稳定。Claude Code 不是让所有层都去碰模型,而是让模型只处在该处的位置上;也不是让所有问题都在 query loop 里解决,而是让 query loop 只负责连续运行,把别的复杂度交给别的层。

这就是为什么它虽然复杂,但不显得散——复杂度没有被平均撒到全系统,而是被有意识地集中管理

Claude Code 更值得借鉴的点在于:它没有追求"最少模块",而是追求"每种复杂度只在一个地方爆炸"——上下文和续跑问题只在 Query Loop 爆炸,副作用和权限问题只在执行链爆炸,并发和后台问题只在任务链爆炸,平台增长问题只在扩展层爆炸。

架构真正成熟的标志,从来不是没有复杂度,而是复杂度被放到了对的位置


最后该带走什么

如果一定要把整篇文章压缩成几句最值得带走的话,我会留下这 5 条

  1. 先定义执行边界,再发起第一轮推理
  2. 当 Agent 进入连续运行阶段,query loop 就必须升级成 runtime
  3. 工具一旦开始碰副作用,工具层就必须制度化
  4. 权限系统的核心不是确认框,而是可解释的执行链
  5. 多 Agent 的前提不是 prompt 分工,而是统一任务抽象

Claude Code 真正值得借鉴的,不是它"做了很多层",而是它知道每一层在承接哪一种真实复杂度。对做 AI Agent 的团队来说,这比抄任何单点功能都更有价值。

说到底,Claude Code 最值得学的,不是某一段实现有多巧,而是它始终在认真回答同一个问题——

当模型开始真的做事,系统准备在哪里把这些复杂度接住。

posted @ 2026-04-07 21:36  cwp0  阅读(94)  评论(0)    收藏  举报