<!-- title: 自定义子代理：无头批量派活实战 -->

> 学习目标：掌握 Claude Code 子代理（Subagent）从定义到编排的完整链路，学会在 `-p` 无头模式下批量派发专项任务、用 `stream-json` 实时追踪进度，并给子代理挂载专属 Hooks，最终落地一个"多仓库多角色代码体检流水线"。适合已能用 `claude -p` 跑通单条命令、想进一步构建无人值守批量自动化与多角色协作的开发者。

---

## 前置条件

- 已安装 Claude Code（建议 v2.1.128 以上，以获得 `--json-schema`、`--bare` 等能力），并能用 `claude -p "hello"` 跑通一次无头调用。
- 终端有 `jq`、`bash`（或 `zsh`）、`git`；Windows 用户建议在 WSL 或 Git Bash 下操作。
- 准备一个含若干代码仓库的目录（或临时 `git clone` 两三个小仓库）用于批量演练。
- 了解 YAML frontmatter 的基本语法，以及 JSON Schema 的最基本结构。

---

## 一、子代理 × 无头模式：为什么要这样组合

`-p`（`--print`）无头模式让 Claude Code 变成一条可被管道、脚本、CI 调用的命令；而子代理（Subagent）则是"带独立上下文窗口、独立工具集、独立权限、独立模型"的专项工人。把两者结合，可以做到三件单用 `-p` 做不好的事：

1. **上下文隔离**：每个专项工人（如安全扫描、复杂度分析、测试覆盖建议）在自己的上下文窗口里翻文件、跑搜索，只把"结论"回传给主对话，避免大批搜索结果把主上下文撑爆。
2. **角色专精与成本分级**：简单扫描用 `haiku`，需要推理的用 `sonnet`，按角色挑模型，把钱花在刀刃上。
3. **可复用、可分发**：子代理是 `.claude/agents/` 下一个 Markdown 文件，可提交版本控制、随插件分发，团队和 CI 共用同一份"专家配置"。

整套协作模型如下：

```mermaid
flowchart LR
    subgraph 驱动层["驱动层（Shell / CI）"]
        SH["run-audit.sh<br/>批量派活循环"]
    end
    subgraph 无头层["无头层 -p"]
        MAIN["claude -p<br/>主编排会话"]
        SA1["子代理: security-scanner<br/>model=haiku 只读"]
        SA2["子代理: complexity-audit<br/>model=sonnet 只读"]
        SA3["子代理: test-coverage-advisor<br/>model=sonnet 只读"]
    end
    subgraph 沉淀层["沉淀层"]
        JSON["每仓 JSON 结果"]
        DASH["聚合仪表盘"]
    end
    SH -->|"逐仓/逐角色"| MAIN
    MAIN -->|"Agent 工具委派"| SA1
    MAIN -->|"Agent 工具委派"| SA2
    MAIN -->|"Agent 工具委派"| SA3
    SA1 --> JSON
    SA2 --> JSON
    SA3 --> JSON
    JSON --> DASH
```

关键认知：子代理可以两种方式被启动——

- **委派式**：主 `-p` 会话里，Claude 根据子代理的 `description` 自动委派，或你在提示里点名"使用 security-scanner 子代理"，Claude 通过 `Agent` 工具调用它。
- **主代理式**：用 `--agent <name>` 直接把某个子代理当作整场 `-p` 会话的唯一执行者，适合"一个角色跑完一个任务"的纯批量场景。

---

## 二、定义第一批自定义子代理

子代理就是带 YAML frontmatter 的 Markdown 文件，存放在：

| 位置 | 作用域 | 优先级 |
|------|--------|--------|
| `~/.claude/agents/` | 你所有项目（个人） | 4（低） |
| `.claude/agents/` | 当前项目（可提交版本控制） | 3 |
| `--agents '<json>'` | 仅本次会话（脚本/CI 临时） | 2 |
| 管理设置目录 | 组织级强制下发 | 1（高） |

