ag2-subagent-delegation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Subagent delegation

子代理委派

When to use

适用场景

  • "Coordinator + specialists" — a parent agent should hand parts of a task to a research agent, math agent, etc.
  • "Fan out then collect" — multi-part questions where each part is independent and parallel execution saves wall time.
  • "Self-delegation" — one agent breaks complex work into focused sub-tasks for itself.
  • "协调者+专家"——父代理应将任务的部分内容交给研究代理、数学代理等处理。
  • "扇出后收集"——多部分问题,每个部分相互独立,并行执行可节省时间。
  • "自我委派"——单个代理将复杂工作拆分为专注的子任务自行处理。

Two patterns

两种模式

PatternReach for it whenAPI
Auto-injected
run_subtask
/
run_subtasks
Lightweight self-delegation, dynamic fan-out, parallel sub-questions
tasks=TaskConfig(...)
on the parent
Agent.as_tool()
Distinct named delegates the LLM should reason about ("call the researcher", "call the writer")Wrap a child Agent as a tool on the parent
The two compose — a coordinator can have both.
模式适用场景API
自动注入
run_subtask
/
run_subtasks
轻量级自我委派、动态扇出、并行子问题父代理配置
tasks=TaskConfig(...)
Agent.as_tool()
需由LLM明确调用的独立命名代理(如"调用研究代理"、"调用写作代理")将子Agent包装为父代理的工具
两种模式可组合使用——协调者可同时具备两种能力。

Pattern 1 — auto-injected
run_subtasks

模式1——自动注入的
run_subtasks

Subtask tools are off by default (
tasks=False
). Opt in with
tasks=TaskConfig(...)
and the agent gains:
  • run_subtask(task: str)
    — one isolated sub-task agent.
  • run_subtasks(tasks: list[str], parallel: bool = True)
    — fan out multiple in one tool call (default concurrent).
python
from ag2 import Agent, TaskConfig
from ag2.config import GeminiConfig

config = GeminiConfig(model="gemini-3-flash-preview")

coordinator = Agent(
    "coordinator",
    prompt=(
        "You answer multi-part questions by dispatching run_subtasks "
        "with parallel=True. Use one tool call with every sub-question "
        "packed into the 'tasks' list."
    ),
    config=config,
    tasks=TaskConfig(),  # opt in
)

reply = await coordinator.ask(
    "In one run_subtasks call, answer: "
    "(a) tallest waterfall, (b) Eiffel Tower year, (c) boiling point of nitrogen."
)
TaskConfig
controls how the sub-task agents are built:
python
@dataclass
class TaskConfig:
    config: ModelConfig | None = None    # falls back to parent's config
    prompt: str = "You are a task agent..."
    include_tools: Iterable[str] | None = None   # None = inherit all parent tools
    exclude_tools: Iterable[str] = ()
    extra_tools: Iterable[Callable | Tool] = ()
Common shape — cheaper model for sub-tasks, narrow tool surface:
python
TaskConfig(
    config=worker_config,                  # smaller model
    prompt="You are a focused worker; one step only.",
    include_tools=["search", "fetch_url"], # don't expose `summarize` to children
)
Sub-task agents are built with
tasks=False
— they never gain
run_subtask
tools themselves. Recursive delegation is structurally impossible; no depth limit needed.
子任务工具默认关闭(
tasks=False
)。通过
tasks=TaskConfig(...)
启用后,代理将获得:
  • run_subtask(task: str)
    ——创建一个独立的子任务代理。
  • run_subtasks(tasks: list[str], parallel: bool = True)
    ——一次工具调用中执行多个子任务(默认并发执行)。
python
from ag2 import Agent, TaskConfig
from ag2.config import GeminiConfig

config = GeminiConfig(model="gemini-3-flash-preview")

coordinator = Agent(
    "coordinator",
    prompt=(
        "You answer multi-part questions by dispatching run_subtasks "
        "with parallel=True. Use one tool call with every sub-question "
        "packed into the 'tasks' list."
    ),
    config=config,
    tasks=TaskConfig(),  # opt in
)

