通过子代理 (Subagents) 在主代理中隔离专注子任务、并行执行分析、应用专用指令而不会膨胀主提示词。使用 agents 参数定义,必须将 Agent 工具加入 allowedTools 列表。子代理继承主会话的 CLAUDE.md 和工具定义,但上下文完全独立,最终消息返回给父代理。可以通过自动匹配描述或显式按名称调用来委托任务,也支持动态创建和恢复会话。

Claude Agent SDK 子代理 (Subagents) 配置与使用

定义和调用子代理来隔离上下文、并行运行任务,并在 Claude Agent SDK 应用中应用专用指令。

子代理是主代理可以生成的独立代理实例,用于处理专项子任务。使用子代理可以隔离上下文、并行执行多项分析,并应用专用指令而不会使主代理的提示词变得臃肿。

本指南说明如何在 SDK 中使用 agents 参数定义和使用子代理。

概述

有三种方式创建子代理:

  • 编程方式:在 query() 选项中使用 agents 参数(TypeScriptPython
  • 基于文件系统:在 .claude/agents/ 目录中定义 Markdown 文件(参见将子代理定义为文件
  • 内置通用代理:无需定义,Claude 可通过 Agent 工具随时调用内置的 general-purpose 子代理

本指南聚焦于编程方式,推荐用于 SDK 应用。

定义子代理后,Claude 会根据每个子代理的 description 字段决定是否调用。编写清晰的描述,说明何时应使用该子代理,Claude 会自动委派适当任务。你还可以在提示词中显式按名称请求子代理(例如“使用 code-reviewer 代理来…”)。

使用子代理的好处

上下文隔离

每个子代理在其自身全新的对话中运行。中间的工具调用和结果保留在子代理内部;只有最终消息返回给父代理。详见子代理继承的内容

示例:一个 research-assistant 子代理可以探索数十个文件,而这些文件内容不会堆积在主对话中。父代理收到的是简洁的摘要,而不是子代理读取的每个文件。

并行化

多个子代理可以同时运行,显著加快复杂工作流的速度。

示例:在代码审查期间,可以同时运行 style-checkersecurity-scannertest-coverage 子代理,将审查时间从几分钟缩短到几秒。

专用指令和知识

每个子代理可以有其定制的系统提示词,包含特定的专业知识、最佳实践和约束条件。

示例:一个 database-migration 子代理可以包含关于 SQL 最佳实践、回滚策略和数据完整性检查的详细知识,这些信息在主代理指令中会是多余的噪音。

工具限制

子代理可以限制在特定的工具范围内,减少意外操作的风险。

示例:一个 doc-reviewer 子代理可能只拥有 Read 和 Grep 工具,确保它能分析文档但永远不会意外修改文档文件。

创建子代理

编程方式定义(推荐)

直接在代码中使用 agents 参数定义子代理。下面的示例创建两个子代理:一个只读的代码审查器和一个可以执行命令的测试运行器。必须将 Agent 工具包含在 allowedTools 中,因为 Claude 通过 Agent 工具调用子代理。

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

async def main():
    async for message in query(
        prompt="Review the authentication module for security issues",
        options=ClaudeAgentOptions(
            # Agent tool is required for subagent invocation
            allowed_tools=["Read", "Grep", "Glob", "Agent"],
            agents={
                "code-reviewer": AgentDefinition(
                    # description tells Claude when to use this subagent
                    description="Expert code review specialist. Use for quality, security, and maintainability reviews.",
                    # prompt defines the subagent's behavior and expertise
                    prompt="""You are a code review specialist with expertise in security, performance, and best practices.

When reviewing code:
- Identify security vulnerabilities
- Check for performance issues
- Verify adherence to coding standards
- Suggest specific improvements

Be thorough but concise in your feedback.""",
                    # tools restricts what the subagent can do (read-only here)
                    tools=["Read", "Grep", "Glob"],
                    # model overrides the default model for this subagent
                    model="sonnet",
                ),
                "test-runner": AgentDefinition(
                    description="Runs and analyzes test suites. Use for test execution and coverage analysis.",
                    prompt="""You are a test execution specialist. Run tests and provide clear analysis of results.

Focus on:
- Running test commands
- Analyzing test output
- Identifying failing tests
- Suggesting fixes for failures""",
                    # Bash access lets this subagent run test commands
                    tools=["Bash", "Read", "Grep"],
                ),
            },
        ),
    ):
        if hasattr(message, "result"):
            print(message.result)

asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Review the authentication module for security issues",
  options: {
    // Agent tool is required for subagent invocation
    allowedTools: ["Read", "Grep", "Glob", "Agent"],
    agents: {
      "code-reviewer": {
        // description tells Claude when to use this subagent
        description:
          "Expert code review specialist. Use for quality, security, and maintainability reviews.",
        // prompt defines the subagent's behavior and expertise
        prompt: `You are a code review specialist with expertise in security, performance, and best practices.

When reviewing code:
- Identify security vulnerabilities
- Check for performance issues
- Verify adherence to coding standards
- Suggest specific improvements

Be thorough but concise in your feedback.`,
        // tools restricts what the subagent can do (read-only here)
        tools: ["Read", "Grep", "Glob"],
        // model overrides the default model for this subagent
        model: "sonnet"
      },
      "test-runner": {
        description:
          "Runs and analyzes test suites. Use for test execution and coverage analysis.",
        prompt: `You are a test execution specialist. Run tests and provide clear analysis of results.

Focus on:
- Running test commands
- Analyzing test output
- Identifying failing tests
- Suggesting fixes for failures`,
        // Bash access lets this subagent run test commands
        tools: ["Bash", "Read", "Grep"]
      }
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}

AgentDefinition 配置字段

字段 类型 必填 说明
description string 何时使用该代理的自然语言描述
prompt string 代理的系统提示词,定义其角色和行为
tools string[] 允许的工具名称数组。如果省略,则继承所有工具
disallowedTools string[] 要从代理工具集中移除的工具名称数组
model string 该代理的模型覆盖。接受别名如 'sonnet''opus''haiku''inherit' 或完整模型 ID。默认为主模型
skills string[] 启动时预加载到代理上下文中的技能名称列表。未列出的技能仍可通过 Skill 工具调用
memory 'user' | 'project' | 'local' 该代理的记忆源
mcpServers (string | object)[] 该代理可用的 MCP 服务器,按名称或内联配置
maxTurns number 代理停止前最大的代理轮次数量
background boolean 调用时作为非阻塞后台任务运行此代理
effort 'low' | 'medium' | 'high' | 'xhigh' | 'max' | number 该代理的推理努力级别
permissionMode PermissionMode 该代理内工具执行的权限模式

在 Python SDK 中,这些字段名称使用 camelCase 以匹配线格式。详见 AgentDefinition 参考

子代理不能生成自己的子代理。不要在子代理的 tools 数组中包含 Agent

基于文件系统定义(备选)

也可以在 .claude/agents/ 目录中将子代理定义为 Markdown 文件。详见 Claude Code 子代理文档。编程定义的代理优先于具有相同名称的基于文件系统的代理。

即使没有定义自定义子代理,当 Agent 存在于 allowedTools 中时,Claude 也可以生成内置的 general-purpose 子代理。这对于委托研究或探索任务而无需创建专门代理非常有用。

子代理继承的内容

子代理的上下文窗口从头开始(没有父对话),但并非为空。从父代理到子代理的唯一通道是 Agent 工具的提示字符串,因此请将所有需要的文件路径、错误消息或决定直接包含在该提示中。

子代理接收 子代理不接收
自身的系统提示词(AgentDefinition.prompt)和 Agent 工具的提示 父代理的对话历史或工具结果
项目 CLAUDE.md(通过 settingSources 加载) 预加载的技能内容,除非在 AgentDefinition.skills 中列出
工具定义(从父代理继承,或 tools 的子集) 父代理的系统提示词

父代理以 Agent 工具结果的原样接收子代理的最终消息,但可能会在其自己的响应中总结。若要在面向用户的响应中逐字保留子代理输出,请在传递给 query() 调用的 promptsystemPrompt 选项中包含一条指令。

调用子代理

自动调用

Claude 根据任务和每个子代理的 description 自动决定何时调用子代理。例如,如果你定义了一个 performance-optimizer 子代理,描述为“性能优化专家(用于查询调优)”,那么当你的提示词提到优化查询时,Claude 将调用它。

编写清晰、具体的描述,以便 Claude 能够将任务匹配到正确的子代理。

显式调用

为确保 Claude 使用特定的子代理,在提示词中按名称提及它:

"Use the code-reviewer agent to check the authentication module"

这绕过了自动匹配,直接调用命名的子代理。

动态代理配置

可以根据运行时条件动态创建代理定义。下面的示例创建一个安全审查器,具有不同的严格级别,对严格审查使用更强大的模型。

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

# Factory function that returns an AgentDefinition
# This pattern lets you customize agents based on runtime conditions
def create_security_agent(security_level: str) -> AgentDefinition:
    is_strict = security_level == "strict"
    return AgentDefinition(
        description="Security code reviewer",
        # Customize the prompt based on strictness level
        prompt=f"You are a {'strict' if is_strict else 'balanced'} security reviewer...",
        tools=["Read", "Grep", "Glob"],
        # Key insight: use a more capable model for high-stakes reviews
        model="opus" if is_strict else "sonnet",
    )

async def main():
    async for message in query(
        prompt="Review this PR for security issues",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Grep", "Glob", "Agent"],
            agents={
                "security-reviewer": create_security_agent("strict")
            },
        ),
    ):
        if hasattr(message, "result"):
            print(message.result)

asyncio.run(main())
import { query, type AgentDefinition } from "@anthropic-ai/claude-agent-sdk";

// Factory function that returns an AgentDefinition
// This pattern lets you customize agents based on runtime conditions
function createSecurityAgent(securityLevel: "basic" | "strict"): AgentDefinition {
  const isStrict = securityLevel === "strict";
  return {
    description: "Security code reviewer",
    // Customize the prompt based on strictness level
    prompt: `You are a ${isStrict ? "strict" : "balanced"} security reviewer...`,
    tools: ["Read", "Grep", "Glob"],
    // Key insight: use a more capable model for high-stakes reviews
    model: isStrict ? "opus" : "sonnet"
  };
}

// The agent is created at query time, so each request can use different settings
for await (const message of query({
  prompt: "Review this PR for security issues",
  options: {
    allowedTools: ["Read", "Grep", "Glob", "Agent"],
    agents: {
      "security-reviewer": createSecurityAgent("strict")
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}

检测子代理调用

子代理通过 Agent 工具调用。要检测子代理何时被调用,请检查 tool_use 块中 name"Agent" 的情况。来自子代理上下文内部的消息包含 parent_tool_use_id 字段。

工具名称在 Claude Code v2.1.63 中从 "Task" 重命名为 "Agent"。当前的 SDK 版本在 tool_use 块中发出 "Agent",但在 system:init 工具列表和 result.permission_denials[].tool_name 中仍然使用 "Task"。在 block.name 中检查两个值可以确保跨 SDK 版本的兼容性。

以下示例迭代流式消息,在子代理被调用时记录,以及后续消息是否来自该子代理执行上下文。

不同 SDK 的消息结构不同。在 Python 中,通过 message.content 直接访问内容块。在 TypeScript 中,SDKAssistantMessage 包装了 Claude API 消息,因此通过 message.message.content 访问内容。

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolUseBlock

async def main():
    async for message in query(
        prompt="Use the code-reviewer agent to review this codebase",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Glob", "Grep", "Agent"],
            agents={
                "code-reviewer": AgentDefinition(
                    description="Expert code reviewer.",
                    prompt="Analyze code quality and suggest improvements.",
                    tools=["Read", "Glob", "Grep"],
                )
            },
        ),
    ):
        # Check for subagent invocation. Match both names: older SDK
        # versions emitted "Task", current versions emit "Agent".
        if hasattr(message, "content") and message.content:
            for block in message.content:
                if isinstance(block, ToolUseBlock) and block.name in ("Task", "Agent"):
                    print(f"Subagent invoked: {block.input.get('subagent_type')}")

        # Check if this message is from within a subagent's context
        if hasattr(message, "parent_tool_use_id") and message.parent_tool_use_id:
            print("  (running inside subagent)")

        if hasattr(message, "result"):
            print(message.result)

asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Use the code-reviewer agent to review this codebase",
  options: {
    allowedTools: ["Read", "Glob", "Grep", "Agent"],
    agents: {
      "code-reviewer": {
        description: "Expert code reviewer.",
        prompt: "Analyze code quality and suggest improvements.",
        tools: ["Read", "Glob", "Grep"]
      }
    }
  }
})) {
  const msg = message as any;

  // Check for subagent invocation. Match both names: older SDK versions
  // emitted "Task", current versions emit "Agent".
  for (const block of msg.message?.content ?? []) {
    if (block.type === "tool_use" && (block.name === "Task" || block.name === "Agent")) {
      console.log(`Subagent invoked: ${block.input.subagent_type}`);
    }
  }

  // Check if this message is from within a subagent's context
  if (msg.parent_tool_use_id) {
    console.log("  (running inside subagent)");
  }

  if ("result" in message) {
    console.log(message.result);
  }
}

恢复子代理

可以恢复子代理以继续之前的执行。恢复的子代理保留其完整的对话历史,包括所有之前的工具调用、结果和推理。子代理从停止的地方继续,而不是从头开始。

当子代理完成时,Claude 在 Agent 工具结果中收到其代理 ID。要以编程方式恢复子代理:

  1. 捕获会话 ID:在第一次查询期间从消息中提取 session_id
  2. 提取代理 ID:从消息内容中解析 agentId
  3. 恢复会话:在第二次查询的选项中传递 resume: sessionId,并在提示中包含代理 ID

必须恢复同一个会话才能访问子代理的对话记录。每个 query() 调用默认启动一个新会话,因此传递 resume: sessionId 以继续同一会话。

如果你使用自定义代理(而非内置代理),还必须在两个查询的 agents 参数中传递相同的代理定义。

下面的示例演示了此流程:第一个查询运行一个子代理并捕获会话 ID 和代理 ID,然后第二个查询恢复会话以请求需要第一次分析上下文的后续问题。

import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";

// Helper to extract agentId from message content
// Stringify to avoid traversing different block types (TextBlock, ToolResultBlock, etc.)
function extractAgentId(message: SDKMessage): string | undefined {
  if (message.type !== "assistant" && message.type !== "user") return undefined;
  const content = JSON.stringify(message.message.content);
  const match = content.match(/agentId:\s*([a-f0-9-]+)/);
  return match?.[1];
}

let agentId: string | undefined;
let sessionId: string | undefined;

// First invocation - use the Explore agent to find API endpoints
for await (const message of query({
  prompt: "Use the Explore agent to find all API endpoints in this codebase",
  options: { allowedTools: ["Read", "Grep", "Glob", "Agent"] }
})) {
  if ("session_id" in message) sessionId = message.session_id;
  const extractedId = extractAgentId(message);
  if (extractedId) agentId = extractedId;
  if ("result" in message) console.log(message.result);
}

// Second invocation - resume and ask follow-up
if (agentId && sessionId) {
  for await (const message of query({
    prompt: `Resume agent ${agentId} and list the top 3 most complex endpoints`,
    options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], resume: sessionId }
  })) {
    if ("result" in message) console.log(message.result);
  }
}
import asyncio
import json
import re
from claude_agent_sdk import query, ClaudeAgentOptions