先在示例项目里建目录与三个子代理。

**`security-scanner.md`**（用最快最便宜的 Haiku，只读）：

```yaml
---
name: security-scanner
description: 扫描硬编码密钥、SQL 注入、不安全反序列化等安全风险。在涉及安全审计、密钥扫描、漏洞排查时主动委派。
tools: Read, Grep, Glob
model: haiku
permissionMode: default
color: red
---
你是一名安全审计专家。给定代码库，只关注：
1. 硬编码密钥、token、密码、连接串
2. SQL 拼接、命令注入、路径穿越
3. 不安全的反序列化、弱加密算法
输出严格 JSON 数组，每项含 file、line、severity(critical/high/medium/low)、rule、evidence(原代码片段)、suggestion。
不要解释，不要修复，只输出 JSON 数组。
```

**`complexity-audit.md`**（需要推理，用 Sonnet）：

```yaml
---
name: complexity-audit
description: 定位圈复杂度过高的函数与重复代码块。在代码复杂度分析、技术债评估、重构候选筛选时委派。
tools: Read, Grep, Glob
model: sonnet
color: yellow
---
你是代码复杂度分析专家。识别：
- 圈复杂度过高的函数（嵌套 if/for/while 过深）
- 超长函数（>80 行）
- 重复代码块（疑似可抽取）
输出 JSON 数组：file、function、issue_type(complexity/length/duplication)、detail、refactor_hint。
只输出 JSON，不要修复。
```

**`test-coverage-advisor.md`**：

```yaml
---
name: test-coverage-advisor
description: 找出缺少测试的关键模块并给出测试用例建议。在测试覆盖分析、补测试任务规划时委派。
tools: Read, Grep, Glob
model: sonnet
color: green
---
你是测试设计专家。找出高风险却缺测试的模块（核心业务逻辑、边界条件、错误处理路径）。
输出 JSON 数组：file、module、risk(missing/partial/edge)、recommended_tests(数组，每条一句)。
只输出 JSON。
```

> **预期结果**：三个文件就位后，在交互模式里运行 `/agents`，能在 Library 标签页看到这三个自定义子代理。文件直接落盘需要重启会话才加载；用 `/agents` 界面创建的则即时生效。

> **常见问题**
> - **子代理没被自动触发？** 90% 是 `description` 写得太宽泛。description 是 Claude 判断"何时委派"的唯一依据，要写清"在什么场景下、做什么事"，并鼓励"主动委派"。
> - **同目录下两个文件 `name` 相同？** Claude Code 只保留一个、静默丢弃另一个，不给警告。务必保证 `name` 在整棵子目录树里唯一。
> - **想让某子代理绝对不能写文件？** 用 `tools` 白名单（只列 Read/Grep/Glob）比用 `disallowedTools` 黑名单更安全——白名单天然屏蔽未来新增的危险工具。

---

## 三、用 `-p` 单发验证子代理

批量之前，先单发跑通。两种启动方式各试一遍。

### 方式 A：委派式（主会话编排多个角色）

```bash
# 在某个目标仓库根目录执行
claude -p "用 security-scanner、complexity-audit、test-coverage-advisor 三个子代理分别审计当前目录，\
把每个子代理的 JSON 结论原样拼接成一个大数组返回。" \
  --output-format json \
  --allowedTools "Read,Grep,Glob" \
  | jq '.result'
```

主会话会通过 `Agent` 工具把任务拆给三个子代理，它们各自独立读文件、互不污染主上下文，最终把结论汇总到 `.result`。

### 方式 B：主代理式（一个角色跑完整任务）

```bash
claude -p "审计当前目录的安全风险" \
  --agent security-scanner \
  --output-format json \
  | jq '.result'
```

`--agent` 让 `security-scanner` 直接成为这场 `-p` 会话的唯一执行者，跳过主编排层，开销更低，最适合"一仓一角色"的纯批量。

### 用 JSON Schema 拿到机器可读的结构化结果