reply = await coordinator.ask(
    "In one run_subtasks call, answer: "
    "(a) tallest waterfall, (b) Eiffel Tower year, (c) boiling point of nitrogen."
)
TaskConfig
用于控制子任务代理的构建方式:
python
@dataclass
class TaskConfig:
    config: ModelConfig | None = None    # falls back to parent's config
    prompt: str = "You are a task agent..."
    include_tools: Iterable[str] | None = None   # None = inherit all parent tools
    exclude_tools: Iterable[str] = ()
    extra_tools: Iterable[Callable | Tool] = ()
常见配置——为子任务使用更轻量化的模型,限制工具范围:
python
TaskConfig(
    config=worker_config,                  # smaller model
    prompt="You are a focused worker; one step only.",
    include_tools=["search", "fetch_url"], # don't expose `summarize` to children
)
子任务代理的
tasks
参数为
False
——它们不会获得
run_subtask
工具。递归委派在结构上无法实现,无需设置深度限制。

Pattern 2 —
Agent.as_tool()

模式2——
Agent.as_tool()

Expose a whole agent as a tool the LLM can name and call:
python
from ag2 import Agent
from ag2.config import AnthropicConfig

config = AnthropicConfig(model="claude-sonnet-4-6")

researcher = Agent("researcher", prompt="Provide concise factual findings.", config=config, tools=[search_tool])
writer     = Agent("writer", prompt="Turn research into clear prose.", config=config)

coordinator = Agent(
    "coordinator",
    prompt="First delegate research, then pass findings to the writer.",
    config=config,
    tools=[
        researcher.as_tool(description="Research a topic and return findings."),
        writer.as_tool(description="Write an article. Pass research notes in the context parameter."),
    ],
)
The coordinator's LLM sees
task_researcher
and
task_writer
. Each call has two parameters:
  • objective
    (required) — what the sub-task should do.
  • context
    (optional) — relevant info the parent wants to share.
as_tool()
accepts:
ParameterDescription
description
Tool description shown to the LLM (required)
name
Override the default
task_{agent.name}
stream
StreamFactory
for custom sub-task streams (see below)
middleware
ToolMiddleware
callables (e.g.
approval_required
)
For more control, use
subagent_tool()
directly:
python
from ag2.tools.subagents import subagent_tool

coordinator = Agent("coordinator", config=config, tools=[
    subagent_tool(researcher, description="Research a topic."),
])
将整个代理暴露为LLM可命名调用的工具:
python
from ag2 import Agent
from ag2.config import AnthropicConfig

config = AnthropicConfig(model="claude-sonnet-4-6")

researcher = Agent("researcher", prompt="Provide concise factual findings.", config=config, tools=[search_tool])
writer     = Agent("writer", prompt="Turn research into clear prose.", config=config)

coordinator = Agent(
    "coordinator",
    prompt="First delegate research, then pass findings to the writer.",
    config=config,
    tools=[
        researcher.as_tool(description="Research a topic and return findings."),
        writer.as_tool(description="Write an article. Pass research notes in the context parameter."),
    ],
)
协调者的LLM会看到
task_researcher
task_writer
。每次调用包含两个参数:
  • objective
    (必填)——子任务需完成的目标。
  • context
    (可选)——父代理需传递的相关信息。
