For complex tasks, deputy supports a lead agent that delegates work to specialised sub-agents. Each sub-agent has its own tools, prompt, and (optionally) model. The lead agent decides when to delegate and synthesises the results.
When to Use Multi-Agent
Multi-agent orchestration is useful when:
- Different parts of a task need different tools (e.g., code analysis vs. data analysis)
- You want to isolate sub-tasks with their own system prompts
- Sub-agents should run with restricted permissions
- You need audit trails per sub-agent via
SubagentStophooks
For simpler tasks, a single Agent with the right tools
is usually enough.
Defining Sub-Agents
Use agent_definition() to describe a sub-agent:
library(deputy)
code_reviewer <- agent_definition(
name = "code_reviewer",
description = "Reviews R code for best practices and potential issues",
prompt = "You are an expert R developer. Review code for correctness,
style, and potential bugs. Be specific and actionable.",
tools = tools_file()
)
data_analyst <- agent_definition(
name = "data_analyst",
description = "Analyses data files and produces statistical summaries",
prompt = "You are a data analyst. Read data files and provide clear,
concise statistical summaries with key insights.",
tools = tools_data()
)Fields:
| Field | Description |
|---|---|
name |
Unique identifier (used by the lead to delegate) |
description |
What this agent does (shown to the lead LLM) |
prompt |
System prompt for the sub-agent |
tools |
Tools available to the sub-agent |
model |
"inherit" (default) or a specific model name |
skills |
Optional list of skills to load |
disallowed_tools |
Additional tool names that the child must not use |
memory |
Optional context appended to the child’s system prompt |
mcp_servers |
Optional MCP servers loaded for the child |
initial_prompt |
Optional text prepended to delegated tasks |
max_requests |
Optional per-child request limit |
permission_mode |
Optional mode that preserves or narrows the lead policy |
Delegated Permission Boundaries
A sub-agent cannot gain authority that its lead does not have. Deputy
intersects the requested child mode with the lead’s capabilities, write
root, tool gates, and permission callback. disallowed_tools
adds further denials.
The allowed child-mode transitions use the same authority order as
Agent$set_permission_mode():
| Lead mode | Allowed child modes |
|---|---|
"readonly" |
"readonly" |
"standard" |
"standard", "readonly"
|
"plan" |
"plan", "readonly"
|
"full" |
"full", "standard", "plan",
"readonly"
|
For example, a readonly reviewer can be declared beneath a standard lead without gaining file reads, web access, or tools that the lead explicitly denied:
reviewer <- agent_definition(
name = "reviewer",
description = "Reviews files without changing them",
prompt = "Review the requested files and report findings.",
tools = tools_file(),
permission_mode = "readonly",
disallowed_tools = "read_csv",
max_requests = 5
)Creating a LeadAgent
LeadAgent extends Agent with a built-in
delegate_to_agent tool:
lead <- LeadAgent$new(
chat = ellmer::chat_anthropic(),
sub_agents = list(code_reviewer, data_analyst),
system_prompt = "You coordinate analysis tasks. Delegate to the
appropriate specialist and synthesise their findings."
)
lead$available_sub_agents()
#> [1] "code_reviewer" "data_analyst"Running a Delegation Task
When the lead agent decides to delegate, it calls
delegate_to_agent internally. The sub-agent runs to
completion and returns its result to the lead:
library(deputy)
code_reviewer <- agent_definition(
name = "code_reviewer",
description = "Reviews R code and suggests improvements",
prompt = "You are an R code reviewer. Be concise.",
tools = tools_file()
)
chat <- ellmer::chat_anthropic(model = "claude-sonnet-4-20250514")
lead <- LeadAgent$new(
chat = chat,
sub_agents = list(code_reviewer)
)
result <- lead$run_sync(
"Ask the code reviewer to look at the DESCRIPTION file and
summarise what this package does."
)
cat(result$response)Direct synchronous delegation participates in the lead run’s usage
limits. Each child inherits the lead’s remaining
UsageLimits, and its usage is aggregated into
result$usage and enforced by the lead after delegation.
This accounting does not yet cover parallel or background children,
transitive child trees, or cross-run global limits.
Monitoring with SubagentStop Hooks
Use a SubagentStop hook to log or inspect sub-agent
results:
hook_monitor <- HookMatcher$new(
event = "SubagentStop",
callback = function(agent_name, task, result, context) {
cli::cli_alert_info("Sub-agent {agent_name} finished")
cli::cli_alert("Requests: {result$usage$requests}")
NULL
}
)
lead$add_hook(hook_monitor)