`--output-format json` 的 `.result` 仍是文本。要拿到强约束的结构化数据，叠加 `--json-schema`，结果落在 `.structured_output`：

```bash
claude -p "提取 auth.py 中所有顶层函数名" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output.functions'
```

> **预期结果**：`.structured_output` 严格符合你给的 schema，可直接喂给下游 `jq` 或聚合脚本，无需正则解析自由文本。
>
> **常见问题**
> - **结果里混入了说明性文字，schema 解析失败？** 子代理的系统提示里务必明确写"只输出 JSON，不要解释"。子代理只收到它的系统提示，不会继承 Claude Code 主提示，约束必须写进 body。
> - **被权限弹窗卡住？** `-p` 模式不能交互授权，未预批准的工具一旦触发会直接中止。用 `--allowedTools` 显式放行所需工具，或 `--permission-mode acceptEdits`。

---

## 四、编写批量派活驱动脚本

核心思路：用一个 shell 循环把"仓库列表 × 角色"展开成一组 `claude -p` 调用，用 `xargs -P` 并发，每条调用产出一份 JSON，最后 `jq` 聚合。

**`run-audit.sh`**：

```bash
#!/usr/bin/env bash
set -euo pipefail

# 待审计仓库列表（每行一个路径）
REPOS=(./repos/auth-svc ./repos/payment-svc ./repos/notification-svc)
# 角色与对应子代理
ROLES=(security-scanner complexity-audit test-coverage-advisor)
OUTDIR="./audit-out"
mkdir -p "$OUTDIR"

audit_one() {
  local repo="$1" role="$2"
  # 用仓库短名 + 角色 + 时间戳命名，避免覆盖
  local name; name="$(basename "$repo")"
  claude -p "审计 $repo 目录，只输出 JSON 结论。" \
    --agent "$role" \
    --output-format json \
    --allowedTools "Read,Grep,Glob" \
    > "$OUTDIR/${name}__${role}.json"
  echo "✓ $name :: $role"
}
export -f audit_one
export OUTDIR

# 展开 仓库×角色 笛卡尔积，并发度 4
for r in "${REPOS[@]}"; do for role in "${ROLES[@]}"; do echo "$r|$role"; done; done \
  | awk -F'|' '{print $1"|"($2-1)}' \
  | tr '|' ' ' \
  | xargs -n2 -P4 bash -c 'audit_one "$0" "$1"'

echo "全部完成，结果在 $OUTDIR/"
```

> **说明**：`--agent "$role"` 让每个角色作为独立主代理跑，互不干扰；`-P4` 控制并发度，避免一次性打满 API 限流。`--bare` 在 CI 里推荐加上，以屏蔽队友 `~/.claude` 里的 hook/MCP 干扰，保证多机一致。

### 聚合成一个总报告

```bash
# 把所有单仓 JSON 的 structured_output/result 汇总，附 cost
jq -s 'map({
  repo: input_filename | ltrimstr("./audit-out/") | split("__")[0],
  role: input_filename | ltrimstr("./audit-out/") | split("__")[1] | rtrimstr(".json"),
  cost: .total_cost_usd,
  result: .result
})' "$OUTDIR"/*.json > ./audit-report.json

# 统计总花费
jq '[.[].cost] | add' ./audit-report.json
```

`--output-format json` 的响应里自带 `total_cost_usd` 和按模型拆分的费用明细，批量后可精确核算每个仓库、每个角色花了多少钱。

> **常见问题**
> - **并发太高被限流？** `stream-json` 里会发出 `system/api_retry` 事件；若用 json 模式则表现为调用失败。调低 `-P`，或对 429 做指数退避重试。
> - **后台进程把 `-p` 卡住不退出？** v2.1.163 之后，`-p` 结束约 5 秒会终止其启动的后台 shell；后台子代理/工作流的等待默认上限 10 分钟（`CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` 可调）。
> - **想跨多机/CI 完全一致？** 加 `--bare`，并用 `--agents '<json>'` 把子代理定义内联进命令，不依赖磁盘文件。下一节会演示这种内联写法。