def extract_agent_id(text: str) -> str | None:
    """Extract agentId from Agent tool result text."""
    match = re.search(r"agentId:\s*([a-f0-9-]+)", text)
    return match.group(1) if match else None

async def main():
    agent_id = None
    session_id = None

    # First invocation - use the Explore agent to find API endpoints
    async for message in query(
        prompt="Use the Explore agent to find all API endpoints in this codebase",
        options=ClaudeAgentOptions(allowed_tools=["Read", "Grep", "Glob", "Agent"]),
    ):
        if hasattr(message, "session_id"):
            session_id = message.session_id
        if hasattr(message, "content"):
            content_str = json.dumps(message.content, default=str)
            extracted = extract_agent_id(content_str)
            if extracted:
                agent_id = extracted
        if hasattr(message, "result"):
            print(message.result)

    # Second invocation - resume and ask follow-up
    if agent_id and session_id:
        async for message in query(
            prompt=f"Resume agent {agent_id} and list the top 3 most complex endpoints",
            options=ClaudeAgentOptions(
                allowed_tools=["Read", "Grep", "Glob", "Agent"], resume=session_id
            ),
        ):
            if hasattr(message, "result"):
                print(message.result)

asyncio.run(main())

子代理的对话记录独立于主对话存在:

  • 主对话压缩:主对话压缩时,子代理对话记录不受影响,它们存储在单独的文件中。
  • 会话持久性:子代理对话记录在其会话内持久存在。可以通过恢复同一会话在重启 Claude Code 后恢复子代理。
  • 自动清理:对话记录根据 cleanupPeriodDays 设置(默认 30 天)自动清理。

