Async Handoffs
Use spawn_workflows() when a workflow needs to hand discovered work off to independent top-level workflows instead of keeping everything inside one run.
If you are explicitly modelling agent runs, spawn_agents() and await_agents() are semantic aliases with agent-oriented defaults.
This is useful for agent-style orchestration:
- a planner workflow discovers tasks at runtime
- each task becomes its own durable workflow
- the parent stores those workflow IDs in state
- the parent can later wait for
all,any, or a selected subset viaawait_workflows()
The ->spawn_workflows() method
use Queuety\Enums\WaitMode;
use Queuety\Queuety;
$agent_task = Queuety::workflow( 'agent_task' )
->then( ResearchTopicStep::class )
->then( SummarizeTopicStep::class );
Queuety::workflow( 'brief_research' )
->then( PlanTopicsStep::class )
->spawn_workflows(
items_key: 'topics',
workflow_builder: $agent_task,
result_key: 'child_workflow_ids',
)
->await_workflows( 'child_workflow_ids', WaitMode::All, 'child_results' )
->then( SynthesizeBriefStep::class )
->dispatch( [ 'brief_id' => 42 ] );In that example:
PlanTopicsStepwrites an array of topic payloads to$state['topics']spawn_workflows()dispatches oneagent_taskworkflow per topic- the spawned workflow IDs are written to
$state['child_workflow_ids'] await_workflows()pauses until those workflows completeSynthesizeBriefStepreceives the completed child states under$state['child_results']
Child payloads
Each item in items_key becomes the initial payload for one spawned workflow.
If the item is an array, its public keys are merged into the child payload:
[
[ 'topic' => 'pricing', 'source' => 'competitors' ],
[ 'topic' => 'reviews', 'source' => 'customers' ],
]If the item is scalar, Queuety stores it under payload_key:
->spawn_workflows(
items_key: 'topic_names',
workflow_builder: $agent_task,
payload_key: 'topic',
)Inheriting parent state
By default, spawn_workflows() merges the parent workflow's public state into every child payload.
That means shared context such as brief_id, account_id, or campaign is automatically available in each spawned workflow.
The source items_key and the result_key are excluded from inheritance so each child does not receive the whole task list or the final workflow ID array.
Set inherit_state: false when each child should receive only its item payload.
Why not sub_workflow()?
sub_workflow() creates a parent-child relationship where the parent parks on that step until the child completes.
spawn_workflows() is looser:
- the spawned workflows are independent top-level workflows
- each spawned workflow can be inspected, retried, or exported on its own
- the parent decides later whether and how to wait on them
That makes spawn_workflows() a better fit for async agent task handoff, while sub_workflow() remains the better fit for nested composition inside one orchestration tree.
Joining later
spawn_workflows() is designed to pair with Workflow Dependencies:
use Queuety\Enums\WaitMode;
Queuety::workflow( 'review_board' )
->spawn_workflows( 'review_tasks', $review_task_workflow, 'review_workflow_ids' )
->await_workflows( 'review_workflow_ids', WaitMode::Any, 'winner' )
->then( FinalizeDecisionStep::class )
->dispatch( $payload );WaitMode::Allcollects all completed child statesWaitMode::Anyresumes on the first successful child workflow
This gives you planner/executor orchestration without needing a full runtime-editable DAG engine.
Agent-friendly aliases
spawn_agents() is an alias for spawn_workflows() with defaults tuned for planner/executor flows:
result_keydefaults toagent_workflow_idspayload_keydefaults toagent_tasknamedefaults tospawn_agents
await_agents() is an alias for await_workflows() with defaults tuned for collecting agent results:
workflowsdefaults toagent_workflow_idsresult_keydefaults toagent_resultsnamedefaults toawait_agents
use Queuety\Enums\WaitMode;
$agent_run = Queuety::workflow( 'agent_run' )
->then( ResearchTopicStep::class )
->then( SummarizeTopicStep::class );
Queuety::workflow( 'brief_research' )
->then( PlanTopicsStep::class )
->spawn_agents( 'agent_tasks', $agent_run )
->await_agents( mode: WaitMode::All )
->then( SynthesizeBriefStep::class )
->dispatch( [ 'brief_id' => 42 ] );