---

## 五、给子代理挂载专属 Hooks

Hooks 让你在生命周期的关键节点自动执行 shell 命令。和子代理结合时有两个要点：

1. **子代理的 `hooks` 字段**：在子代理 frontmatter 里写 `hooks:`，该 hook 只在此子代理生命周期内触发（注意：来自插件的子代理会忽略此字段）。
2. **`agent_type` 字段**：项目级 hook 收到的输入 JSON 里带 `agent_type`，值就是当前子代理的 `name`，据此可区分"是哪个子代理触发的"。

### 用例 1：记录每次子代理委派（项目级 PostToolUse）

在 `.claude/settings.json` 里：

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Agent",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '\"[\\(now|todateiso8601)] agent=\\(.tool_response.agent_type // \"main\") task=\\(.tool_input.description // \"-\")\"' >> ./.audit-delegation.log"
          }
        ]
      }
    ]
  }
}
```

每次主会话通过 `Agent` 工具委派子代理，都会追加一行审计日志，`agent_type` 标明派给了谁——批量跑完你能复盘"哪个角色被调了多少次"。

### 用例 2：子代理专属的事后留痕（frontmatter 内 hooks）

在 `security-scanner.md` 里追加（让安全子代理每次跑完都落一份痕迹）：

```yaml
---
name: security-scanner
description: 扫描硬编码密钥与注入风险，安全审计时主动委派。
tools: Read, Grep, Glob
model: haiku
color: red
hooks:
  Stop:
    - hooks:
        - type: command
          command: "echo \"security-scancer finished at $(date)\" >> ./.security-runs.log"
---
...（同前系统提示）
```

`Stop` 事件在该子代理结束本轮时触发。可改成调用 webhook、推送 IM 通知、触发下游流水线。

### 用例 3：UserPromptSubmit 预处理（批量场景的"上下文注入"）

批量派活时，可以让一个 `UserPromptSubmit` hook 自动把仓库元信息（git 分支、最近 commit、语言统计）注入每条提示，省去每次手写：

```json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "jq -n --arg br \"$(git rev-parse --abbrev-ref HEAD)\" --arg sha \"$(git rev-parse --short HEAD)\" '{additionalContext: (\"当前分支: \" + $br + \"，HEAD: \" + $sha)}'"
          }
        ]
      }
    ]
  }
}
```

hook 返回的 `additionalContext` 会被追加到用户提示后，子代理据此知道自己在审计哪个快照。

> **注意**：`--bare` 模式不读取任何 hook/MCP/CLAUDE.md，所以上面这些 hook 在 `--bare` 下不会生效。CI 里若要用 hook，就别加 `--bare`；若要绝对可复现，就用 `--bare` 并把逻辑前移到脚本里。两者择一，别混用。

---

## 六、流式输出与实时进度看板

批量跑几十个仓库时，等所有 JSON 落盘再看结果太被动。用 `stream-json` 实时拿到事件流，边跑边看进度。

```bash
claude -p "审计当前目录安全风险" \
  --agent security-scanner \
  --output-format stream-json \
  --verbose \
  --include-partial-messages \
  | jq -rc 'select(.type=="system" and .subtype=="init") | "会话启动，模型=\(.model)"'
```

关键事件类型：

- `system/init`：会话首个事件，含 `model`、加载的工具、MCP 服务器、`plugins` 与 `plugin_errors`——CI 里可用它检测"插件没加载上就 fail"。
- `stream_event` + `event.delta.type=="text_delta"`：逐 token 文本，可拼成实时打字效果。
- `system/api_retry`：API 重试前发出，含 `attempt`、`max_retries`、`retry_delay_ms`、`error_status`——可据此自绘重试进度或自定义退避。
- `result`：最终结果，含 `total_cost_usd`。

把驱动脚本改用 `stream-json`，再用 `jq` 过滤出"开始/重试/完成"事件，就能拼出一个轻量进度看板：

```bash
... | jq -rc '
  if .type=="system" and .subtype=="init" then "▶ 启动"
  elif .type=="system" and .subtype=="api_retry" then "↻ 重试 #\(.attempt)"
  elif .type=="result" then "■ 完成，花费 $\(.total_cost_usd)"
  else empty end'
