首页 › tutorials › 造三角色子代理团队接力迁移

概述

本教程带你用 Claude Code 的自定义子代理(subagents)搭建一个"探索者→实现者→评审者"三角色接力流水线,把一个 CommonJS 小工程无人值守地迁移到 ESM。你将掌握 .claude/agents/ 子代理文件的写法、frontmatter 字段、Agent(type) 协调者白名单、以及用 -p / --agent 无头模式跑通整条自动化链路。适合已写过一两个 Skill 或 MCP、想进入"多代理协作"阶段的 Claude Code 中级用户。

前置条件


一、先理解子代理到底解决了什么

很多人第一次接触"多代理"会以为是开几个终端窗口。Claude Code 的子代理完全是另一回事:它在同一个会话里,派生出一个拥有独立上下文窗口、独立系统提示、独立工具权限的"分身",干完活只把摘要回传给主会话。

这个机制解决三个真实痛点:

  1. 上下文污染——跑一次完整测试套件、读十几个源文件、抓一堆日志,这些会把你主会话的上下文撑爆。交给子代理,冗长输出留在它自己的窗口里,主会话只收到一句"3 个失败用例,原因分别是…"。
  2. 权限收敛——你可以给某个子代理只开 Read, Grep, Glob,让它"只能看不能改",从结构上杜绝误删。
  3. 角色分工与复用——评审、实现、测试这类反复出现的角色,固化成一个 .md 文件,团队所有人都能用。

本次要造的三角色分工如下:

角色 文件名 能力 模型策略
探索者 cjs-explorer.md 只读扫描,产出迁移清单 haiku 省钱
实现者 esm-implementer.md 改文件、装依赖 sonnet,acceptEdits
评审者 migration-reviewer.md 只读 + 跑测试 sonnet
协调者 migration-coordinator.md 调度上面三个 inherit

二、步骤一:搭好示例工程

先造一个最小可迁移的 CommonJS 工程,这样后面的代理有真东西可改。

mkdir esm-migrate-demo && cd esm-migrate-demo
git init -q
mkdir -p src

package.json:

{ "name": "esm-migrate-demo", "version": "1.0.0", "type": "commonjs" }

src/math.js:

function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
module.exports = { add, multiply };

src/index.js:

const { add, multiply } = require('./math');
function compute(x, y) { return multiply(add(x, y), 2); }
module.exports = { compute };

test.js(根目录):

const { compute } = require('./index');
const assert = require('assert');
assert.strictEqual(compute(2, 3), 10);
console.log('all tests passed');

验证起点是通的:

node test.js        # 期望输出: all tests passed

常见问题:如果 node test.js 报 Cannot find module,说明 require('./index') 的路径解析有误——确认 src/index.js 里导出的是 compute,且 test.js 与 src/ 同级。

三、步骤二:造探索者(只读、省钱)

在工程根目录建子代理目录与第一个文件:

mkdir -p .claude/agents

.claude/agents/cjs-explorer.md:

---
name: cjs-explorer
description: 扫描 CommonJS 工程并列出全部 require/module.exports 用法,产出 ESM 迁移清单。只读、不改任何文件。
tools: Read, Glob, Grep
model: haiku
color: cyan
---

你是 CommonJS→ESM 迁移的探索者。收到任务后:
1. 用 Glob 找出所有 `.js` 文件(含 test 文件)。
2. 用 Grep 定位每个文件里的 `require(...)`、`module.exports`、`exports.xxx`。
3. 读取相关文件确认细节。

输出**严格 JSON 清单**(不要多余解释),形如:
[
  { "file": "src/math.js", "requires": [], "exports": ["add", "multiply"], "needsExt": false },
  { "file": "src/index.js", "requires": [{ "from": "./math", "names": ["add", "multiply"], "addExt": true }], "exports": ["compute"], "needsExt": false },
  { "file": "test.js", "requires": [{ "from": "./index", "names": ["compute"], "addExt": true }, { "from": "assert", "builtin": true, "nodePrefix": true }], "exports": [], "needsExt": false }
]
规则:相对路径导入要标记 `addExt:true`(补 `.js`);Node 内建模块标记 `nodePrefix:true`(改 `node:assert`)。

