Workflows And Multi Agent Systems
Workflow Patterns
Every pattern in this chapter is a workflow by Module 1, Chapter 2's test: the developer decided the control flow, not the model.
Jr Codex Agentic AI Notes
Level: Advanced Prerequisites: Module 5, Chapter 3: LangGraph Time to complete: ~25 minutes
Table of Contents
- Orchestration Without Autonomy
- Sequential Chaining
- Parallel Fan-Out
- Routing
- Evaluator-Optimiser
- Event-Driven Workflows
- Composing Patterns
- Summary & Next Steps
1. Orchestration Without Autonomy
Every pattern in this chapter is a workflow by Module 1, Chapter 2's test: the developer decided the control flow, not the model.
Why a Module on Agents Covers Workflows
─────────────────────────────────────────
Because most problems people solve with agents are
workflows, and the workflow version is cheaper,
faster, testable and reproducible.
These patterns also compose WITH agents — an agent
inside a workflow node is common and good. What is
usually wrong is an agent where a workflow would
do.
─────────────────────────────────────────
2. Sequential Chaining
When It Applies
─────────────────────────────────────────
A ──► B ──► C
Each stage's output is the next stage's input, and
the order is known.
Use when: the task genuinely decomposes into fixed
stages — extract, then transform, then format.
─────────────────────────────────────────
def sequential(client, document):
facts = extract(client, document) # each stage gets a CLEAN context
checked = verify(client, facts, document) # containing only what it needs
return summarise(client, checked)The Underrated Benefit
─────────────────────────────────────────
Each stage has its own small context, so none of
them suffers the degradation from Module 2,
Chapter 1.
Three focused calls frequently beat one long agent
run on quality, not just on cost — because each
call is doing one thing with nothing else in the
way.
─────────────────────────────────────────
The Gate Variant
─────────────────────────────────────────
Insert a cheap check between stages and stop early
when it fails:
extract ──► [is it a valid invoice?] ──► no ──► STOP
│ yes
▼
transform
Saves the expensive stages on inputs that were
never going to work.
─────────────────────────────────────────
3. Parallel Fan-Out
import asyncio
async def fan_out(client, document, aspects):
"""Independent analyses, concurrently, then combined."""
results = await asyncio.gather(*[
analyse(client, document, aspect) for aspect in aspects
])
return await synthesise(client, dict(zip(aspects, results)))
# Latency becomes that of the SLOWEST branch, not the sum.
report = await fan_out(client, contract,
["payment terms", "liability", "termination", "IP"])Two Distinct Uses
─────────────────────────────────────────
SECTIONING different branches do DIFFERENT
work on the same input (the example
above). Combine by concatenation or
synthesis.
VOTING several branches do the SAME work,
and you take the majority. This is
the Gen AI Notes' self-consistency,
applied to a workflow stage.
─────────────────────────────────────────
async def vote(client, question, n=3):
answers = await asyncio.gather(*[classify(client, question) for _ in range(n)])
counts = collections.Counter(answers)
answer, votes = counts.most_common(1)[0]
return answer, votes / n # the AGREEMENT RATE gates what happens next4. Routing
from pydantic import BaseModel
from typing import Literal
class Route(BaseModel):
category: Literal["billing", "technical", "refund", "other"]
confidence: Literal["high", "low"]
def route(client, ticket):
r = client.chat.completions.parse(
model="gpt-4o-mini", # a SMALL model is enough to route
messages=[{"role": "system", "content": ROUTING_RULES},
{"role": "user", "content": ticket}],
response_format=Route, temperature=0,
).choices[0].message.parsed
if r.confidence == "low":
return handle_human_review(ticket) # do not guess on a weak signal
return HANDLERS[r.category](client, ticket)Why Routing Is Usually Better Than an Agent
─────────────────────────────────────────
A router plus specialised handlers gives each
branch a FOCUSED prompt and a SMALL tool set —
three tools rather than fifteen.
Module 2, Chapter 3 noted that tool selection
accuracy degrades past ~20 tools. Routing is the
cheapest way to keep every branch well under that
threshold.
You also get to use a cheap model for the routing
decision and an expensive one only where it
matters.
─────────────────────────────────────────
5. Evaluator-Optimiser
A generator and a separate evaluator, looping — Module 4, Chapter 3's reflection as an explicit workflow.
def evaluator_optimiser(client, task, criteria, max_rounds=3):
output = generate(client, task)
for round_num in range(max_rounds):
verdict = evaluate(client, output, criteria) # a SEPARATE call, fresh context
if verdict.passed:
return output, round_num
output = revise(client, task, output, verdict.issues)
return output, max_rounds # return best effort, flaggedWhen This Shape Earns Its Cost
─────────────────────────────────────────
Use it when the criteria are EXPLICIT and the
evaluation is cheaper than the generation —
Module 4, Chapter 3's condition.
Best case: the evaluator is not an LLM at all but
a compiler, a test suite or a schema validator.
Then the loop is nearly free and the signal is
perfect.
─────────────────────────────────────────
6. Event-Driven Workflows
Everything above is request-response. Real systems often need work triggered by events, running in the background.
async def on_event(event, ctx):
"""One handler, dispatched by event type. Each run is short and bounded."""
match event.type:
case "ticket.created":
route_and_enqueue(event.payload)
case "investigation.completed":
if event.payload["severity"] == "high":
await ctx.emit("escalation.required", event.payload) # CHAIN by event
case "approval.granted":
await ctx.resume(event.payload["thread_id"]) # Chapter 2Why Event-Driven Suits Agents
─────────────────────────────────────────
LONG RUNS an agent taking four minutes does
not hold an HTTP connection (Gen AI
Notes, Module 7, Chapter 1)
HUMAN GATES "wait for approval" is naturally an
event, not a blocking call
RETRY a failed handler re-runs from the
queue without re-running everything
before it
BACKPRESSURE the queue absorbs spikes instead of
hammering a rate-limited model API
─────────────────────────────────────────
The Requirement It Imposes
─────────────────────────────────────────
Handlers must be IDEMPOTENT. Queues deliver at
least once, so every handler will eventually run
twice on the same event.
Same discipline as Module 2, Chapter 3's tool
design: an idempotency key, checked before acting.
─────────────────────────────────────────
7. Composing Patterns
Real systems are combinations. The composition is where the design lives.
A Support System, Assembled
─────────────────────────────────────────
ticket ──► ROUTE (cheap model)
│
┌──────────┼──────────┐
▼ ▼ ▼
billing technical refund
(chain) (AGENT) (chain + human gate)
│ │ │
└──────────┼──────────┘
▼
EVALUATE the draft reply
│
┌───────┴────────┐
pass fail ──► revise (max 2)
▼
send
─────────────────────────────────────────
Read the Design Choices
─────────────────────────────────────────
ROUTING first, so each branch is focused and cheap.
Only the TECHNICAL branch is an agent — it is the
one whose steps genuinely depend on what is found.
Billing and refunds are known procedures.
The REFUND branch has a human gate because it
moves money (Module 8, Chapter 2).
ONE evaluator at the end, shared by all branches —
quality control in one place rather than three.
─────────────────────────────────────────
The Principle
─────────────────────────────────────────
Push autonomy DOWN and IN.
Keep the outer structure deterministic and
testable. Give agent autonomy only to the specific
inner nodes that need it.
This is how you get an agent's flexibility with a
workflow's predictability, and it is the shape
most successful production systems converge on.
─────────────────────────────────────────
8. Summary & Next Steps
Key Takeaways
- These patterns are workflows: the developer decides control flow, which makes them cheaper, testable and reproducible — and most agent problems are really workflow problems.
- Sequential stages give each call a clean, small context, so three focused calls often beat one long agent run on quality as well as cost.
- Routing keeps each branch's tool set small and lets a cheap model make the routing decision, directly addressing tool-selection degradation.
- Compose by pushing autonomy down and in: a deterministic outer structure with agent autonomy only at the nodes that genuinely need it.
Concept Check
- Why can splitting one agent run into three sequential calls improve quality rather than just cost?
- What property must event handlers have, and which earlier chapter's discipline does it reuse?
- In the composed support system, why is only the technical branch an agent?
Next Chapter
→ Chapter 2: State, Checkpointing & Durability
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Agentic AI Index