Claude Code 子 Agent 机制全解:怎么跑起来、怎么被管理、怎么互不干扰

从源码梳理 Claude Code 包含的几种子 Agent 实现机制------同一进程内的、后台异步的、tmux 多进程的、以及 git worktree 隔离的。不逐行贴源码,把每种机制的核心区别讲清楚。

总览:Claude Code 有多少种"子代理"?

Claude Code 的子代理不是一个单一概念。从源码看,至少可以按四个维度来区分:

按隔离程度:从最轻的"同一进程的 async generator 协程"到最重的"tmux 独立进程"

按运行模式:sync 同步(阻塞父级,结果回来才继续)和 async 异步(后台跑,通知回来)

按 agent 类型:general-purpose、Explore、Plan、verification 等,每种有不同的工具权限和系统提示词

按来源 :内置(built-in)、项目自定义(.claude/agents/)、插件(plugin agents)、fork 模式

一个 Agent 工具调用可以按不同维度组合出多种形态。比如 isolation: "worktree" + subagent_type: "Explore" = 在独立 worktree 里跑只读 Explore agent;run_in_background: true + subagent_type: "general-purpose" = 后台跑全功能 agent。

维度一:运行模式------sync、async、background

这是最基本的区分。由 runAgent() 调用时的 isAsync 参数决定。

sync 模式isAsync: false):

scss 复制代码
主循环 ──→ 发出 Agent 调用 ──→ 子代理跑 query() 循环 ──→ tool_result 返回
   ↑                                                              │
   └──────────────── 一直阻塞等待 ────────────────────────────────┘
  • 共享父级 AbortController------按 Escape 全停
  • setAppState 共享父级(子代理能写入状态)
  • /simplify 的三个审查 Agent 就是 sync 模式

async 模式isAsync: true):

  • 独立 AbortController------按 Escape 不影响它
  • setAppState 为空函数(禁止写入父子代理状态)
  • 消息通过 SendMessage 工具后续发送给子代理
  • 子代理完成后自动通知

background 运行 :AgentTool 的参数里有 run_in_background: truerunAgent() 收到后走 async 路径,主模型不需要等结果。提示词里专门有一段指导 LLM:"When an agent runs in the background, you will be automatically notified when it completes --- do NOT sleep, poll, or proactively check on its progress."

源码里决定 isAsync 的关键逻辑在 AgentTool.tsxcall() 方法:

typescript 复制代码
const shouldRunAsync = (
  run_in_background === true ||
  selectedAgent.background === true ||
  isCoordinator ||
  forceAsync ||
  assistantForceAsync
) && !isBackgroundTasksDisabled;

FYI:还有一种 fork subagent (实验性 feature gate FORK_SUBAGENT),不指定 subagent_type 时触发。它强制所有子代理走 async 路径,子代理继承父级的完整对话上下文和系统提示词,通过 prompt cache 共享降低 token 开销。不展开,但知道有这么个东西就行。

维度二:隔离程度------四种级别的"隔开"

Level 1:纯协程(无隔离)

sync 模式的子代理就是典型的纯协程------同一个进程、同一个堆、同一个 event loop、同一个文件系统。资源隔离全靠 createSubagentContext() 在上层"画线":

属性 隔离方式
messages[] 独立空数组
readFileState cloneFileStateCache() 深克隆
setAppState 空函数(async)/ 共享父级(sync)
addNotification / setToolJSX / setStreamMode 全部 undefined
queryTracking 独立 chainId,depth + 1

子代理看不到父级的对话历史,读文件缓存互不污染,不能弹通知、不能改 UI。但它跟父级共享同一个 JS 堆------如果一个子代理的代码里写了 while(true){},整个进程就卡死了。Claude Code 能接受的原因是子代理的"代码"是 LLM 生成的工具调用,不是任意用户代码。

Level 2:worktree 隔离(文件系统级)

isolation: "worktree" 参数触发。创建临时 git worktree,子代理在独立的工作目录里跑:

