Skip to main content

Overview

PhasedFanOutWorkflow lets a lead agent design the multi-agent run for a task instead of you wiring it up in advance. The lead writes a plan of phases. Phases run in order, the steps inside a phase run in parallel, and a step can fan out to one fresh agent per item of a list an earlier step returned. When the last phase finishes, the lead reads the results and writes the answer. It is inspired by the dynamic workflows in Anthropic’s Claude Managed Agents, where an orchestrating agent decides at run time how many agents to start and what each one does.
For a task like this, the lead might plan one finder agent per file in the first phase, then one verifier per reported bug in the second. The number of verifiers is only known once the finders have answered.

How a run works

  1. Plan. The lead receives the task, the roster of predefined agents, whether it may define its own agents, the agent limit and a size guideline, and returns a WorkflowPlan as JSON. The plan is data, not code, so nothing the model writes is executed.
  2. Validate. The plan is checked against the rules in Plan validation. A rejected plan goes back to the lead with its errors, up to max_plan_attempts times.
  3. Execute. Each phase runs its steps on a thread pool of at most max_concurrent_agents. Every step gets a fresh agent, so no conversation state carries over between steps or runs.
  4. Combine. The lead receives the task, the plan that ran and the outputs of the return_steps, and writes the final answer.

When to use it

  • The work splits into stages, and how wide a stage is depends on what an earlier stage found: one agent per file, per bug, per contract, per search result.
  • You want independent checking: one agent finds, a different fresh agent verifies.
  • You don’t know the right team in advance and want the model to design it within limits you set.
Use SequentialWorkflow, ConcurrentWorkflow or GraphWorkflow when you already know the shape of the run.

Import

PhasedFanOutWorkflow is not exported from swarms and is not a SwarmRouter swarm type. Import it from its module.

Constructor

str
default:"PhasedFanOutWorkflow"
Name of the workflow. The default lead agent is named {name}-lead.
str
Description of the workflow.
Agent
default:"None"
The agent that plans and combines. When omitted, one is built from model_name with the built-in planning prompt. A lead you pass is copied fresh for every run, so it should be able to return the plan JSON described in The plan.
str
default:"gpt-5.4"
Model for the default lead agent. Ignored when lead_agent is given.
List[Agent]
default:"None"
Predefined agents a plan may use, by agent_name. The lead sees each one’s agent_name and agent_description, so write descriptions that say what the agent is for. Each use gets a fresh copy built from the agent’s constructor arguments. Names must be unique.
bool
default:"True"
Whether the plan may define its own agents, each with a name and a system prompt. Set it to False to restrict the plan to agents.
str
default:"None"
Model for inline agents. Defaults to the lead agent’s model. Predefined agents keep their own models.
int
default:"1000"
Hard limit on worker agents started in one run, the lead not included. A plan whose fixed steps already exceed it is rejected, and a fan-out that would pass it raises before any of its agents start. Retries do not count.
int
default:"16"
Agents that may run at the same time within a phase. Must be between 1 and 256.
str
default:"medium"
Tells the lead how many agents to aim for. It is guidance in the prompt; max_agents is the enforced limit.
int
default:"2"
Retries for a failed agent, each with a new fresh agent, before its step fails. 0 disables retries.
int
default:"3"
Plans the lead may write, including repairs after rejection, before the run raises.
OutputType
default:"final"
Format of the value run() returns. "final" returns the lead’s answer. Other values format the run’s conversation (see Return value).
Raises: ValueError if max_agents is below 1, max_concurrent_agents is outside 1 to 256, size_guideline is unknown, max_retries is negative, max_plan_attempts is below 1, predefined agent names repeat, or there are no predefined agents and inline agents are not allowed.

The plan

The lead returns JSON matching WorkflowPlan:

Placeholders

Workers see only their own rendered prompt, not the task, so placeholders are how they get their material:

Fan-out

A for_each step reads a list field from an earlier step’s structured output and starts one agent per item. If the source step is itself a fan-out, the lists from all its agents are joined. The output of a fan-out step is a list with one entry per item, in item order. A failed item’s entry is None.

Plan validation

Every rule below is checked before any agent runs. A plan that breaks one is sent back to the lead with the reasons.
  • Every step’s agent is a predefined or inline agent.
  • Inline agents are allowed (allow_inline_agents), have unique names, and don’t reuse a predefined agent’s name.
  • There is at least one phase, and every phase has at least one step.
  • Step ids are unique and are not task or item.
  • Every step prompt contains {task}, {item} or a {step_id}. A step with none of them would give its agent nothing to work on.
  • A {step_id} names a step in an earlier phase. Steps in the same phase run at the same time and can’t see each other.
  • {item} appears only in a for_each step.
  • for_each looks like <step_id>.<field>, the source is in an earlier phase, and field is declared in the source step’s output_schema with type array (or no type).
  • No two steps in a phase have the same agent, prompt and for_each, since they would return the same output.
  • return_steps is non-empty and names known steps.
  • The number of steps without for_each does not exceed max_agents.
You can run the same checks yourself:

Methods

run()

Plan the task, run the phases and return the combined result.
Parameters:
  • task (str): The task. Put all the material workers need in it; they reach it through {task}.
  • img (str, optional): An image passed to the lead when planning. Workers do not receive it.
Returns: The run’s conversation formatted by output_type. With the default "final", the lead’s answer. Raises: PhasedFanOutWorkflowError if no valid plan is written within max_plan_attempts, a fan-out would pass max_agents, a fan-out source does not hold a list at the for_each field, or a step without for_each fails after its retries.

validate_plan()

Check a plan without running it.
Returns: The problems found; an empty list when the plan is valid.

plan()

Ask the lead for a valid plan, sending errors back for repair. run() calls it with the run’s fresh lead.
In the current release plan() uses the lead that run() creates, so calling it before any run() raises AttributeError. To inspect a plan, call run() and read last_plan.

Attributes after a run

Each entry in last_run["failures"] is a dict with phase, step, item and error.

Return value

run() records the run in a Conversation and returns it through output_type. The conversation holds, in order:

Failure handling

  • A failed agent is retried with a new fresh agent up to max_retries times.
  • A fan-out item that still fails becomes None in that step’s output list, is recorded in last_run["failures"], and the run continues.
  • A single step that still fails raises PhasedFanOutWorkflowError once its phase finishes.
  • A fan-out that would pass max_agents raises before any of its agents start.

Usage Examples

Predefined agents only

Restrict the plan to agents you trust and let the lead decide how many copies of each to run.

Inline agents on a cheaper model

More walkthroughs are in the PhasedFanOutWorkflow examples.

Source Code