要点拆解:

常见问题:子代理输出带了 Markdown 代码块包裹(```json)。这是 Haiku 常见习惯。解决方法是在正文里用"严格 JSON 清单(不要代码块包裹、不要多余解释)"这种强约束,或者让协调者做一次剥离。本教程让协调者解析。

四、步骤三:造实现者(可改、自动接受编辑)

.claude/agents/esm-implementer.md:

---
name: esm-implementer
description: 接收探索者产出的迁移清单,把 CommonJS 改写为 ESM:require→import、module.exports→export、相对路径补 .js、内建模块加 node: 前缀、更新 package.json 的 type。
tools: Read, Edit, Write, Glob
model: sonnet
permissionMode: acceptEdits
color: green
---

你是 ESM 迁移的实现者。你会收到一份 JSON 迁移清单。逐文件执行:
- `const {a,b} = require('./x')` → `import {a,b} from './x.js'`
- `const x = require('assert')`(内建)→ `import x from 'node:assert'`
- `module.exports = {foo}` → `export {foo}`;`exports.foo = ...` → `export const foo = ...`
- `const m = require('./m')`(整体导入)→ `import * as m from './m.js'`
- 把 `package.json` 的 `"type": "commonjs"` 改为 `"type": "module"`。

完成后用 `Read` 自检每个文件语法,回传"已修改文件列表"与每个文件的关键改动行。不要运行测试——那是评审者的职责。

要点:

常见问题:实现者改完 package.json 后,旧测试还用 require,跑起来必然报错。这是预期的——本步不跑测试,把验证交给下一步。不要在实现者里塞测试逻辑,那是越界。

五、步骤四:造评审者(只读 + 跑测试)

.claude/agents/migration-reviewer.md:

---
name: migration-reviewer
description: ESM 迁移完成后的评审者。对每个改动文件执行 node --check 语法校验,再跑测试,回传通过/失败结论与失败明细。不改正文代码。
tools: Read, Bash, Grep
disallowedTools: Write, Edit
model: sonnet
color: yellow
---

你是迁移评审者。任务:
1. 对所有被改动的 `.js` 文件执行 `node --check <file>`,收集语法错误。
2. 运行 `node test.js`(或工程定义的测试命令)。
3. 输出结构化结论:
   PASS 或 FAIL。
   若 FAIL,列出每个失败项:文件、错误信息、定位到的根因(如"相对导入缺 .js 扩展名")。
不要修改代码。只报告。

要点:

常见问题:node --check 对 ESM 的静态 import 校验很严格,"相对导入必须带扩展名"这类错误能被它第一时间抓出来,比直接跑测试定位更快。养成"先 --check 后测试"的两段式评审习惯。

六、步骤五:造协调者(编排三角色)

关键一步:协调者要能调用上面三个子代理,且只能调这三个。这用 Agent(type) 白名单语法实现。

.claude/agents/migration-coordinator.md:

---
name: migration-coordinator
description: 协调 CommonJS→ESM 迁移的三角色流水线:先派 cjs-explorer 扫描,再派 esm-implementer 按清单改写,最后派 migration-reviewer 验证。失败则把评审反馈回传实现者重试,最多 2 轮。
tools: Agent(cjs-explorer, esm-implementer, migration-reviewer), Read, Bash
model: inherit
color: purple
---

你是迁移流水线的协调者。严格按下列顺序工作:
1. 派 `cjs-explorer` 扫描,拿到 JSON 迁移清单。若输出被代码块包裹,先剥离再传给下一步。
2. 把清单交给 `esm-implementer` 执行改写。
3. 派 `migration-reviewer` 校验。
4. 若评审为 FAIL:解析失败项,连同失败文件列表再次交给 `esm-implementer` 修复;重新评审。最多 2 轮重试。
5. 若 2 轮后仍 FAIL,停止并汇报剩余问题;若 PASS,汇报成功与改动摘要。

不要自己改代码——你没有 Edit/Write。你的职责是调度与传递上下文。

要点:

七、步骤六:交互式跑通

先确认子代理被识别。重启一次会话(首次创建 .claude/agents/ 目录后需要重启才能加载;之后改动文件会自动热加载,无需重启):

claude

进入会话后输入一句点名协调者:

用 migration-coordinator 把 src/ 和 test.js 从 CommonJS 迁移到 ESM,评审通过为止。

预期行为:主会话识别到 migration-coordinator,通过 Agent 工具依次派生三个子代理。你会看到子代理面板里出现三个不同颜色的行(青/绿/黄),协调者行(紫)带 (+N) 后代计数。

成功跑完后,文件已变成 ESM:

// src/index.js(迁移后)
import { add, multiply } from './math.js';
export function compute(x, y) { return multiply(add(x, y), 2); }
// test.js(迁移后)
import { compute } from './index.js';
import assert from 'node:assert';
assert.strictEqual(compute(2, 3), 10);
console.log('all tests passed');

验证:

node test.js        # 期望: all tests passed

常见问题:若主会话没有自动派生协调者,改用 @ 提及强制点名:@"migration-coordinator (agent)" 把 src/ 迁移到 ESM。@ 提及保证"这个子代理一定会跑",把选择权从模型手里拿过来。

八、步骤七:无头自动化(-p / --agent)

交互式确认无误后,把它变成一条可放进 CI 的命令。--agent 让整个会话以协调者的系统提示启动,-p 一次性投递任务:

claude --agent migration-coordinator \
  --permission-mode acceptEdits \
  -p "将 src/ 与 test.js 从 CommonJS 迁移到 ESM:相对导入补 .js,内建模块加 node: 前缀,更新 package.json 的 type。完整跑通探索→实现→评审,评审 PASS 才算完成。" \
  > migrate-report.txt 2>&1

要点:

临时一次性用法:不想落盘子代理文件,可以用 --agents 直接传 JSON 定义。注意 JSON 里系统提示字段叫 prompt(等价于文件的正文 body):

claude --agents '{
  "quick-reviewer": {
    "description": "快速只读评审,跑 node --check 与测试。",
    "tools": ["Read", "Bash", "Grep"],
    "disallowedTools": ["Write", "Edit"],
    "model": "sonnet",
    "prompt": "你是评审者,只报告 PASS/FAIL 与失败明细,不改代码。"
  }
}' -p "用 quick-reviewer 评审当前目录的 ESM 改造结果"

这条命令里子代理只在本会话存活、不写盘,适合脚本里临时组合能力。

九、增强:给评审者挂子代理级钩子

Claude Code 支持把生命周期钩子作用域限定到单个子代理(hooks: frontmatter 字段),这是 2026 年的新能力,比全局钩子更精准。给评审者加一个"每次跑完 Bash 命令就把输出存档"的钩子:

# 在 migration-reviewer.md 的 frontmatter 里追加
hooks:
  PostToolUse:
    - matcher: Bash
      hooks:
        - type: command
          command: "mkdir -p .review-logs && cat >> .review-logs/run.log"

要点:

常见问题:钩子命令的 stdin 是事件 JSON,所以上面用 cat >> run.log 能把每次 Bash 工具调用的元数据(含退出码、命令)追加进日志。调试钩子时先 cat > /tmp/hook.json 看一眼真实载荷结构再写处理逻辑。

十、实战案例:完整流水线总览

把整条链路画成两张图。第一张是数据流(谁产出什么交给谁):

flowchart LR
    U([用户 / -p 任务]) --> CO[migration-coordinator<br/>协调者]
    CO -->|1. 扫描| EX[cjs-explorer<br/>只读·haiku]
    EX -->|JSON 迁移清单| CO
    CO -->|2. 改写+清单| IM[esm-implementer<br/>可改·sonnet]
    IM -->|改动摘要| CO
    CO -->|3. 校验| RV[migration-reviewer<br/>只读+Bash·sonnet]
    RV -->|PASS / FAIL+明细| CO
    CO -. FAIL→重试最多2轮 .-> IM
    CO -->|PASS 摘要| R([完成报告])

第二张是并发与时序(注意 v2.1.198 起子代理默认在后台跑,可并发推进不互相阻塞的任务):

sequenceDiagram
    participant Main as 主会话
    participant CO as 协调者
    participant EX as 探索者
    participant IM as 实现者
    participant RV as 评审者
    Main->>CO: --agent + -p 派发迁移任务
    CO->>EX: 扫描工程(白名单内)
    EX-->>CO: 返回迁移清单 JSON
    CO->>IM: 传清单,执行改写
    IM-->>CO: 改动文件摘要
    CO->>RV: 校验(node --check + test)
    alt PASS
        RV-->>CO: PASS
        CO-->>Main: 成功摘要
    else FAIL
        RV-->>CO: FAIL + 失败明细
        CO->>IM: 回传失败项修复
        IM-->>CO: 二次改动
        CO->>RV: 重新校验
    end

十一、常见问题汇总

Q1:协调者不按顺序,自己跑去读文件改代码了。 检查 tools 是否误开了 Edit/Write,并确认 Agent(...) 白名单语法写对。协调者只应有 Agent(三个名字), Read, Bash。没有 Edit/Write,它物理上改不了代码。

Q2:探索者跑得慢、成本高。 确认 model: haiku 真的生效。模型解析优先级是:环境变量 CLAUDE_CODE_SUBAGENT_MODEL > 单次调用的 model 参数 > frontmatter model > 主会话模型。如果你设了该环境变量,会覆盖 frontmatter。

Q3:子代理之间要不要传完整文件内容? 不要。让探索者只回传清单,协调者把清单交给实现者——实现者自己去 Read 文件。传完整文件内容等于把上下文污染从子代理搬回协调者,违背隔离初衷。

Q4:评审者卡在测试失败,实现者改了两轮还过不了。 协调者上限设的是 2 轮重试。超出就停止并汇报,把剩余问题交还人类判断。这是"有界自主"的设计——避免无限循环烧钱。可按任务复杂度调整协调者正文里的重试上限。

Q5:想并行迁移多个工程。 子代理在单个会话内工作;要跑很多独立会话并行,应该用 background agents(多会话并行+集中监控),那是另一套机制,不在本教程范围。

Q6:并发派多个探索者会不会乱套? 不会。每个子代理有独立上下文与独立工作目录起点(默认共享主会话 cwd)。若担心并行改文件互相覆盖,给实现者加 isolation: worktree,让它在一个临时 git worktree 里改,无改动时自动清理。

十二、设计取舍:子代理 vs 主会话 vs Skills

这是最容易被忽视的一层判断,选错机制事倍功半:

一句话记忆:子代理=独立房间+独立钥匙;Skill=主房间里的一张操作手册。

训练营适用性评估

参考来源

  1. Create custom subagents — Claude Code Docs — 官方子代理权威文档,覆盖 frontmatter 全字段、Agent(type) 白名单、权限模式、链式与嵌套派生、v2.1.198 后台默认等核心机制。
  2. Subagents in the SDK — Claude Code Docs — Agent SDK 中定义与调用子代理、隔离上下文、并行任务与专项指令的官方说明。
  3. How to Build Custom Sub-Agents in Claude Code: YAML, Tools — MindStudio — 详解子代理 YAML frontmatter 的 description/tools/model 写法与触发准确率实践。
  4. Claude Code Subagents: Parallelize Development — AI for Anything (2026) — 2026 年视角的子代理并行开发与多代理工作流编排指南。
  5. Claude Code Subagents, Background Agents & Agent Teams in 2026 — SaaSCity — 区分子代理(单会话)、background agents(多会话并行)、agent teams(会话间通信)三类机制的对比。

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入