Skip to contents

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 SubagentStop hooks

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)