DEV Community

Cover image for Building a Distributed Multi-Agent Course Creation System with Google ADK
Bezawada Haritha
Bezawada Haritha

Posted on

Building a Distributed Multi-Agent Course Creation System with Google ADK

Education Track: Build Multi-Agent Systems with ADK

What I built

A distributed multi-agent system that takes a topic and turns it into a fully structured course, deployed as independent microservices on Google Cloud Run and coordinated through the Agent-to-Agent (A2A) protocol.

The whole point was to stop thinking in terms of one massive prompt that does everything, and instead break the work into agents that each have one focused job. That shift in thinking was honestly the biggest takeaway from this lab.

The system has four agents:

  • Researcher - searches the web using google_search to pull in fresh, relevant information on the topic
  • Judge - critiques the research using Pydantic structured output, scoring quality and completeness
  • Escalation Checker - acts as the loop gatekeeper; a Pass verdict breaks the loop, a Fail verdict sends the Researcher back to try again
  • Content Builder - takes the validated research and turns it into a coherent, structured course

The architecture

The control flow is built using two orchestrator types from the ADK, and understanding the difference between them was where things clicked for me.

LoopAgent (Research Loop)

This runs Researcher → Judge → Escalation Checker in a cycle until the Judge issues a Pass. It is basically a while loop, but the exit condition is decided by an agent using structured output rather than a hardcoded boolean. The system corrects itself before any content ever gets generated.

SequentialAgent (Main Pipeline)

This runs the Research Loop first, waits for it to finish with validated data, then hands everything off to the Content Builder. The order is guaranteed. No content gets written from bad research.

Each agent lives in its own Cloud Run service. They communicate via the A2A protocol, which means any one of them can be scaled, swapped, or updated without touching the others. That is the part that makes this feel production-ready rather than just a script.


What I learned

Decomposition is harder than it looks. Writing the agents is the easy part. The harder question is deciding what each agent should not do. A Researcher that also evaluates its own output defeats the purpose entirely. Drawing those lines clearly made everything downstream simpler.

Structured output changes how you think about control flow. Having the Judge return a Pydantic model with a verdict, feedback, and score field rather than free text made the Escalation Checker completely deterministic. There is no string parsing, no prompt engineering for the check. It either says Pass or it does not.

LoopAgent reframes retries as a first-class concept. Before this lab I would have handled retries in application code with a try/except or a for loop. Seeing it modeled as an agent that participates in the conversation was a different way of thinking about quality control.

A2A is what separates this from a script. Running agents as independent HTTP services means you can swap a cheaper, faster model onto the Judge without ever touching the Researcher service. That kind of modularity only happens when the agents are genuinely decoupled.


Key code patterns

Researcher with tool use

researcher = Agent(
    name="researcher",
    model="gemini-2.0-flash",
    tools=[google_search],
    instruction="Research the given topic thoroughly and return detailed, factual findings."
)
Enter fullscreen mode Exit fullscreen mode

Judge with structured output

class JudgeVerdict(BaseModel):
    verdict: Literal["Pass", "Fail"]
    feedback: str
    score: int = Field(ge=0, le=10)

judge = Agent(
    name="judge",
    model="gemini-2.0-flash",
    output_schema=JudgeVerdict,
    instruction="Evaluate the research quality. Return Pass only if score is 7 or above."
)
Enter fullscreen mode Exit fullscreen mode

Composing the loop and pipeline

research_loop = LoopAgent(
    name="research_loop",
    max_iterations=3,
    sub_agents=[researcher, judge, escalation_checker]
)

pipeline = SequentialAgent(
    name="pipeline",
    sub_agents=[research_loop, content_builder]
)
Enter fullscreen mode Exit fullscreen mode

Running it locally

adk run pipeline
Enter fullscreen mode Exit fullscreen mode

The ADK dev UI spins up at localhost:8000 and you can watch each agent hand off to the next in real time.


Deploying to Cloud Run

Each agent has its own Dockerfile. Deploy them individually:

gcloud run deploy researcher-agent --source ./researcher
gcloud run deploy judge-agent --source ./judge
gcloud run deploy content-builder-agent --source ./content_builder
gcloud run deploy orchestrator --source ./orchestrator
Enter fullscreen mode Exit fullscreen mode

The orchestrator gets the URLs of the other services as environment variables and connects to them via the A2A client.


What I would build next

  • A human-in-the-loop step where a reviewer can approve or reject the Judge's feedback before the next iteration kicks off
  • Giving the Researcher access to internal documents through a RAG tool, not just web search
  • Using different models per agent, a faster cheaper model for the Judge and a stronger one for the Content Builder
  • A streaming frontend that shows the loop iterations happening in real time so users can actually see the self-correction process

One thing worth noting

The LoopAgent pattern is not specific to course creation. Anywhere you have an attempt, evaluate, retry cycle, this same structure applies. Code generation with a linter as the Judge, draft writing with an editor as the Judge, data extraction with a validator as the Judge. Once you see the pattern it shows up everywhere.

Top comments (0)