Skip to main content

4. Multi-agent team

In step 3 a single agent did everything: think, call tools, reply. In this step you split the work across specialists that the coordinator delegates to. The coordinator is the agent the user talks to; each specialist is a fresh agent loop spawned on demand through the Agent tool.

You add one module (agent_spawn) and declare more than one entry under agents:. The coordinator picks who to dispatch to and runs them in parallel.

Prerequisites​

Same as the previous steps (running daemon, authenticated user, DeepSeek credential deepseek_main provisioned).

The YAML​

Save this as multi-agent.yaml. Three agents share the same brain config (a per-user DeepSeek credential), but each has its own role and system prompt.

app.yaml
app:
app_id: multi-agent
name: Multi-Agent Team
version: "1.0"

runtime:
mode: conversation
workdir_mode: auto
max_turns: 8
timeout: 180

agents:
- id: coordinator
role: coordinator
delegate_to:
- summarizer # simple form: just the id
- id: translator # rich form: id + instructions
instructions: "Delegate here whenever the user's message needs a French translation."
brain: &deepseek
provider: deepseek
model: deepseek-chat
backend: openai_compat
credential:
ref: deepseek_main
scope: per_user
provider: deepseek
config:
api_key: "{{env.DEEPSEEK_API_KEY}}"
base_url: https://api.deepseek.com/v1
temperature: 0
max_tokens: 512
system_prompt: |
You are a coordinator. For any user question that needs both a
summary and a French translation, spawn both specialists IN
PARALLEL with Agent(prompt="...", specialist="summarizer",
wait=true) and Agent(prompt="...", specialist="translator",
wait=true). Then combine their results into a single answer.
Keep the final answer short.

- id: summarizer
role: specialist
specialty: "Summarises the input in ONE sentence"
brain: *deepseek
system_prompt: |
You summarise the input in ONE sentence. No preamble, no
explanation, just the one-sentence summary.

- id: translator
role: specialist
specialty: "Translates the input to French"
brain: *deepseek
system_prompt: |
You translate the user's input to French. Output only the
translation, no preamble.

tools:
modules:
agent_spawn: {}
capabilities:
default_policy: auto

ui:
greeting: "Coordinator + 2 specialists. Try: 'Summarise and translate this paragraph...'"

The YAML anchor (&deepseek / *deepseek) reuses the same brain across the three agents to avoid copy-paste. Anything is allowed here: a different model per specialist, a cheaper one for the coordinator, etc. Read more on agent shapes in the Agents reference.

Deploy and chat​

bash
digitorn install multi-agent.yaml
digitorn chat multi-agent

Live transcript​

Sample transcript. The user paragraph asks the coordinator to summarise and translate at once.

text
> Summarise and translate this paragraph: Solar panels in Europe
produced more electricity than coal in 2023, marking the first
time renewables outpaced any single fossil fuel across the
continent.

Here are the results:

**Summary:** Solar panels in Europe generated more electricity
than coal in 2023, a historic first where renewables exceeded a
single fossil fuel continent-wide.

**French translation:** Les panneaux solaires en Europe ont
produit plus d'électricité que le charbon en 2023, marquant la
première fois que les énergies renouvelables surpassaient un
seul combustible fossile sur le continent.

Behind the scene the coordinator fired exactly two tool calls (tool_calls_count: 2 on the result event). The live event stream shows both specialists spawned in parallel:

text
[agent_event] specialist=summarizer status=spawned
[agent_event] specialist=summarizer status=running duration=2.1s
[agent_event] specialist=summarizer status=completed
preview="Solar panels in Europe generated more
electricity than coal in 2023, a historic
first where renewables exceeded a single
fossil fuel continent-wide."

[agent_event] specialist=translator status=spawned
[agent_event] specialist=translator status=running duration=2.1s
[agent_event] specialist=translator status=completed
preview="Les panneaux solaires en Europe ont produit
plus d'électricité que le charbon en 2023,
marquant la première fois que les énergies
renouvelables surpassaient un seul
combustible fossile sur le continent."

Both specialists finished in roughly two seconds because they ran concurrently through concurrent execution. The coordinator received both results back in the same turn and produced the combined reply.

What changed vs step 3​

Three things. First, agents: now lists three entries with distinct role and system_prompt blocks. The coordinator's role is coordinator; the workers' role is specialist. The coordinator is the entry_agent by default - it's the one the user talks to.

Second, tools.modules.agent_spawn: {} is loaded. That module exposes the Agent tool to the coordinator. Its modes - spawn one specialist or a batch, wait for one or several previously-spawned runs, poll status, cancel (with or without its sub-tree), list - are documented in the agent_spawn module reference.

yaml
tools:
modules:
agent_spawn: {} # adds the Agent tool
capabilities:
default_policy: auto

Third, the coordinator declares delegate_to: [summarizer, translator] (mixing the simple id form and the {id, instructions} form above). This isn't optional decoration - it does two real things at once:

  • Prompt: because role: coordinator and delegate_to is non-empty, the daemon automatically appends an "Available specialists" block to the coordinator's own prompt, one line per entry (id + its specialty:, plus its instructions: when set) - you don't have to hand-write that roster into system_prompt yourself.
  • Authorization: at runtime, a coordinator can only spawn an id that's actually in its own delegate_to. Leaving summarizer out of the list wouldn't just skip a prompt hint - every Agent(specialist="summarizer", ...) call would be rejected outright, even though agent_spawn is granted and summarizer is a real agent in the file. See Multi-agent reference for the full mechanics.

When to use this pattern​

A flat single-agent setup is enough when the work is sequential. Multi-agent shines when you can fan out:

  • Parallel reads - search across N data sources, merge results
  • Specialised tools - one agent owns the database tools, another owns the file system, another owns web search
  • Isolation - spawn a writer agent with default_policy: block on filesystem so it can't touch disk while the coordinator can

The coordinator stays in charge of the conversation. Specialists return raw output and exit. They don't carry their own user session - their context dies with the spawn.

Going further​

  • The Agent tool's full surface (wait=true, run_id=.../ run_ids=[...], cancel=true/cancel_tree=true, list=true, batch spawn via agents: [...], parallel Agent() calls in one turn) is in the agent_spawn reference.
  • Granular per-specialist tool restriction: agents[].modules: [{filesystem: [read]}]. The specialist sees only the listed actions, not the coordinator's full toolbox. See Multi-agent.
  • Modules are shared across the whole app, not just some of them: a module is instantiated once per app, so the coordinator and every specialist talk to the same memory, filesystem, and any other declared module instance - there's no per-specialist fresh copy. The same workdir is shared too, so files written by one specialist are visible to the next.

Next: explore the Agent tool reference or pick a module from the index for your next experiment.