Lab 02 — Workflow Agents: Sequential / Parallel / Loop
Phase 22 · Lab 02 · Phase README · Warmup
The problem
Here is the single distinction that separates someone who uses Google ADK from someone who can be trusted to own it — and interviewers probe it directly:
- LLM-driven delegation (
sub_agentson anLlmAgent): a coordinator agent reads each child'sdescriptionand, at runtime, the model decides which sub-agent to hand control to (transfer_to_agent). Flexible, but non-deterministic — the LLM is the router. - Workflow agents (this lab): the orchestration is fixed in code.
SequentialAgent,ParallelAgent, andLoopAgentschedule theirsub_agentsin a guaranteed structure. No LLM decides the order.
The ADK philosophy in one line: deterministic structure around LLM reasoning. You get reliable, testable, replayable pipelines while still using LLM reasoning inside each leaf. And you should reach for it constantly — reliability compounds (Phase 00), so code you can test beats a model you have to trust whenever the flow is knowable.
You build the three workflow agents:
SequentialAgent— runs sub-agents in listed order, sharing ONE session state, so a step sees the previous step'soutput_key. The ADK data-flow pipeline.ParallelAgent— runs sub-agents on the SAME input in isolated branches, then merges their outputs. Branches must be independent (one must not read another's write).LoopAgent— repeats its sub-agents until a sub-agent signalsescalate(ADK's "stop the loop", e.g. anexit_looptool or a checker settingactions.escalate=True) OR amax_iterationsbound is hit. The iterative-refinement / critic-loop pattern.
Leaves are injected as pure policies (state -> text), so everything is deterministic and
offline — exactly how you unit-test an ADK workflow: the structure is verified independently of
the model. Parallel "concurrency" is modeled deterministically (run each branch against the same
input snapshot, merge deltas) — same semantics, reproducible order.
What you build
| Object | Semantics | The lesson |
|---|---|---|
LlmAgent (leaf) | policy(state) -> text; writes output_key to shared state | the deterministic stand-in for a reasoning agent |
CheckerAgent | emits escalate = condition(state) | the exit_loop-style stopper a LoopAgent watches |
SequentialAgent | run sub-agents in order, shared state | state flows downstream via output_key |
ParallelAgent | fan out on the same input, merge outputs | independent branches; fan-in merge |
LoopAgent | repeat until escalate or max_iterations | bounded iterative refinement |
Runner / run_to_list | drive an agent against a session, yield Events | the same driver across all three labs |
File map
| File | Role |
|---|---|
lab.py | your implementation (fill the # TODOs) |
solution.py | reference implementation + worked example (python solution.py) |
test_lab.py | 19 tests: order + state flow, fan-out isolation + merge, loop termination, nesting, determinism |
requirements.txt | pytest only |
Run it
pytest test_lab.py -v # your lab.py (red until you implement)
LAB_MODULE=solution pytest test_lab.py -v # the reference (green)
python solution.py # worked example
Success criteria
-
SequentialAgentruns sub-agents in listed order and agent-2 can read agent-1'soutput_keyfrom shared state. -
ParallelAgentruns every branch, each sees the same input (not a sibling's write), and all outputs are merged into final state. -
LoopAgentrepeats until aCheckerAgentescalates, stops immediately on escalation (a later sub-agent in that iteration does not run), and is bounded bymax_iterations. -
Nesting works: a
SequentialAgentof aLoopAgent, aParallelAgentinside aSequentialAgent. -
You can state, out loud, workflow agents vs
sub_agenttransfer. All 19 tests pass under bothlabandsolution.
How this maps to the real stack
SequentialAgent,ParallelAgent, andLoopAgentare the real classes ingoogle.adk.agents, all subclasses ofBaseAgent. In real ADK,SequentialAgentruns its sub-agents over the sameInvocationContext/session sooutput_keystate genuinely propagates down the pipeline — the canonical example is a write-code → review-code → refactor-code chain.- Real
ParallelAgentruns sub-agents concurrently in isolated branches of the invocation that still share one session state; because there is no ordering guarantee, the branches must be independent. We reproduce those semantics deterministically (same input snapshot per branch, merge deltas) so tests are reproducible — the correctness property (independent fan-out, fan-in merge) is identical. - Real
LoopAgentterminates when a sub-agent's event hasactions.escalate=True(typically set by anexit_loopFunctionToolor a critic sub-agent) or whenmax_iterationsis reached — exactly ourCheckerAgent+ bound. This is how ADK expresses generate/critique/refine loops. - The contrast that matters: put the same three leaves under an
LlmAgentwithsub_agentsandtransfer_to_agent, and the model would choose the order at runtime (LLM-driven, non-deterministic). Workflow agents make that choice in code. Real systems use both: deterministic workflow scaffolding with LLM leaves, and an LLM coordinator only where runtime routing is genuinely needed.
Limits of the miniature. No true concurrency (parallel is deterministic-sequential), no async, no cancellation/timeouts on a branch, and leaves are pure policies rather than real LLM agents. The orchestration semantics — order + state flow, independent fan-out + merge, repeat-until- escalate — are faithful, which is the part interviewers test.
Extensions (your own machine)
- Add an
LlmAgentcoordinator withsub_agentsand atransfer_to_agenttool, and watch the injected policy pick a child by itsdescription— then compare its (in)determinism to the workflow agents side by side. - Give
ParallelAgentreal threads (concurrent.futures) and show that with a shared mutable state dict you get races — motivating the isolated-branch + merge design. - Add a
LoopAgentwith a real critic: a leaf whose policy grades the draft and only escalates above a score threshold; plot iterations-to-convergence.
Interview / resume signal
"Implemented Google ADK's deterministic workflow agents —
SequentialAgent(state flow viaoutput_key),ParallelAgent(independent fan-out + fan-in merge), andLoopAgent(repeat-until-escalate with amax_iterationsbound) — and can articulate the core ADK distinction: deterministic, code-defined orchestration around LLM leaves vs LLM-drivensub_agenttransfer, and when to choose each."