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.
How a run works
- 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
WorkflowPlanas JSON. The plan is data, not code, so nothing the model writes is executed. - 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_attemptstimes. - 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. - 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.
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).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 matchingWorkflowPlan:
Placeholders
Workers see only their own rendered prompt, not the task, so placeholders are how they get their material:Fan-out
Afor_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
agentis 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
taskoritem. - 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 afor_eachstep.for_eachlooks like<step_id>.<field>, the source is in an earlier phase, andfieldis declared in the source step’soutput_schemawith typearray(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_stepsis non-empty and names known steps.- The number of steps without
for_eachdoes not exceedmax_agents.
Methods
run()
Plan the task, run the phases and return the combined result.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.
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.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_retriestimes. - A fan-out item that still fails becomes
Nonein that step’s output list, is recorded inlast_run["failures"], and the run continues. - A single step that still fails raises
PhasedFanOutWorkflowErroronce its phase finishes. - A fan-out that would pass
max_agentsraises before any of its agents start.