```

---

## 实战案例：多仓库代码体检流水线（完整可运行）

把前六步拼成一个真实可用的小项目。目录结构：

```
code-health-pipeline/
├── .claude/
│   ├── agents/
│   │   ├── security-scanner.md
│   │   ├── complexity-audit.md
│   │   └── test-coverage-advisor.md
│   └── settings.json          # 委派日志 + 上下文注入 hooks
├── repos/                      # 待审计仓库（git clone 进来）
├── run-audit.sh               # 批量派活驱动
├── aggregate.sh               # jq 聚合
└── audit-out/                  # 产出（gitignore）
```

**核心驱动（内联子代理版本，CI 友好，不依赖磁盘文件）**：

```bash
#!/usr/bin/env bash
# run-audit.sh —— 用 --agents 内联定义，--bare 保证多机一致
set -euo pipefail

AGENTS_JSON='{"security-scanner":{"description":"扫描密钥与注入风险，安全审计时委派","prompt":"只输出 JSON 数组：file,line,severity,rule,evidence,suggestion","tools":["Read","Grep","Glob"],"model":"haiku"}}'

REPOS=(./repos/auth-svc ./repos/payment-svc)

run_one() {
  local repo="$1"
  local name; name="$(basename "$repo")"
  claude --bare -p "审计 $repo 目录的安全风险。" \
    --agents "$AGENTS_JSON" \
    --agent security-scanner \
    --output-format json \
    --allowedTools "Read,Grep,Glob" \
    > "./audit-out/${name}__security.json"
}
export -f run_one; export AGENTS_JSON
mkdir -p audit-out
printf '%s\n' "${REPOS[@]}" | xargs -n1 -P4 -I{} bash -c 'run_one "$@"' _ {}
```

**聚合脚本 `aggregate.sh`**：

```bash
#!/usr/bin/env bash
jq -s 'map({
  repo: (input_filename | split("/") | last | split("__")[0]),
  cost: .total_cost_usd,
  findings: (.result | fromjson? // [])
}) | {total_cost: (map(.cost)|add), reports: .}' \
  audit-out/*.json > audit-report.json