typescript 复制代码
// src/tools/AgentTool/AgentTool.tsx
if (effectiveIsolation === 'worktree') {
  const slug = `agent-${earlyAgentId.slice(0, 8)}`
  worktreeInfo = await createAgentWorktree(slug)
}

worktree 解决了两个问题:

  1. 多个子代理同时改文件时不会冲突(各自在不同的工作目录)
  2. 子代理如果改坏了东西,主工作目录不受影响

但注意:worktree 隔离的是文件系统 ,不是进程。子代理仍然在同一个 Node.js 进程里,只是 getCwd() 返回不同路径。代码里有注释专门说明这一点。如果子代理没有改动,worktree 自动清理;如果有改动,返回 worktree 路径和分支名。

Level 3:teammate 模式(真正多进程)

通过 team_name + name 参数触发,走 spawnTeammate() 路径。每个 teammate 跑在独立的 tmux pane 里,是真正的进程级隔离。

提示词里有一条硬限制:in-process 子代理不能 spawn 新 teammate,teammate 也不能 spawn teammate。"The team roster is flat"------teammate 的 roster 是平铺的,不允许嵌套。

Level 4:remote 隔离(云端 sandbox,仅 ant)

内部使用的 isolation: "remote",在远程 CCR 环境运行 agent,always background。外部版本不可用。

快速对照

模式 进程 文件系统 abort 关系 适用
sync 协程 共享 共享 共享父级 需要结果的快速子任务
async 协程 共享 共享 独立 后台长任务
worktree 共享 隔离 独立 多 agent 并行修改文件
teammate 独立 共享 独立 重度并行
remote 独立 隔离 独立 云端 sandbox

维度三:Agent 类型------内置和自定义

通过 subagent_type 参数指定。不指定时默认 general-purpose,或者走 fork 路径(前面说的 fork subagent)。

内置 agent 定义在 src/tools/AgentTool/built-in/ 下,每个文件 export 一个 BuiltInAgentDefinition

Agent 核心特征 关键配置
general-purpose 默认 agent,全能力 tools: ['*'], source: 'built-in'
Explore 只读,快速搜索 disallowedTools: [FileEdit, FileWrite, NotebookEdit, Agent, ExitPlanMode], model 定向 haiku
Plan 只读,设计实现计划 系统提示词侧重架构设计,omitClaudeMd: true, 复用 Explore 的工具权限
verification 验证 agent 输出 专门用于检查和验证
statusline-setup 配置状态栏 专用小工具
claude-code-guide Claude Code 文档查询 加载官方文档作为上下文

Explore 就是 Claude Code 在探索代码库时派出去的搜索子代理。系统提示词第一句就是 "You are a file search specialist",核心能力只有三项:Glob 找文件、Grep 搜内容、Read 读代码。主模型收到"帮我查一下这个代码库怎么处理认证"这类任务时,就可以 subagent_type: "Explore" 把它派出去------子代理在自己的隔离上下文里大范围搜索,最后只把精简结论返回。这也是零索引方案里上下文隔离的关键支撑:大量搜索中间结果由 Explore 在自己的上下文里消化,不撑满主对话。

Explore 和 Plan 都标记为 READ-ONLY。disallowedTools 排除了所有编辑类工具。Bash 虽然保留,但提示词严格限定只读命令(lsgit statuscatheadtail 等)。

每个内置 agent 都定义了自己的 whenToUse,这个文本会出现在主模型的系统提示词里,告诉 LLM 什么时候该切换到哪个 agent。例如 Explore 的 whenToUse:"Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns..."

Explore 还有一个特有属性 omitClaudeMd: true------它的系统提示词不加载 CLAUDE.md 的项目规则。"The main agent has full context and interprets results." 省掉约 5-15 Gtok/week 的 token。

自定义 Agent