工具限制

可以通过 tools 字段限制子代理的工具访问:

  • 省略该字段:代理继承所有可用工具(默认)
  • 指定工具:代理只能使用列出的工具

以下示例创建一个只读分析代理,可以检查代码但不能修改文件或运行命令。

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

async def main():
    async for message in query(
        prompt="Analyze the architecture of this codebase",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Grep", "Glob", "Agent"],
            agents={
                "code-analyzer": AgentDefinition(
                    description="Static code analysis and architecture review",
                    prompt="""You are a code architecture analyst. Analyze code structure,
identify patterns, and suggest improvements without making changes.""",
                    # Read-only tools: no Edit, Write, or Bash access
                    tools=["Read", "Grep", "Glob"],
                )
            },
        ),
    ):
        if hasattr(message, "result"):
            print(message.result)

asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Analyze the architecture of this codebase",
  options: {
    allowedTools: ["Read", "Grep", "Glob", "Agent"],
    agents: {
      "code-analyzer": {
        description: "Static code analysis and architecture review",
        prompt: `You are a code architecture analyst. Analyze code structure,
identify patterns, and suggest improvements without making changes.`,
        // Read-only tools: no Edit, Write, or Bash access
        tools: ["Read", "Grep", "Glob"]
      }
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}

常用工具组合

使用场景 工具 说明
只读分析 Read, Grep, Glob 可以检查代码但不能修改或执行
测试执行 Bash, Read, Grep 可以运行命令并分析输出
代码修改 Read, Edit, Write, Grep, Glob 具有完整的读写权限,不能执行命令
完整访问 所有工具 从父代理继承所有工具(省略 tools 字段)

故障排查

Claude 没有将任务委托给子代理

如果 Claude 直接完成任务而不是委托给你的子代理:

  1. 包含 Agent 工具:子代理通过 Agent 工具调用,因此必须在 allowedTools
  2. 使用显式提示:在提示中按名称提及子代理(例如“使用 code-reviewer 代理来…”)
  3. 编写清晰的描述:准确解释何时应使用该子代理,以便 Claude 正确匹配任务

基于文件系统的代理未加载

.claude/agents/ 中定义的代理仅在启动时加载。如果在 Claude Code 运行时创建新的代理文件,请重新启动会话以加载它。

Windows 上提示过长失败

在 Windows 上,提示非常长的子代理可能因命令行长度限制(8191 个字符)而失败。保持提示简洁,或对复杂指令使用基于文件系统的代理。

相关文档

常见问题

子代理怎么继承主代理的配置?

子代理继承项目的 CLAUDE.md(通过 settingSources 加载)和从父代理继承的工具定义(除非在 tools 字段中指定子集)。子代理的上下文窗口从头开始,不会看到主对话历史,但可以通过 Agent 工具提示显式传递所需信息。

子代理可以互相调用吗?

不能。子代理不能生成自己的子代理,不要在子代理的 tools 数组中包含 Agent 工具。如果要实现多层委托,需要从主代理层面手动设计工作流。

子代理调用后怎么恢复之前的会话?

捕获第一次查询中的 session_id 和子代理返回的 agentId,然后在第二次查询的选项中设置 resume: sessionId 并在提示中包含代理 ID。必须恢复同一个会话,并且如果使用了自定义代理,需要在两个查询中传递相同的 agents 定义。