Skip to content

Commit 6bf9893

Browse files
committed
feat: 支持动态创建子agent能力
1 parent 73655ab commit 6bf9893

47 files changed

Lines changed: 5501 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/mkdocs/en/multi_agents.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,13 @@ Coordinator Agent (Main Entry Point)
311311
| `disallow_transfer_to_peers` | `False` | Set to `True` to prevent a child Agent from transferring control to peer Agents |
312312
| `default_transfer_message` | `None` | Custom transfer instruction that overrides the default transfer prompt |
313313

314+
#### Spawned Sub-Agents
315+
316+
As an alternative to persistent `sub_agents` (transfer-based), you can spawn
317+
short-lived sub-agents at run time via ``SpawnSubAgentTool`` (pick from a
318+
pre-registered catalog) or ``DynamicSubAgentTool`` (LLM defines the role on the
319+
fly). See [Sub-Agent Tools](sub_agent.md).
320+
314321
## Compose Patterns (Compose Agents)
315322

316323
Different orchestration patterns can be flexibly combined, connecting results of different stages via `output_key` to create more complex workflows:

docs/mkdocs/en/sub_agent.md

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
# Spawned Sub-Agents
2+
3+
Complex tasks often require delegated subtasks — computing results, searching codebases, auditing for security issues. Doing everything in the parent agent's own context causes several problems:
4+
5+
- **Context pollution**: exploratory searches, tool outputs, and intermediate steps fill the context window, crowding out what matters.
6+
- **Tool sprawl**: the parent carries every tool all the time, even though most subtasks only need a subset.
7+
- **No role isolation**: the parent has one system prompt; it cannot adopt a different persona or constraints per subtask.
8+
- **No outside perspective**: an agent reviewing its own work is inherently biased — it's unlikely to spot its own mistakes. A fresh context acts as a second pair of eyes, auditing code, challenging a design, or verifying a claim without the parent's assumptions and reasoning shortcuts.
9+
10+
A **short-lived sub-agent** is a natural fit for these problems: a fresh context per delegation, only the tools it needs, a dedicated system prompt. It runs, returns its result, and is destroyed — keeping the parent conversation clean and focused.
11+
12+
**Spawned Sub-Agents** give the parent agent two tools for creating short-lived sub-agents at run time:
13+
14+
- **`SpawnSubAgentTool`** — choose from a **pre-defined catalog** of standardized specialists. Instruction, tool set, and model are locked by the archetype at construction time.
15+
16+
Use this when you have a fixed set of expert roles (security auditor, code explorer, planner) and want the LLM to pick the right one per task. The parent LLM selects via `subagent_type` and writes a task-specific `prompt`, but cannot alter the sub-agent's instruction or tools.
17+
18+
- **`DynamicSubAgentTool`** — the LLM **invents the specialist on the fly**, writing the instruction at call time. No pre-registration needed.
19+
20+
Use this when you cannot predict all the specialist types you'll need ahead of time. Every call can define a different role — the LLM decides what expertise, constraints, and tool subset each task requires.
21+
22+
The difference is *who defines the role*: the developer (Spawn) or the LLM (Dynamic).
23+
24+
## Quick Start
25+
26+
```python
27+
from trpc_agent_sdk.agents import LlmAgent
28+
from trpc_agent_sdk.tools import SpawnSubAgentTool, DynamicSubAgentTool
29+
30+
# Spawn: pick from a catalog of pre-defined specialists
31+
agent_with_spawn = LlmAgent(
32+
name="orchestrator",
33+
tools=[SpawnSubAgentTool()], # built-in `default` archetype
34+
)
35+
36+
# Dynamic: LLM writes the specialist's role at call time
37+
agent_with_dynamic = LlmAgent(
38+
name="orchestrator",
39+
tools=[DynamicSubAgentTool()], # sub-agent inherits all parent tools
40+
)
41+
```
42+
43+
## Two Tools
44+
45+
| | `SpawnSubAgentTool` | `DynamicSubAgentTool` |
46+
| --- | --- | --- |
47+
| **Pattern** | Pick from a pre-defined catalog | LLM invents role at call time |
48+
| **Who defines the role** | Developer, two modes:<br>① `SubAgentArchetype` in code<br>② Markdown file (YAML frontmatter + body) | LLM (via `instruction` parameter) |
49+
| **Best for** | Standardized, repeatable specialists | Roles you can't pre-register |
50+
| **Role flexibility** | Locked — only `prompt` varies | Full — every call can be different |
51+
| **Tool surface** | Locked by archetype | Inherits parent tools; LLM can narrow via `tools` |
52+
53+
### `SpawnSubAgentTool`
54+
55+
Dispatches tasks to pre-registered archetypes. The parent LLM picks the right specialist via `subagent_type`; its instruction and tools are fixed.
56+
57+
```python
58+
class SpawnSubAgentTool(BaseTool):
59+
def __init__(
60+
self,
61+
agents: list[SubAgentArchetype] | None = None,
62+
agent_paths: list[str | os.PathLike] | None = None,
63+
tool_mapping: dict[str, Any] | None = None,
64+
with_default: bool = True,
65+
agent_config: SubAgentConfig | None = None,
66+
skip_summarization: bool = False,
67+
filters_name: list[str] | None = None,
68+
filters: list[BaseFilter] | None = None,
69+
) -> None: ...
70+
```
71+
72+
| parameter | meaning |
73+
| --- | --- |
74+
| `agents` | Additional archetypes to register. |
75+
| `agent_paths` | Directories of `*.md` files to load archetypes from disk. |
76+
| `tool_mapping` | Custom tool name → tool class mapping for resolving MD frontmatter. |
77+
| `with_default` | Whether to register the built-in `default` archetype. Default `True`. |
78+
| `agent_config` | `SubAgentConfig` applied to every spawned sub-agent. |
79+
| `skip_summarization` | When `True`, skip the parent's summarization turn after the sub-agent returns. |
80+
81+
**Three ways to configure:**
82+
83+
```python
84+
# Zero config — only the built-in `default` archetype
85+
SpawnSubAgentTool()
86+
87+
# Code-defined archetypes
88+
SpawnSubAgentTool(agents=[security_auditor, EXPLORE_AGENT, PLAN_AGENT])
89+
90+
# Load from Markdown files
91+
SpawnSubAgentTool(agent_paths=[".trpc_agents/"])
92+
```
93+
94+
#### `SubAgentArchetype`
95+
96+
A frozen template that describes *one kind of sub-agent the parent is allowed to spawn*. It locks down the dangerous knobs (instruction, tools, model) so prompt-injected calls cannot reshape the sub-agent.
97+
98+
```python
99+
@dataclass(frozen=True)
100+
class SubAgentArchetype:
101+
name: str # registry key + the value LLM passes as `subagent_type`
102+
description: str # what the LLM reads to pick this archetype
103+
instruction: str | InstructionProvider
104+
tools: tuple | None = None # None = inherit all parent tools
105+
model: Any = None # None = inherit via SubAgentConfig or parent's model
106+
```
107+
108+
- **`description`** — read by the **parent LLM** when selecting which archetype to spawn. Third-person, selection-focused.
109+
- **`instruction`** — the **sub-agent's** system prompt. Second-person, execution-focused. Supports both strings and `InstructionProvider` callables.
110+
111+
#### Built-in Archetypes
112+
113+
| name | tools | typical use |
114+
| --- | --- | --- |
115+
| `default` | `None` (inherits all parent tools) | **Neutral task executor.** Does not impose a specific role. **Auto-registered.** |
116+
| `general-purpose` | `None` (inherits all parent tools) | **Researcher / explorer** with soft "NEVER create files" constraints. Opt-in only. |
117+
| `Explore` | `Read` / `Glob` / `Grep` / `WebFetch` | Read-only search: locate files, grep symbols. |
118+
| `Plan` | `Read` / `Glob` / `Grep` | Design implementation plans without modifying code. |
119+
120+
Only `default` is auto-registered. `general-purpose`, `Explore`, and `Plan` must be explicitly added via the `agents` parameter.
121+
122+
### `DynamicSubAgentTool`
123+
124+
The LLM writes the sub-agent's `instruction` at call time, creating any specialist on the fly. By default the sub-agent inherits all parent tools.
125+
126+
```python
127+
class DynamicSubAgentTool(BaseTool):
128+
def __init__(
129+
self,
130+
name: str = "dynamic_subagent",
131+
description: str | None = None,
132+
tools: tuple | None = None,
133+
expose_tool_selection: bool = True,
134+
agent_config: SubAgentConfig | None = None,
135+
skip_summarization: bool = False,
136+
filters_name: list[str] | None = None,
137+
filters: list[BaseFilter] | None = None,
138+
) -> None: ...
139+
```
140+
141+
| parameter | meaning |
142+
| --- | --- |
143+
| `name` | Tool name. Default `"dynamic_subagent"`. |
144+
| `description` | Tool description. |
145+
| `tools` | Fixed tool set for the sub-agent. `None` (default) = inherit all parent tools. |
146+
| `expose_tool_selection` | When `True` (default), the `tools` field is exposed so the LLM can narrow the tool surface per call. |
147+
| `agent_config` | `SubAgentConfig` applied to every spawned sub-agent. |
148+
| `skip_summarization` | When `True`, skip the parent's summarization turn after the sub-agent returns. |
149+
150+
## Shared Configuration
151+
152+
### `SubAgentConfig`
153+
154+
Unified construction-time defaults for every spawned sub-agent. `None` means "inherit from the parent agent".
155+
156+
```python
157+
@dataclass(frozen=True)
158+
class SubAgentConfig:
159+
model: LLMModel | None = None
160+
"""Model for the sub-agent. None inherits the parent's model."""
161+
162+
generate_content_config: GenerateContentConfig | None = None
163+
"""Generation config (temperature, top_p, etc.). None inherits from parent."""
164+
165+
parallel_tool_calls: bool | None = None
166+
"""Whether the sub-agent may issue parallel tool calls. None inherits from parent."""
167+
168+
include_parent_history: bool = False
169+
"""Whether to inject parent conversation history into the sub-agent's session."""
170+
171+
max_parent_history_turns: int | None = None
172+
"""Max parent turns to inject. None = unlimited. Only used when include_parent_history=True."""
173+
174+
max_turns: int | None = None
175+
"""Max LLM calls the sub-agent may make. None = unlimited."""
176+
```
177+
178+
## Usage
179+
180+
### SpawnSubAgentTool
181+
182+
**Zero config** — only the built-in `default` archetype:
183+
184+
```python
185+
from trpc_agent_sdk.agents import LlmAgent
186+
from trpc_agent_sdk.tools import SpawnSubAgentTool
187+
188+
orchestrator = LlmAgent(
189+
name="main",
190+
model=opus_model,
191+
instruction="When a task benefits from isolated context, spawn a sub-agent via spawn_subagent.",
192+
tools=[SpawnSubAgentTool()],
193+
)
194+
```
195+
196+
**Code-defined archetypes**:
197+
198+
```python
199+
from trpc_agent_sdk.agents.sub_agent import SubAgentArchetype
200+
from trpc_agent_sdk.tools import SpawnSubAgentTool
201+
202+
security_auditor = SubAgentArchetype(
203+
name="security-auditor",
204+
description="Use for security code audit. **IMPORTANT:** This agent is read-only.",
205+
instruction="You are a security auditor...",
206+
tools=(ReadTool, GrepTool, GlobTool),
207+
)
208+
209+
orchestrator = LlmAgent(
210+
tools=[SpawnSubAgentTool(agents=[security_auditor])],
211+
)
212+
```
213+
214+
**Loading archetypes from Markdown files**:
215+
216+
Place `.md` files in a directory with YAML frontmatter:
217+
218+
```markdown
219+
---
220+
name: security-auditor
221+
description: Use for security code audit.
222+
tools:
223+
- Read
224+
- Glob
225+
- Grep
226+
---
227+
228+
You are a security auditor...
229+
```
230+
231+
```python
232+
tools=[SpawnSubAgentTool(agent_paths=[".trpc_agents/"])]
233+
```
234+
235+
### DynamicSubAgentTool
236+
237+
**Unbounded (default)** — the sub-agent inherits all parent tools. The LLM narrows the tool set per call via `tools`:
238+
239+
```python
240+
from trpc_agent_sdk.agents import LlmAgent
241+
from trpc_agent_sdk.tools import DynamicSubAgentTool
242+
243+
orchestrator = LlmAgent(
244+
name="main",
245+
model=opus_model,
246+
instruction="When you need a specialist, create one via dynamic_subagent. Narrow tools as needed.",
247+
tools=[DynamicSubAgentTool()],
248+
)
249+
```
250+
251+
**Bounded** — the sub-agent uses a fixed tool set. The parent agent has no direct access to those tools; every task must be delegated. This is useful for keeping dangerous tools behind the sub-agent boundary:
252+
253+
```python
254+
orchestrator = LlmAgent(
255+
name="main",
256+
model=opus_model,
257+
instruction="You can only use tools by delegating via dynamic_subagent. Do not attempt direct calls.",
258+
tools=[
259+
DynamicSubAgentTool(
260+
tools=(calculator, word_count),
261+
expose_tool_selection=False,
262+
),
263+
],
264+
)
265+
```
266+
267+
## Additional Notes
268+
269+
- **Tool inheritance**: `DynamicSubAgentTool()` inherits all parent tools by default; pass `tools=(...)` to give the sub-agent a fixed set instead. For `SpawnSubAgentTool`, the archetype's `tools` field decides — `None` means inherit, `(ReadTool, ...)` means that exact set. In all cases, spawn tools are stripped from the sub-agent to prevent recursion.
270+
- **Session isolation**: sub-agents run in a fresh ephemeral session. Parent history is not shared by default; opt in via `include_parent_history=True`.
271+
- **Nesting**: 1-level hard cap. Sub-agents cannot spawn further sub-agents.
272+
- **Result shape**: the sub-agent's final text is returned as the tool result string.

docs/mkdocs/zh/multi_agents.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,10 @@ Route customer inquiries:
311311
| `disallow_transfer_to_peers` | `False` | 设为 `True` 禁止子 Agent 将控制权转给同级 Agent |
312312
| `default_transfer_message` | `None` | 自定义转移指令,覆盖默认的转移提示语 |
313313

314+
#### Spawned Sub-Agents
315+
316+
除持久化的 `sub_agents`(基于 transfer)之外,还可以在运行时通过 ``SpawnSubAgentTool``(从预注册目录中选择)或 ``DynamicSubAgentTool``(LLM 现场定义角色)创建短期子 agent。详见 [子 Agent 工具](sub_agent.md)
317+
314318
## 组合模式(Compose Agents)
315319

316320
不同的编排模式可以灵活组合,通过 `output_key` 连接不同阶段的结果,创建更复杂的工作流:

0 commit comments

Comments
 (0)