前三篇搓出了一个能执行命令、会列计划、还能按需加载知识的 agent。一个人干活,从头干到尾。

大部分时候这没问题。但有些场景会让单个 agent 很为难。

比如用户说"帮我用 Java 搭一个 Spring Boot 项目,然后写个 README"。这里面涉及两个差异很大的领域:Java 架构设计和文档写作。一个 agent 要在同一个上下文里同时扮演 Java 架构师和技术写手,效果往往不太好——上下文越长,模型的注意力越分散,每个领域都只能给出及格水平的回答。

Claude Code 的做法是派 sub-agent。主 agent 负责理解用户意图、拆解任务,然后把具体的子任务委派给专门的 sub-agent 去执行。每个 sub-agent 有自己独立的上下文和专业知识,干完活把结果交回来。

这篇来实现这个机制。

Skill 和 Sub-Agent 的区别

上一篇的 skill 是"给 agent 一本参考书"。agent 读完之后,还是自己动手干活。知识注入到了主 agent 的上下文里,占用的是主 agent 的 token 预算。

Sub-agent 不一样。它是一个独立的 agent 实例,有自己的 system prompt、自己的消息历史、自己的工具集。主 agent 只需要告诉它"做什么",然后等结果就行。

打个比方:skill 像是程序员翻文档,sub-agent 像是把活儿派给另一个程序员。

SubAgentLoader:和 SkillLoader 几乎一样

sub-agent 的定义方式和 skill 一样——一个 markdown 文件,YAML front matter 写元信息,正文写 system prompt:

---
name: java-architect
description: "When specified for use"
---

You are a senior Java architect with deep expertise in Java 17+ LTS
and the enterprise Java ecosystem...

SubAgentLoader 的代码和上一篇的 SkillLoader 结构几乎一模一样:启动时扫描 sub_agents/ 目录,解析所有 .md 文件,提供 get_descriptions()get_body() 两个接口。

区别在于 get_body() 的返回值。skill 返回的是知识内容,会注入到主 agent 的上下文里。sub-agent 返回的是 system prompt,会用来初始化一个新的 agent 实例。

run_subagent_task:核心工具

这是整篇文章最关键的部分。run_subagent_task 这个工具做的事情是:接收 sub-agent 名字和任务描述,启动一个独立的 agent 循环,跑完后把结果返回。

MAX_SUBAGENT_ROUNDS = 20

@tool
def run_subagent_task(sub_agent_name: str, task: str) -> str:
    """Delegate a subtask to a specialized subagent and return its summary."""

    sub_agent_system_prompt = sub_agent_loader.get_body(sub_agent_name)
    sub_llm = ChatOpenAI(
        model="deepseek-chat",
        base_url="https://api.deepseek.com/v1",
        api_key=os.getenv("DEEPSEEK_API_KEY"),
    ).bind_tools(SUBAGENT_TOOLS)

    messages: list[BaseMessage] = [
        SystemMessage(content=sub_agent_system_prompt),
        HumanMessage(content=task),
    ]

    for round_num in range(MAX_SUBAGENT_ROUNDS):
        response = sub_llm.invoke(messages)
        messages.append(response)

        if not response.tool_calls:
            return response.content or "[subagent finished without output]"

        for tool_call in response.tool_calls:
            result = subagent_tools_dict[tool_call["name"]].invoke(tool_call["args"])
            messages.append(ToolMessage(
                content=result,
                tool_call_id=tool_call["id"],
            ))

    return "Error: subagent reached max rounds limit (20)"

逐段看。

首先,根据 sub-agent 名字拿到对应的 system prompt,创建一个新的 LLM 实例。注意这里是新建的,不是复用主 agent 的 llm。每个 sub-agent 有自己独立的模型实例。

然后,构建初始消息列表:system prompt + 任务描述。这就是 sub-agent 的全部上下文,干净利落,不带主 agent 的历史包袱。

接下来是一个 for 循环,最多跑 20 轮。每轮的逻辑和主 agent 一样:调用模型,如果有 tool_calls 就执行,没有就返回结果。

MAX_SUBAGENT_ROUNDS = 20 是安全阀。万一 sub-agent 陷入死循环(比如反复执行同一个命令),20 轮后强制终止。

防止无限递归

这里有一个容易忽略的细节。看工具列表的定义:

SUBAGENT_TOOLS = [run_bash, run_read_file, run_write_file, run_edit_file]
subagent_tools_dict = {t.name: t for t in SUBAGENT_TOOLS}

ALL_TOOLS = SUBAGENT_TOOLS + [run_subagent_task]
all_tools_dict = {t.name: t for t in ALL_TOOLS}

主 agent 的工具列表是 ALL_TOOLS,包含 run_subagent_task。sub-agent 的工具列表是 SUBAGENT_TOOLS,不包含 run_subagent_task

为什么?如果 sub-agent 也能调用 run_subagent_task,它就可以再派一个 sub-agent,那个 sub-agent 又可以再派……无限递归,直到内存爆掉或者 API 费用爆掉。

所以 sub-agent 只有基础工具:跑命令、读文件、写文件、编辑文件。它能干活,但不能再分活儿。

独立上下文的好处

sub-agent 最大的价值不是"多了一个帮手",而是上下文隔离。

主 agent 的消息历史可能已经很长了——之前的对话、之前的工具调用结果、各种中间状态。在这么长的上下文里让模型去做一个专业任务,效果会打折扣。

sub-agent 的上下文是全新的。只有一个专业的 system prompt 和一个明确的任务描述。模型的全部注意力都集中在这个任务上。

而且 sub-agent 执行过程中产生的中间消息(工具调用、工具结果)不会污染主 agent 的上下文。主 agent 只拿到最终的总结结果,一段干净的文本。

实际运行的样子

假设 sub_agents/ 目录下有一个 java-architect.md,用户输入"帮我设计一个 Spring Boot 项目的目录结构":

[用户输入] 帮我设计一个 Spring Boot 项目的目录结构

[工具调用 - 第 1 轮]
  - 调用工具: run_subagent_task
    参数: {'sub_agent_name': 'java-architect', 'task': '设计一个 Spring Boot 项目的标准目录结构'}
  [subagent 第 1 轮]
    - 调用工具: run_bash({'command': 'mkdir -p src/main/java/com/example/{config,controller,service,repository,model}'})
      结果: (no output)
  [subagent 第 2 轮]
    - 调用工具: run_write_file({'path': 'src/main/resources/application.yml', ...})
      结果: Wrote 245 bytes to src/main/resources/application.yml
    结果: 已按照 Spring Boot 最佳实践创建了标准目录结构...

[最终回复] Java 架构师已经帮你创建好了项目结构...

主 agent 决定把任务委派给 java-architect,sub-agent 独立执行了两轮工具调用,最后把总结返回给主 agent。主 agent 再把结果转述给用户。

和前三篇的对比

第一篇给了 agent 一双手。第二篇给了它一个计划本。第三篇给了它一个书架。这一篇给了它一个团队。

到这里,我们的 agent 已经具备了 Claude Code 的几个核心能力:工具调用、任务规划、知识加载、子任务委派。当然,真正的 Claude Code 在每个环节上都做了大量的工程优化——流式输出、并发控制、错误恢复、权限管理。但骨架是一样的。