Skip to content

通过子代理 (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 工具调用子代理。

python
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())
typescript
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 配置字段

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

在 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 使用特定的子代理,在提示词中按名称提及它:

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

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

动态代理配置

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

python
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())
typescript
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 访问内容。

python
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())
typescript
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,然后第二个查询恢复会话以请求需要第一次分析上下文的后续问题。

typescript
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);
  }
}
python
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 字段限制子代理的工具访问:

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

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

python
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())
typescript
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 定义。