The first collaboration pattern is the one you control completely. When the order of work is predictable, you write it in code rather than letting the model decide.
This is the workflow-versus-agent decision from the very start of this series, one level up. There it was "should the model or your code decide the next step?" Here the steps are whole agents, and the same answer holds: if you can see the order in advance, write it down.
Three building blocks
Agent workflows are built from three control-flow shapes, the same three any program uses.
Sequential: one after another. Run agent A, feed its output to agent B, then to agent C. Research, then write, then review. Each agent's result is the next one's input.
async def sequential(agents, task):
result = task
for agent in agents: # A -> B -> C, in order
result = await agent.run(result)
return result
Parallel: all at once. Run several agents on the same input at the same time, and gather their outputs. Use it when you want independent perspectives, three analysts looking at the same data, with no dependency between them.
async def parallel(agents, task):
results = await asyncio.gather(*(agent.run(task) for agent in agents))
return combine(results) # independent runs, gathered
The asyncio.gather is the same concurrency the earlier post used for many model calls, now running
whole agents at once. Three independent analyses finish in about the time of the slowest one, well under their combined time.
Loop: until it is good enough. Run an agent, check the result against a condition, and run again until the condition holds. A writer drafts, a reviewer checks, and the draft goes back until the reviewer approves or a limit is hit.
async def loop(agent, task, done, max_rounds=5):
result = await agent.run(task)
for _ in range(max_rounds):
if done(result): # the exit condition
break
result = await agent.run(result)
return result
The max_rounds is the same seatbelt as an agent's step limit: a loop with no bound is a loop that can
run forever, so you cap it.
Sequential runs agents in order, parallel runs them at once, loop repeats until a condition holds.
Composing the blocks
The three shapes are pieces you nest. Real pipelines combine them: run three researchers in parallel, feed their combined output to a writer, then loop the writer and a reviewer until the piece passes.
async def article_pipeline(topic):
research = await parallel([web_researcher, paper_researcher, data_researcher], topic)
draft = await writer.run(research) # sequential step
final = await loop(reviser, draft, done=reviewer_approves) # loop until approved
return final
Read the shape: parallel research, a sequential write, then a review loop. Each block is one of the three primitives, and the whole pipeline is code you can read top to bottom, test, and reason about. No model chose this order. You did, because you knew it in advance.
Compose the primitives: parallel research, a sequential write, a review loop, all in code.
Why choose a workflow over letting the model orchestrate
A workflow gives up flexibility and buys certainty, and for predictable work that is a good trade.
The pipeline runs the same way every time, which makes it testable and debuggable: a failure is in a known step, not somewhere in a chain the model invented. It costs no extra model calls to decide the order, because the order is already decided. And it stays on the rails you laid, because the path is fixed in code.
The cost is that it only handles the pipeline you wrote. A request that needs a different order falls outside it. That is exactly when you reach for the next two patterns, where the model decides the order at runtime, which the following posts build.
| Workflow of agents | Model-orchestrated | |
|---|---|---|
| Who sets the order | You, in code | The model, at runtime |
| Predictability | Same every run | Varies with the request |
| Testable | Yes, a known pipeline | Harder, an invented chain |
| Handles novel requests | Only the one you wrote | Adapts |
| Best for | A known, repeated process | Varied, unpredictable work |
What to take from this
- When the order of work is predictable, write it in code. A workflow of agents is the workflow-versus- agent decision from the start of this series, applied to whole agents.
- Three primitives build any pipeline: sequential (in order), parallel (at once, gathered), and loop (until a condition holds, with a bound). You compose them by nesting.
- A workflow trades flexibility for certainty. It runs the same way every time and is testable, but handles only the pipeline you wrote. Novel orders need the model to orchestrate.
Some requests need research, some need code, some need both, and you cannot know which in advance. For those, the model has to pick the specialist at runtime. The next post makes an agent callable as a tool so it can.