Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 20 additions & 19 deletions tools/delegate_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
"""
Delegate Tool -- Subagent Architecture

Spawns child AIAgent instances with isolated context, restricted toolsets,
Spawns child AIAgent instances with isolated context, inherited toolsets,
and their own terminal sessions. Supports single-task and batch (parallel)
modes. The parent blocks until all children complete.
modes. Top-level model calls run in the background; orchestrator children
wait for their own workers so they can synthesize the results.

Each child gets:
- A fresh conversation (no parent history)
- Its own task_id (own terminal session, file ops cache)
- A restricted toolset (configurable, with blocked tools always stripped)
- The parent's toolsets, with child-only blocked tools stripped
- A focused system prompt built from the delegated goal + context

The parent's context only sees the delegation call and the summary result,
Expand Down Expand Up @@ -2391,8 +2392,8 @@ def delegate_task(
Spawn one or more child agents to handle delegated tasks.

Supports two modes:
- Single: provide goal (+ optional context, toolsets, role)
- Batch: provide tasks array [{goal, context, toolsets, role}, ...]
- Single: provide goal (+ optional context and role)
- Batch: provide tasks array [{goal, context, role}, ...]

The 'role' parameter controls whether a child can further delegate:
'leaf' (default) cannot; 'orchestrator' retains the delegation
Expand Down Expand Up @@ -3272,16 +3273,16 @@ def _build_top_level_description() -> str:
"Only the final summary is returned -- intermediate tool results "
"never enter your context window.\n\n"
"TWO MODES (one of 'goal' or 'tasks' is required):\n"
"1. Single task: provide 'goal' (+ optional context, toolsets).\n"
"1. Single task: provide 'goal' (+ optional context and role).\n"
f"2. Batch (parallel): provide 'tasks' array with up to {max_children} "
f"items concurrently for this user (configured via "
f"delegation.max_concurrent_children in config.yaml). {nesting_clause}\n\n"
"BOTH MODES RUN IN THE BACKGROUND. delegate_task returns immediately β€” "
"you and the user keep working, and each subagent's full result "
"re-enters the conversation as its own new message when it finishes. A "
"batch is just N independent background subagents (N handles, each "
"completes on its own). Do NOT wait or poll; just continue with other "
"work after dispatching.\n\n"
"you and the user keep working, and the completed result re-enters "
"the conversation as a new message. A "
"batch returns one handle, runs N subagents concurrently, and delivers "
"one consolidated result after ALL of them finish. Do NOT wait or poll; "
"just continue with other work after dispatching.\n\n"
"WHEN TO USE delegate_task:\n"
"- Reasoning-heavy subtasks (debugging, code review, research synthesis)\n"
"- Tasks that would flood your context with intermediate data\n"
Expand Down Expand Up @@ -3335,7 +3336,7 @@ def _build_tasks_param_description() -> str:
f"Batch mode: tasks to run in parallel (up to {max_children} for this "
f"user, set via delegation.max_concurrent_children). Each gets "
"its own subagent with isolated context and terminal session. "
"When provided, top-level goal/context/toolsets are ignored."
"When provided, top-level goal/context/role are ignored."
)


Expand Down Expand Up @@ -3464,13 +3465,13 @@ def _build_dynamic_schema_overrides() -> dict:
"background": {
"type": "boolean",
"description": (
"DEPRECATED / IGNORED. Single-task delegations always run "
"in the background automatically β€” you do not need to (and "
"cannot) opt in or out. The result re-enters the "
"conversation as a new message when the subagent finishes; "
"just continue working in the meantime. Setting this has no "
"effect; the parameter remains only for backward "
"compatibility."
"DEPRECATED / IGNORED. Top-level single and batch "
"delegations run in the background automatically β€” you do "
"not need to (and cannot) opt in or out. A single result or "
"consolidated batch result re-enters the conversation when "
"the work finishes; just continue working in the meantime. "
"Setting this has no effect; the parameter remains only for "
"backward compatibility."
),
},
},
Expand Down
41 changes: 12 additions & 29 deletions website/docs/guides/delegation-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ For the full feature reference, see [Subagent Delegation](/user-guide/features/d
- Mechanical multi-step work with logic between steps β†’ `execute_code`
- Tasks needing user interaction β†’ subagents can't use `clarify`
- Quick file edits β†’ do them directly
- Durable long-running work that must outlive the current turn β†’ `cronjob` or `terminal(background=True, notify_on_complete=True)`. `delegate_task` is **synchronous**: if the parent turn is interrupted, active children are cancelled and their work is discarded.
- Durable long-running work that must survive session closure or process restart β†’ `cronjob` or `terminal(background=True, notify_on_complete=True)`. Top-level delegation is asynchronous but still process-local.

---

Expand All @@ -48,18 +48,15 @@ Behind the scenes, Hermes uses:
delegate_task(tasks=[
{
"goal": "Research WebAssembly outside the browser in 2025",
"context": "Focus on: runtimes (Wasmtime, Wasmer), cloud/edge use cases, WASI progress",
"toolsets": ["web"]
"context": "Focus on: runtimes (Wasmtime, Wasmer), cloud/edge use cases, WASI progress"
},
{
"goal": "Research RISC-V server chip adoption",
"context": "Focus on: server chips shipping, cloud providers adopting, software ecosystem",
"toolsets": ["web"]
"context": "Focus on: server chips shipping, cloud providers adopting, software ecosystem"
},
{
"goal": "Research practical quantum computing applications",
"context": "Focus on: error correction breakthroughs, real-world use cases, key companies",
"toolsets": ["web"]
"context": "Focus on: error correction breakthroughs, real-world use cases, key companies"
}
])
```
Expand Down Expand Up @@ -87,8 +84,7 @@ delegate_task(
Auth files: src/auth/login.py, src/auth/jwt.py, src/auth/middleware.py
Test command: pytest tests/auth/ -v
Focus on: SQL injection, JWT validation, password hashing, session management.
Fix issues found and verify tests pass.""",
toolsets=["terminal", "file"]
Fix issues found and verify tests pass."""
)
```

Expand Down Expand Up @@ -130,25 +126,22 @@ delegate_task(tasks=[
Old format: return {"data": result, "status": "ok"}
New format: return APIResponse(data=result, status=200).to_dict()
Import: from src.responses import APIResponse
Run tests after: pytest tests/handlers/ -v""",
"toolsets": ["terminal", "file"]
Run tests after: pytest tests/handlers/ -v"""
},
{
"goal": "Update all client SDK methods to handle the new response format",
"context": """Project at /home/user/api-server.
Files: sdk/python/client.py, sdk/python/models.py
Old parsing: result = response.json()["data"]
New parsing: result = response.json()["data"] (same key, but add status code checking)
Also update sdk/python/tests/test_client.py""",
"toolsets": ["terminal", "file"]
Also update sdk/python/tests/test_client.py"""
},
{
"goal": "Update API documentation to reflect the new response format",
"context": """Project at /home/user/api-server.
Docs at: docs/api/. Format: Markdown with code examples.
Update all response examples from old format to new format.
Add a 'Response Format' section to docs/api/overview.md explaining the schema.""",
"toolsets": ["terminal", "file"]
Add a 'Response Format' section to docs/api/overview.md explaining the schema."""
}
])
```
Expand Down Expand Up @@ -191,27 +184,17 @@ delegate_task(
context="""Raw data at /tmp/ai-funding-data.json contains search results and
extracted web pages about AI funding, acquisitions, and IPOs in Q1 2026.
Write a structured market report: key deals, trends, notable players,
and outlook. Focus on deals over $100M.""",
toolsets=["terminal", "file"]
and outlook. Focus on deals over $100M."""
)
```

This is often the most efficient pattern: `execute_code` handles the 10+ sequential tool calls cheaply, then a subagent does the single expensive reasoning task with a clean context.

---

## Toolset Selection
## Inherited Tool Access

Choose toolsets based on what the subagent needs:

| Task type | Toolsets | Why |
|-----------|----------|-----|
| Web research | `["web"]` | web_search + web_extract only |
| Code work | `["terminal", "file"]` | Shell access + file operations |
| Full-stack | `["terminal", "file", "web"]` | Everything except messaging |
| Read-only analysis | `["file"]` | Can only read files, no shell |

Restricting toolsets keeps the subagent focused and prevents accidental side effects (like a research subagent running shell commands).
Subagents inherit the parent's enabled toolsets. `delegate_task` does not accept a model-facing `toolsets` parameter, so delegated work cannot grant itself capabilities that the parent does not have. Configure the parent's tools before starting the conversation when a delegated task needs web, terminal, file, or other access. Hermes still strips child-blocked tools such as `clarify`, `memory`, and `execute_code`.

---

Expand All @@ -238,7 +221,7 @@ delegation:
- **Separate terminals** β€” each subagent gets its own terminal session with separate working directory and state
- **No conversation history** β€” subagents see only the `goal` and `context` the parent agent passes when calling `delegate_task`
- **Default 50 iterations** β€” set `max_iterations` lower for simple tasks to save cost
- **Not durable** β€” `delegate_task` is synchronous and runs inside the parent turn. If the parent is interrupted (new user message, `/stop`, `/new`), all active children are cancelled (`status="interrupted"`) and their work is discarded. For work that must outlive the current turn, use `cronjob` or `terminal(background=True, notify_on_complete=True)`.
- **Not durable** β€” top-level delegation runs in the background and posts its result back later, but it remains tied to the owning session and Hermes process. Session closure, `/stop`, `/new`, or a process restart can cancel or strand in-progress work. Use `cronjob` or `terminal(background=True, notify_on_complete=True)` for work that must survive those boundaries.

---

Expand Down
Loading
Loading