echo "总花费：$(jq '.total_cost' audit-report.json) USD"
echo "高危项数：$(jq '[.reports[].findings[]? | select(.severity=="critical" or .severity=="high")] | length' audit-report.json)"
```

**执行效果**：

```bash
chmod +x run-audit.sh aggregate.sh
./run-audit.sh        # 并发跑完所有仓库
✓ auth-svc :: security-scanner
✓ payment-svc :: security-scanner
./aggregate.sh        # 出总报告
总花费：0.0124 USD
高危项数：3
```

最终的端到端时序如下：

```mermaid
sequenceDiagram
    participant U as 运维/CI
    participant S as run-audit.sh
    participant C as claude -p
    participant A as 子代理 security-scanner
    participant H as Hooks
    U->>S: 启动批量
    loop 每个仓库 (并发 -P4)
        S->>C: claude --bare -p --agent security-scanner
        C->>H: UserPromptSubmit（注入分支/HEAD）
        H-->>C: additionalContext
        C->>A: 委派审计任务
        A->>A: Read/Grep/Glob 扫描
        A-->>C: JSON 结论
        C->>H: PostToolUse（记委派日志）
        C-->>S: .result / structured_output
        S->>S: 落 audit-out/*.json
    end
    S->>U: 全部完成
    U->>S: aggregate.sh
    S-->>U: audit-report.json + 总花费 + 高危数
```

---

## 进阶技巧

- **`--agent` 跑主代理时配 `initialPrompt`**：在子代理 frontmatter 写 `initialPrompt: "审计当前目录"`，配合 `--agent <name>` 即可不传提示词直接 `claude -p --agent security-scanner`，进一步脚本化。
- **模型解析优先级**：`CLAUDE_CODE_SUBAGENT_MODEL` 环境变量 > 单次调用 `model` 参数 > 子代理 frontmatter `model` > 主会话模型。批量场景可用环境变量临时把所有子代理降到 Haiku 做快速预扫。
- **`isolation: worktree`**：给可能改文件的子代理（如"自动修复"角色）设此字段，让它在临时 git worktree 里干活，不污染主分支，无改动时自动清理。
- **禁用内置子代理**：批量 CI 里若只想用自己的角色，设 `CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS=1`，去掉 Explore/Plan 等内置代理，避免误委派。
- **断点续跑**：捕获 `session_id` 后用 `--resume` 续跑长任务——`session_id=$(claude -p "..." --output-format json | jq -r .session_id)`，再 `claude -p "继续" --resume "$session_id"`。注意 session ID 按项目目录作用域，须在同一目录下 resume。

---

## 延伸阅读

- Claude Code 官方文档 · Create custom subagents：`https://code.claude.com/docs/en/sub-agents`
- Claude Code 官方文档 · Run Claude Code programmatically（headless）：`https://code.claude.com/docs/en/headless`
- Claude Code 官方文档 · Automate actions with hooks：`https://code.claude.com/docs/en/hooks-guide`
- Claude Code 官方文档 · Run agent teams / Dynamic workflows：`https://code.claude.com/docs/en/sub-agents`（同域"Agents and parallel work"章节）
- Agent SDK（Python/TypeScript 编程式控制）：`https://code.claude.com/docs/en/headless` 末尾"Next steps"链接

---

## 训练营适用性评估

- **适合训练营类型**：DevOps/SRE 自动化营、研发效能（工程效率）营、安全审计营、AI 工程实战营。具备"定义→批量→聚合→可视化"的完整闭环，适合作为中期到后期的综合项目。
- **教学时长**：建议 6–8 课时。其中子代理定义与 frontmatter 约 1.5 课时，`-p`/`--agent`/`--output-format` 实操约 2 课时，批量脚本与 `jq` 聚合约 2 课时，Hooks 与 stream-json 实时看板约 1.5 课时，综合实战与复盘约 1 课时。
- **前置依赖**：学员应先掌握命令行基础、`jq`、JSON Schema 概念，以及至少跑通过一次 `claude -p`。可作为"无头模式入门"之后的进阶主项目。
- **可裁剪路径**：若课时紧张，先去掉 Hooks 与 stream-json 两节，保留"三子代理 + 批量 + 聚合"主线即可形成一个完整可交付的作业；Hooks 与流式作为加分项。

---

## 参考来源

1. [Create custom subagents — Claude Code Docs](https://code.claude.com/docs/en/sub-agents) — 子代理 frontmatter 全字段、作用域优先级、`--agents`/`--agent` 启动方式与委派机制的官方权威说明。
2. [Run Claude Code programmatically — Claude Code Docs](https://code.claude.com/docs/en/headless) — `-p` 无头模式、`--bare`、`--output-format json/stream-json`、`--json-schema`、`--allowedTools`、API 重试事件等自动化核心能力。
3. [Automate actions with hooks / Hooks reference — Claude Code Docs](https://code.claude.com/docs/en/hooks) — `UserPromptSubmit`/`PostToolUse`/`Stop` 事件、`agent_type` 字段与 hook 输出契约。
4. [Claude Code Subagents: The Complete Guide — ComputingForGeeks](https://computingforgeeks.com/claude-code-subagents-guide/) — `.claude/agents` frontmatter schema 与自动委派、工具受限的实操补充。
5. [Claude Code stream-json: the output format that changes everything — backgroundclaude](https://backgroundclaude.com/blog/stream-json) — 换行分隔 JSON 事件流在实时进度追踪与自定义看板中的应用解析。

> [!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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
