Subagents

API reference: Subagent, SubagentPolicy

An agent can hand parts of a job to subagents. Each one works on its own task with its own history while the others run, and the parent combines what they report. A subagent is a session of its own, with its own turns.

Coming from Agents API handoffs: a subagent reports back to its parent; it does not take over the conversation.

When to use subagents

When to use subagents

  • Independent tasks in parallel: look up five products at once, one subagent each.
  • A specialist: a subagent with its own instructions and tools, such as a deployer that alone can call deploy.
  • A long task that would fill the parent's context: the subagent reads the logs, and the parent gets only its summary.

When a step needs the result of the step before it, let the parent do both. Use a skill when only the procedure changes.

Declare and run subagents

Declare and run subagents

Pass the subagents the model may start in Agent(subagents=[...]). The parent's model picks one by name when it starts a subagent:

import asyncio

from pydantic import BaseModel

from mistralai import agents

PRICES = {"atlas": "1,200 EUR", "nova": "850 EUR"}

class PriceArgs(BaseModel):
    product: str

@agents.tool(name="lookup_price", description="Price of one product.", input_schema=PriceArgs)
async def lookup_price(args: PriceArgs) -> dict[str, str]:
    return {"price": PRICES.get(args.product.casefold(), "unknown")}

agent = agents.Agent(
    model="mistral-large-4",
    instructions=(
        "You answer questions about our products. Start one researcher per product, "
        "wait for every result, then answer in one sentence."
    ),
    subagents=[
        agents.Subagent(
            name="researcher",
            description="Looks up one product and reports its price.",
            instructions="Call lookup_price once, then answer with the product name and its price.",
            tools=[lookup_price],
            builtins=agents.Builtins(subagents=False),
        )
    ],
)

async def main() -> None:
    session = await agent.start_session()
    state = await session.run("What do the Atlas and the Nova cost?")
    if isinstance(state, agents.Completed):
        print(state.text)
    print(len(await session.subagents()))
    await session.cancel()

asyncio.run(main())
Output
The Atlas costs 1,200 EUR and the Nova costs 850 EUR.
2

The parent has no tools of its own; it starts a researcher for each product and waits for both results. Each researcher has lookup_price, and agents.Builtins(subagents=False) keeps it from starting subagents of its own, generic ones included. session.subagents() returns the two researchers' sessions, so the program prints 2; see See and stop subagents.

name, description and instructions are required. The model reads the name and the description when it picks a subagent, so write the description for the model. Most other fields come from the parent unless you set them; see Configure subagents.

The model can also start a generic subagent. It has the parent's model, tools and settings, and runs on instructions the model writes. The model can start one whenever the agent has subagents or a subagent_policy.

How subagents run

How subagents run

The agent you run is the root; it and its subagents form a tree. The parent's model drives its subagents with tools the harness adds for it: one starts a subagent, others send it a message, wait for its result or interrupt its turn. You write none of them.

  • A subagent runs in the background, so the parent can start several at once.
  • The parent can send a subagent follow-up messages, and the subagent keeps its history.
  • Each result reaches the parent's current turn, or its next turn if it has ended. session.run() returns when the parent's turn ends, not when its subagents' turns end. Tell the parent to "wait for every result" when it must not answer before them.
  • A subagent stopped early still reports its last message, and the parent can message it to continue.
main /root>▌add CSV export▌is export_v1 still used?▌which pandas do we pin?▌> add CSV export■✓editexport.py> is export_v1 still used?■✓agents.spawnresearcher⎢ {"name": "researcher",⎢ "config_name": "researcher",⎣ "message": "is export_v1 still used?"}■✓editexport.py> which pandas do we pin?■✓agents.send_messageresearcher⎢ {"agent_path": "researcher",⎣ "message": "which pandas do we pin?"}■✓editcli.py← Agent message from /root/researcher⎣ pandas==2.2.3pandas==2.2.3■✓write_filetest_export.py■✓bashpytest⎣ 12 passed■✓waittimeout_ms: 600000← Agent result from /root/researcher⎢ export_v1: 3 callers⎣ cli.py · api.py · jobs.pyCSV export: done · 12 passedexport_v1: 3 callers (cli.py, api.py, jobs.py)researcher /root/researcher← Agent task from /root⎣ is export_v1 still used?■✓bashgrep -rn export_v1■✓read_filecli.py← Agent message from /root⎣ which pandas do we pin?■✓read_filepyproject.toml■✓agents.send_message/root⎢ {"agent_path": "/root",⎣ "message": "pandas==2.2.3"}■✓read_fileapi.py■✓read_filejobs.py■✓bashgit log -S export_v1export_v1: 3 callerscli.py · api.py · jobs.py
Step 1 of 14. The main agent works on your task: add CSV export.
Where subagents run

Where subagents run

  • Subagents run in the root's sandbox and share its files. You set the sandbox once, on the root agent. Give subagents that run at once separate files to write.
  • In your process, every sandbox works with subagents.
  • When you deploy with Studio, self-hosted or Mistral-managed, use MistralSandbox, which needs the cloud_user role, or NoSandbox. With another sandbox, an agent with subagents stops at startup.
Limits

Limits

At most 10 subagent turns run at once in the whole tree. The tree is two levels deep: subagents and their own subagents. Change the limits on the root agent with subagent_policy=. Add to the Agent above:

subagent_policy=agents.SubagentPolicy(concurrent_child_turn_limit=4),

Every field and its default: SubagentPolicy reference. When the tree is full, starting a subagent fails, and the parent's model reads the error like any other tool error.

See and stop subagents

See and stop subagents

await session.subagents() returns a session handle for each direct subagent, newest first. You can read a subagent and interrupt its turn, but only its parent gives it work: send_message, run, start_turn and cancel on its handle, and steer on its turn, raise agents.UnsupportedSessionProcedureError. When a subagent waits on a callback, such as an approval, the root's Blocked result lists it in state.callbacks with the subagent's IDs (Approvals across subagents). Interrupt its turn with await agent.session(callback.session_id).turn(callback.turn_id).interrupt().

To stop a subagent, cancel the root: await session.cancel() on the root session ends the root and every subagent. turn.interrupt() on the root's turn stops that turn only: the subagents keep running, and their results reach the root's next turn. In your process, the tree also stops when the event loop ends, so let the parent wait for its subagents before your program exits.

Common issues

Common issues

You seeCauseFix
The answer leaves out a subagent's resultThe parent's turn ended before the subagent's, so the result reached the next turn.Tell the parent to wait for every result before it answers.
A deployed agent stops at startup with subagents_with_unborrowable_sandboxIts subagents cannot share its sandbox.Use MistralSandbox or NoSandbox.