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_agents on an LlmAgent): a coordinator agent reads each child's description and, 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, and LoopAgent schedule their sub_agents in 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:

  1. SequentialAgent — runs sub-agents in listed order, sharing ONE session state, so a step sees the previous step's output_key. The ADK data-flow pipeline.
  2. 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).
  3. LoopAgent — repeats its sub-agents until a sub-agent signals escalate (ADK's "stop the loop", e.g. an exit_loop tool or a checker setting actions.escalate=True) OR a max_iterations bound 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

ObjectSemanticsThe lesson
LlmAgent (leaf)policy(state) -> text; writes output_key to shared statethe deterministic stand-in for a reasoning agent
CheckerAgentemits escalate = condition(state)the exit_loop-style stopper a LoopAgent watches
SequentialAgentrun sub-agents in order, shared statestate flows downstream via output_key
ParallelAgentfan out on the same input, merge outputsindependent branches; fan-in merge
LoopAgentrepeat until escalate or max_iterationsbounded iterative refinement
Runner / run_to_listdrive an agent against a session, yield Eventsthe same driver across all three labs

File map

FileRole
lab.pyyour implementation (fill the # TODOs)
solution.pyreference implementation + worked example (python solution.py)
test_lab.py19 tests: order + state flow, fan-out isolation + merge, loop termination, nesting, determinism
requirements.txtpytest 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

  • SequentialAgent runs sub-agents in listed order and agent-2 can read agent-1's output_key from shared state.
  • ParallelAgent runs every branch, each sees the same input (not a sibling's write), and all outputs are merged into final state.
  • LoopAgent repeats until a CheckerAgent escalates, stops immediately on escalation (a later sub-agent in that iteration does not run), and is bounded by max_iterations.
  • Nesting works: a SequentialAgent of a LoopAgent, a ParallelAgent inside a SequentialAgent.
  • You can state, out loud, workflow agents vs sub_agent transfer. All 19 tests pass under both lab and solution.

How this maps to the real stack

  • SequentialAgent, ParallelAgent, and LoopAgent are the real classes in google.adk.agents, all subclasses of BaseAgent. In real ADK, SequentialAgent runs its sub-agents over the same InvocationContext/session so output_key state genuinely propagates down the pipeline — the canonical example is a write-code → review-code → refactor-code chain.
  • Real ParallelAgent runs 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 LoopAgent terminates when a sub-agent's event has actions.escalate=True (typically set by an exit_loop FunctionTool or a critic sub-agent) or when max_iterations is reached — exactly our CheckerAgent + bound. This is how ADK expresses generate/critique/refine loops.
  • The contrast that matters: put the same three leaves under an LlmAgent with sub_agents and transfer_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 LlmAgent coordinator with sub_agents and a transfer_to_agent tool, and watch the injected policy pick a child by its description — then compare its (in)determinism to the workflow agents side by side.
  • Give ParallelAgent real threads (concurrent.futures) and show that with a shared mutable state dict you get races — motivating the isolated-branch + merge design.
  • Add a LoopAgent with 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 via output_key), ParallelAgent (independent fan-out + fan-in merge), and LoopAgent (repeat-until-escalate with a max_iterations bound) — and can articulate the core ADK distinction: deterministic, code-defined orchestration around LLM leaves vs LLM-driven sub_agent transfer, and when to choose each."