通过 .claude/agents/*.md 或插件的 agents/ 目录定义。用 YAML frontmatter + Markdown 内容配置:

markdown 复制代码
---
name: my-code-reviewer
description: 专门审查代码质量和安全性
tools: Read, Grep, Glob, Bash
permissionMode: acceptEdits
model: sonnet
---

当进行代码审查时:
1. 先读取变更的代码
2. 用 Grep 查找相关的已有实现
3. 报告发现的问题和优化建议

支持的配置项包括 toolsdisallowedToolspermissionModemodeleffortmaxTurnsskillsmcpServershooksbackgroundisolationmemoryinitialPrompt。自定义 agent 跟内置 agent 走同一套 AgentDefinition 类型系统,runAgent() 不区分来源。

维度四:fork 子代理(实验性)

这是一个 feature gate FORK_SUBAGENT 控制的功能。当 gate 开启且不指定 subagent_type 时触发。

跟普通子代理最大的区别:fork 子代理继承父级的完整对话上下文和系统提示词 ,而不是从干净的 "prompt + diff" 开始。每个 fork spawn 强制走 async,用户通过 <task-notification> 接收完成通知。

设计动机是 prompt cache 共享:因为 fork 子代理的系统提示词和父级完全相同,API 能复用 prompt cache,降低 token 成本。/fork <directive> slash 命令也是基于这个机制。

跟本篇关系不大,知道存在即可。

子代理怎么跑起来的------统一的 runAgent() 管线

不管哪种模式、哪种 agent 类型,最终都走同一个 runAgent() 函数。管线大致是:

scss 复制代码
AgentTool.call()
    ↓
判断模式(sync/async,worktree?teammate?)
    ↓
如果是 teammate → spawnTeammate() → 返回(独立进程)
    ↓
如果是 subagent → 构建 runAgentParams(确定 agent 定义、组装工具池、确认 isAsync)
    ↓
runAgent()
    ├── 1. 确定模型
    ├── 2. 生成 agentId
    ├── 3. 组装初始消息(promptMessages + forkContextMessages)
    ├── 4. createSubagentContext() 构建隔离上下文
    ├── 5. resolveAgentTools() 解析工具池
    ├── 6. 生成系统提示词(agent.getSystemPrompt() + 环境增强)
    ├── 7. 运行 query() 循环(与主模型完全相同的 API 调用循环)
    └── 8. finally: 7 项清理(MCP、hooks、缓存、todos、shell 任务等)

不管 sync 还是 async,不管 general-purpose 还是 Explore,全部走这条管线。区别在于调用时的参数不同。

总结

Claude Code 的子代理不是单一实现------它是一个多层面的组合系统:

  1. 运行模式(sync / async / background)决定父级是否等待以及 abort 关系
  2. 隔离程度(协程 / worktree / teammate / remote)决定资源隔离层级
  3. agent 类型(内置或自定义)决定工具权限、模型选择、系统提示词
  4. fork 模式(实验性)继承上下文,优化 prompt cache

各种组合覆盖了从"快速查一下代码"到"深度重构且不能搞坏主工作目录"的场景。核心原则是:默认最轻(in-process 协程),只在需要时才升级隔离级别。

相关推荐
HSunR2 小时前
dify 搭建ai作业批改流
开发语言·前端·javascript
代码不加糖2 小时前
2026 跨境电商独立站实战:从 0 到 1 搭建高转化 SaaS 商城(附源码)
开发语言·前端·javascript
用户617517157014 小时前
关于普通函数和箭头函数的this
javascript
RPGMZ4 小时前
RPGMakerMZ 地图存档点制作 标题继续游戏直接读取存档
开发语言·javascript·游戏·游戏引擎·rpgmz·rpgmakermz
有一个好名字5 小时前
Agent Loop —— 一切从那个 while 循环开始
前端·javascript·chrome
EF@蛐蛐堂5 小时前
【js】浏览器滚动条优化组件OverlayScrollbars
开发语言·javascript·ecmascript
辰同学ovo6 小时前
从全局登录状态管理学习 Redux
前端·javascript·学习·react.js
爱看书的小沐6 小时前
【小沐杂货铺】基于Three.js绘制三维艺术画廊3DArtGallery (Three.js,WebGL)
javascript·3d·webgl·three.js·babylon.js·三维画廊
ZC跨境爬虫6 小时前
跟着 MDN 学 HTML day_12:(HTML网页图片嵌入)
前端·javascript·css·ui·html