as_tool()
接受以下参数:
参数描述
description
展示给LLM的工具描述(必填)
name
覆盖默认的
task_{agent.name}
名称
stream
用于自定义子任务流的
StreamFactory
(见下文)
middleware
ToolMiddleware
可调用对象(如
approval_required
如需更多控制,可直接使用
subagent_tool()
python
from ag2.tools.subagents import subagent_tool

coordinator = Agent("coordinator", config=config, tools=[
    subagent_tool(researcher, description="Research a topic."),
])

Self-delegation via
as_tool()

通过
as_tool()
实现自我委派

If you want a named self-delegate (
sub_task
instead of generic
run_subtask
), give an agent its own tool:
python
analyst = Agent(
    "analyst",
    prompt=(
        "You have search and sub_task tools. "
        "Only use sub_task when the task has clearly independent parts."
    ),
    config=config,
    tools=[search_tool],
)

analyst.add_tool(
    analyst.as_tool(
        description="Break work into a focused sub-task for independent analysis.",
        name="sub_task",
    )
)
如果需要命名的自我代理(而非通用的
run_subtask
),可为代理添加自身工具:
python
analyst = Agent(
    "analyst",
    prompt=(
        "You have search and sub_task tools. "
        "Only use sub_task when the task has clearly independent parts."
    ),
    config=config,
    tools=[search_tool],
)

analyst.add_tool(
    analyst.as_tool(
        description="Break work into a focused sub-task for independent analysis.",
        name="sub_task",
    )
)

Recursion safety

递归安全

Self-delegation via
as_tool()
can recurse — the child has the same
sub_task
tool, so without a guard the LLM may chain calls indefinitely.
The simplest safe pattern is to prefer the auto-injected
run_subtask
/
run_subtasks
path
for self-delegation. Sub-tasks spawned that way are constructed with
tasks=False
, so they have no
run_subtask
tools and recursion is structurally impossible.
If you genuinely need recursive
as_tool()
self-delegation, write a tool middleware that increments a depth counter in
context.dependencies
and short-circuits past a threshold. The
subagents
module exports
subagent_tool
,
background_agent_tool
,
persistent_stream
, and
StreamFactory
from
ag2.tools.subagents
— verify the current public surface there before relying on a built-in depth-limiting helper.
通过
as_tool()
实现的自我委派可能发生递归——子代理拥有相同的
sub_task
工具,若没有防护机制,LLM可能无限链式调用。
最简单的安全模式是优先选择自动注入的
run_subtask
/
run_subtasks
路径进行自我委派
。通过该方式生成的子任务代理
tasks=False
,因此没有
run_subtask
工具,递归在结构上无法实现。
如果确实需要通过
as_tool()
实现递归自我委派,可编写工具中间件,在
context.dependencies
中增加深度计数器,并在超过阈值时终止调用。
subagents
模块从
ag2.tools.subagents
导出
subagent_tool
background_agent_tool
persistent_stream
StreamFactory
——在依赖内置深度限制工具前,请先确认当前的公开接口。

Sub-task streams

子任务流

By default, each sub-task gets a fresh
MemoryStream
— its history is isolated and starts empty. Context flow:
WhatBehaviourWhy
DependenciesCopied (top-level shallow)Isolated; treat dependencies as read-only inside subtasks
VariablesCopied; not synced back to the parentConcurrent-safe — with siblings running via
asyncio.gather
, last-writer-wins would silently clobber values, so child mutations stay scoped to the child by design
HistoryFresh streamClean context; relevant info passes via the
context
tool parameter
ToolsInherited from parent (filtered by
TaskConfig
)
Sub-tasks need real capabilities to do work
默认情况下,每个子任务会获得全新的
MemoryStream
——其历史记录独立且初始为空。上下文流转规则:
内容行为原因
Dependencies浅拷贝(顶层)隔离环境;子任务内将dependencies视为只读
Variables拷贝;不会同步回父代理并发安全——当子任务通过
asyncio.gather
运行时,最后写入者获胜的机制会静默覆盖值,因此子代理的变更默认仅作用于自身范围
History全新流干净的上下文;相关信息通过
context
工具参数传递
Tools继承自父代理(受
TaskConfig
过滤)
子任务需要实际能力来完成工作

persistent_stream()

persistent_stream()

When a sub-agent benefits from seeing its prior calls (e.g. avoid repeating searches), give it a stream that persists across invocations within the parent context:
python
from ag2.tools.subagents import persistent_stream

researcher.as_tool(
    description="Research a topic",
    stream=persistent_stream(),
)
Stores stream id in
context.dependencies
keyed by
f"ag:{agent.name}:stream"
and reuses the parent stream's storage backend.
当子代理需要查看其之前的调用记录(如避免重复搜索)时,可为其提供在父代理上下文内跨调用持久化的流:
python
from ag2.tools.subagents import persistent_stream

researcher.as_tool(
    description="Research a topic",
    stream=persistent_stream(),
)
流ID存储在
context.dependencies
中,键为
f"ag:{agent.name}:stream"
,并复用父代理流的存储后端。

Custom factory

自定义工厂

python
from ag2 import Agent, Context
from ag2.streams.redis import RedisStream

def make_redis_stream(agent: Agent, ctx: Context) -> RedisStream:
    return RedisStream(MY_REDIS_URL, prefix=f"ag2:sub:{agent.name}")

researcher.as_tool(description="Research a topic", stream=make_redis_stream)
python
from ag2 import Agent, Context
from ag2.streams.redis import RedisStream

def make_redis_stream(agent: Agent, ctx: Context) -> RedisStream:
    return RedisStream(MY_REDIS_URL, prefix=f"ag2:sub:{agent.name}")

researcher.as_tool(description="Research a topic", stream=make_redis_stream)

Going deeper

深入了解

  • Working starter:
    assets/research_squad.py
    (mirrors
    code_examples/05
    ) — covers both
    run_subtasks(parallel=True)
    and
    Agent.as_tool()
    , with
    TaskStarted
    /
    TaskCompleted
    lifecycle events.
  • Full reference:
    website/docs/user-guide/subagents.mdx
    .
  • tasks=
    constructor knob (with
    KnowledgeConfig
    , etc.):
    website/docs/user-guide/agent_harness.mdx
    .
  • 入门示例:
    assets/research_squad.py
    (对应
    code_examples/05
    )——涵盖
    run_subtasks(parallel=True)
    Agent.as_tool()
    两种模式,包含
    TaskStarted
    /
    TaskCompleted
    生命周期事件。
  • 完整参考:
    website/docs/user-guide/subagents.mdx
  • 构造函数参数
    tasks=
    (结合
    KnowledgeConfig
    等):
    website/docs/user-guide/agent_harness.mdx

Common pitfalls

常见陷阱

  • Forgetting to opt in
    tasks=False
    is the default. No
    TaskConfig
    , no
    run_subtask
    tools.
  • Expecting sub-tasks to recurse with
    run_subtask
    — they can't. Sub-tasks themselves have
    tasks=False
    . If you need deeper trees, use
    Agent.as_tool()
    self-delegation with a manual depth-counter middleware (see "Recursion safety" above).
  • Sharing mutable variables expecting them to merge — each sub-task copies the parent's variables, and mutations are never synced back to the parent (not even on success). Sibling mutations don't propagate either. Pass any result you need back through the sub-task's return value, not via shared variables.
  • Treating
    dependencies
    as scoped per sub-task
    — only the top-level dict is copied. Mutable values inside it are still shared by reference. Treat dependencies as read-only inside sub-tasks.
  • No
    description=
    on
    as_tool()
    — the LLM doesn't know when to call it. Required parameter.
  • run_subtasks(parallel=False)
    when work is concurrent
    — defaults to
    True
    for a reason; only set
    False
    when later tasks depend on earlier results.
  • Confusing
    task_{agent.name}
    collisions
    — pass
    name=
    to override if you want shorter names or distinct delegates of the same agent.
  • 忘记启用——默认
    tasks=False
    。不配置
    TaskConfig
    则无法使用
    run_subtask
    工具。
  • 期望子任务通过
    run_subtask
    递归
    ——不可能。子任务自身的
    tasks=False
    。如需更深层级的任务树,请使用
    Agent.as_tool()
    自我委派,并手动添加深度计数器中间件(见上文“递归安全”部分)。
  • 共享可变变量并期望合并——每个子任务都会拷贝父代理的变量,且变更永远不会同步回父代理(即使成功)。子任务之间的变更也不会相互传递。需通过子任务的返回值传递所需结果,而非共享变量。
  • dependencies
    视为子任务专属
    ——仅顶层字典会被拷贝。其中的可变值仍通过引用共享。子任务内请将dependencies视为只读。
  • as_tool()
    未设置
    description=
    ——LLM无法知晓何时调用该工具。此参数为必填项。
  • 工作可并发时设置
    run_subtasks(parallel=False)
    ——默认
    True
    是有原因的;仅当后续任务依赖前期结果时才设置为
    False
  • task_{agent.name}
    命名冲突
    ——如需更短名称或同一代理的不同实例,请传递
    name=
    参数覆盖默认名称。