首页 › tutorials › 自定义子代理:无头批量派活实战

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


前置条件


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

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

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

整套协作模型如下:

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

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


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

子代理就是带 YAML frontmatter 的 Markdown 文件,存放在:

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

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

security-scanner.md(用最快最便宜的 Haiku,只读):

---
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):

---
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:

---
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:委派式(主会话编排多个角色)

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

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

方式 B:主代理式(一个角色跑完整任务)

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

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

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

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

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:

#!/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 干扰,保证多机一致。

聚合成一个总报告

# 把所有单仓 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 里:

{
  "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 里追加(让安全子代理每次跑完都落一份痕迹):

---
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、语言统计)注入每条提示,省去每次手写:

{
  "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 实时拿到事件流,边跑边看进度。

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

关键事件类型:

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

... | 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 友好,不依赖磁盘文件):

#!/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:

#!/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)"

执行效果:

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

最终的端到端时序如下:

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 + 总花费 + 高危数

进阶技巧


延伸阅读


训练营适用性评估


参考来源

  1. Create custom subagents — Claude Code Docs — 子代理 frontmatter 全字段、作用域优先级、--agents/--agent 启动方式与委派机制的官方权威说明。
  2. Run Claude Code programmatically — Claude Code Docs — -p 无头模式、--bare、--output-format json/stream-json、--json-schema、--allowedTools、API 重试事件等自动化核心能力。
  3. Automate actions with hooks / Hooks reference — Claude Code Docs — UserPromptSubmit/PostToolUse/Stop 事件、agent_type 字段与 hook 输出契约。
  4. Claude Code Subagents: The Complete Guide — ComputingForGeeks — .claude/agents frontmatter schema 与自动委派、工具受限的实操补充。
  5. Claude Code stream-json: the output format that changes everything — backgroundclaude — 换行分隔 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学通信知识星球海报-扫码加入