Agent workflows: Build a self-correcting course system

Blog 15 min read

Fifty-seven percent of organizations now deploy multi-step agent workflows, marking a decisive shift toward distributed architectures. You will learn how to construct a Course Creation System using distinct Researcher, Judge, and Content Builder agents to handle real-world complexity.

The guide details the mechanics of Agent-to-Agent (A2A) protocols for connecting remote services and enforces structured output via Pydantic. It distinguishes between standard workers that execute tools and Orchestrator Agents that manage delegation without direct tool access. Specific attention is given to the LoopAgent, which functions as a conditional while loop to validate research quality before proceeding.

Finally, the text covers deploying these distributed AI components on Google Cloud Run after local testing with the ADK. By implementing an EscalationChecker, the system ensures the SequentialAgent only advances when the Judge approves the data. This approach replaces fragile single-model attempts with a reliable, self-correcting pipeline capable of managing complex workflows.

The Role of Specialized Agents in Modern AI Architectures

Defining the Multi-Agent System and Specialized Roles

Forget the giant prompt. Splitting complex work into specialized roles beats trying to force a single model to do everything. This approach shifts focus from isolated apps to coordinated agent sets using explicit routing. Single large language models answer questions well enough, yet real-world complexity demands specific roles just as backend engineers and designers focus on distinct tasks in software development. Currently, 57% of organizations now deploy multi-step agent workflows in production to handle such complexity.

The reference implementation, called the Course Creation System, shows this modality through four distinct parts. A Researcher Agent uses `google_search` to gather current data while a Judge Agent critiques output quality using structured Pydantic schemas. A Content Builder Agent turns approved research into final deliverables, and an Orchestrator Agent manages the communication flow between these specialists.

Agent Role Primary Function Output Constraint
Researcher Information retrieval Text summary
Judge Quality validation JSON Pass/Fail
Content Builder Synthesis Structured course
Orchestrator Workflow management Delegation signals

Enforcing structured output via schema validation stops the ambiguity common in generative text responses. This rigidity introduces coordination overhead that monolithic prompts avoid entirely. Developers must define strict interfaces and error protocols for every handoff between the Researcher and Judge. Agent loops can fail to terminate or produce incoherent results without these set boundaries. Modularity improves debuggability but demands rigorous interface contracts as its cost.

Implementing Structured Output with Pydantic and JudgeFeedback

The Judge Agent enforces deterministic evaluation by requiring outputs matching a strict Pydantic schema named JudgeFeedback. This model restricts response to a `status` field containing literal "pass" or "fail" values plus a text `feedback` string. Rigid typing stops the large language model from generating conversational filler or attempting unauthorized delegation to peer agents.

Configuration sets the judge agent with `disallow_transfer_to_parent=True` and `disallow_transfer_to_peers=True`. This restriction forces the agent to return only the structured JudgeFeedback and prevents chatting with users or delegating tasks. Such architectural constraint forces the system to rely on explicit schema adherence for automation logic instead of parsing natural language nuances. The resulting JSON structure allows the LoopAgent orchestrator to programmatically determine whether to retry the research step or proceed to content generation.

Engineers must implement strong retry mechanisms or fallback parsers to handle edge cases where the model ignores system instructions. A single formatting error in the Judge Agent output can break the feedback loop entirely without these safety valves.

Field Type Allowed Values
status string "pass", "fail"
feedback string Any text

This design pattern shifts complexity from prompt engineering to data contract definition. Builders gain predictable behavior at the cost of reduced flexibility in how the judge explains reasoning. High-volume automated pipelines favor this approach where consistency outweighs detailed explanation.

Monolithic Prompts vs Distributed Teams Using Agent-to-Agent Protocol

Unstructured outputs from monolithic prompts complicate debugging compared to modular designs. Small, focused agents are easier to evaluate and debug; if research is bad, you iterate on the Researcher's prompt specifically. By 2027, approximately a majority of production multi-agent systems apply the 'orchestrator-worker' topology to manage these interactions efficiently.

Small, focused agents isolate failure domains, allowing engineers to iterate on specific logic without breaking unrelated system components. The Agent-to-Agent (A2A) protocol enables this by connecting remote agents and constructing LoopAgent structures for feedback, differentiating it from hierarchical chains that suffer from communication errors.

Feature Monolithic Prompt Distributed Team
Debugging High difficulty; cascading failures Isolated component testing
Context Single large window (truncation risk) Distributed memory via session.state
Orchestration Implicit linear generation Explicit A2A delegation

Explicit handoffs enforce structured output through this architecture rather than hoping for compliant JSON from a massive prompt.

Inside Agent Orchestration with LoopAgent and SequentialAgent

LoopAgent and SequentialAgent Mechanics in the A2A Protocol

Distributed coordination happens when the Agent-to-Agent (A2A) protocol treats agents as independent microservices talking over HTTP. Monolithic prompts cannot match this architecture, which depends on the RemoteA2aAgent client to discover and route messages between specialized roles. Connections rely strictly on environment variables like RESEARCHER_AGENT_CARD_URL, JUDGE_AGENT_CARD_URL, and CONTENT_BUILDER_AGENT_CARD_URL, pointing each service to well-known JSON endpoints.

Two topologies govern execution flow. The LoopAgent runs iterative feedback cycles, repeating sub-agents until a condition is met or a maximum iteration count is reached. The SequentialAgent orchestrates linear pipelines, passing control to the next stage only after the previous task completes.

Feature LoopAgent SequentialAgent
Flow Control Iterative (While/Until) Linear (Step 1 → Step 2)
Primary Use Refinement and Validation Pipeline Staging
Termination Condition or Max Iterations Completion of Final Step

Google's implementation builds a research_loop where a Researcher gathers data and a Judge critiques it, cycling until quality thresholds are satisfied. This loop functions as a single node within a broader SequentialAgent pipeline that triggers content generation. Latency conflicts with accuracy here. Loops improve output quality through repetition but introduce variable execution times that complicate timeout configurations for downstream services. Set iteration limits manage execution duration during partial failures. Recent observations indicate that coding agent sessions have grown from an average duration of 4 minutes to 23 minutes, reflecting increased complexity and multi-file engagement.

Hierarchical Composition: Nesting Research Loops in Course Creation Pipelines

Hierarchical composition treats the research_loop as a single executable unit inside the broader course_creation_pipeline. This architecture nests a LoopAgent inside a SequentialAgent to enforce iterative data validation before linear content generation begins. The outer sequence first executes the inner loop, which cycles through researcher and judge agents until the escalation_checker detects a "pass" status or reaches the iteration limit. Only after the loop terminates does the pipeline invoke the content_builder to synthesize findings. Technical content generation requires verified facts before drafting, mirroring engineering workflows. Convergence speed conflicts with output quality. Setting `max_iterations` too low risks accepting incomplete research, while excessive looping increases latency without guaranteed improvement. Modular design isolates the feedback mechanism, allowing operators to tune evaluation logic independently of synthesis rules. Complex reasoning tasks benefit from explicit separation between verification cycles and production steps. Constraining the LoopAgent to return structured JudgeFeedback prevents the drift often seen in open-ended conversational chains. Final output relies on certified data rather than probabilistic guesses.

Feature Monolithic Prompt Hierarchical Agents
Error Handling Implicit via retry Explicit via loop break
Debugging Full trace required Isolated to sub-agent
Flow Control Linear or random Structured nesting

Task accuracy depending on preceding validation steps demands this pattern. The Agent-to-Agent (A2A) protocol enables distinct session states for each sub-process. A SequentialAgent named 'course_creation_pipeline' stitches the final system together using two sub-agents: the research_loop and the content_build.

Configuring Escalation Checkers and Max Iterations for Loop Control

The EscalationChecker prevents infinite cycles by monitoring session state for a literal "pass" status rather than invoking an LLM. This custom agent, built on BaseAgent, executes deterministic Python logic to inspect `judge_feedback` within the shared context. If the status field equals "pass", the checker yields an Event with `actions=EventActions(escalate=True)`, forcing the parent LoopAgent to terminate immediately. A "fail" status allows the loop to restart the researcher sub-agent. Operators must configure `max_iterations` as a hard safety boundary to handle cases where the judge never returns a passing grade.

Component Logic Type Termination Trigger
EscalationChecker Deterministic Python `status == "pass"`
Max Iterations Configurable Limit Count reaches threshold
Judge Agent LLM Evaluation N/A (Provides input)

Premature termination occurs if `max_iterations` is set too low before data quality improves. Excessive limits waste compute resources during repeated failures. Most production systems apply an orchestrator-worker topology to isolate these failure domains effectively. Sufficient refinement time conflicts with preventing runaway costs in distributed environments. The escalation signal functions as a binary circuit breaker that overrides standard sequential flow. Feedback loops remain bounded even when the Agent-to-Agent (A2A) protocol connects remote microservices with varying latency profiles. Validating these termination conditions against worst-case latency scenarios is recommended before deployment.

Deploying Distributed AI Systems on Google Cloud Run

Prerequisites for Cloud Run and Agent Dependencies

Deploying distributed agents requires enabling five specific Google Cloud services via `gcloud services enable`. Operators must activate run.googleapis.com, artifactregistry.googleapis.com, cloudbuild.googleapis.com, aiplatform.googleapis.com, and compute.googleapis.com before any code execution. This strict dependency chain ensures the Production Ready AI Roadshow infrastructure functions correctly without runtime permission errors. Neglecting a single service, such as compute.googleapis.com, causes silent failures during container image builds rather than clear API rejection messages.

Environment configuration relies on uv for rapid dependency synchronization and reproducible Python environments. Installing the tool via `pip install uv` and running `uv sync` locks package versions to prevent drift between local testing and cloud deployment.

  1. Clone the starter repository into the working directory.
  2. Execute the service enablement command for all five required APIs.
  3. Initialize the .env file with project identifiers and location flags.
  4. Run `source.env` to export variables into the current shell session.

A critical operational constraint involves session persistence; environment variables set in .env do not survive terminal disconnections automatically. Engineers must explicitly re-source the file after any network interruption to maintain valid authentication contexts for the Agent-to-Agent protocol.

Validating Agents Locally with ADK Interactive Runtime

Validate individual agent logic before distributed integration using the `adk run` command within the interactive runtime. This isolated execution mode allows engineers to verify Pydantic schema adherence and tool-calling behavior without the overhead of full Agent-to-Agent (A2A) protocol coordination. Testing the Judge agent locally reveals strict output constraints that monolithic prompts often miss.

  1. Execute the judge in isolation via `uv run adk run agents/judge`.
  2. Simulate input with insufficient data, such as "Topic: Tokyo. Findings: Tokyo is a city."
  3. Observe the returned structured object where `status='fail'` confirms the rejection of brief findings.

This process ensures the JudgeFeedback schema correctly enforces quality gates before content synthesis begins. A critical tension exists between rapid iteration and rigorous validation; skipping local checks forces debugging into the slower cloud deployment cycle. While small agents simplify evaluation, neglecting local verification of the specialized roles introduces latent failure modes in the feedback loop. Operators must confirm that the escalation checker receives valid status flags to prevent infinite retry cycles in production.

Environment Variable Configuration for Vertex AI Connectivity

Define GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION as global, and GOOGLE_GENAI_USE_VERTEXAI as true within a `.env` file.

  1. Create the configuration file using `cat` to export these three specific variables.
  2. Execute `source.env` to load credentials into the active shell session.
  3. Verify connectivity by running individual agents before initiating the full distributed system.
Variable Required Value Function
GOOGLE_CLOUD_PROJECT Flexible ID Identifies billing account
GOOGLE_CLOUD_LOCATION global Sets regional endpoint
GOOGLE_GENAI_USE_VERTEXAI true Enables Vertex AI routing

Shell sessions do not persist variables across disconnects, requiring operators to re-source the file after every terminal reset. This manual step prevents silent authentication failures during long debugging cycles where background tokens expire. Misconfigured paths often manifest as generic connection timeouts rather than explicit permission errors, complicating root cause analysis for new deployments. Engineers must treat environment reloading as a mandatory precondition for any ADK invocation.

Resolving Common Failures in Agent Tool Access and Output

Enforcing Determinism with disallow_transfer Flags

Setting `disallow_transfer_to_parent=True` and `disallow_transfer_to_peers=True` on the Judge Agent removes non-deterministic delegation paths that frequently break automation pipelines. This configuration forces the agent to return literal `pass` or `fail` status values instead of generating open-ended text. Specialized roles improve reliability, yet rigid output constraints create tension between safety and adaptability because the agent cannot request clarification when input data remains ambiguous.

Poorly configured tool access introduces specific operational risks:

  • Pipeline corruption occurs when unstructured text returns where JSON is expected.
  • Debugging complexity increases when tool access in LLM sessions lack set boundaries.
  • Downstream agents fail to parse responses that deviate from the strict schema.
  • Automated tests produce false negatives due to format mismatches rather than logic errors.

These flags serve as necessary parameters to keep the agent within its set role. A drawback is that this approach assumes the initial prompt provides enough context for a binary decision. If research findings are fundamentally unclear, the judge will deterministically return `fail` without the ability to negotiate improved input. This constraint favors system stability over conversational flexibility, ensuring that only validated data proceeds to the content builder.

Simulating Judge Failures via Interactive Runtime

Executing `uv run adk run agents/judge` with insufficient input like "Topic: Tokyo. Findings: Tokyo is a city." immediately verifies if the Judge Agent correctly returns `status='fail'`. This isolated test confirms the Pydantic model enforces the JudgeFeedback schema before distributed orchestration begins. Engineers must validate this deterministic behavior because specialized agents are increasingly designed to be relatively ignorant of the broader workflow to improve reliability.

Rigid output constraints introduce specific operational friction regarding error visibility. When the judge rejects input, it provides no natural language explanation unless the feedback field is explicitly populated by the LLM logic.

  • Silent failures occur if the disallow_transfer_to_parent flag prevents the agent from requesting clarification.
  • Debugging requires parsing structured JSON rather than reading conversational logs.
  • Network timeouts during schema validation can mimic logical failures without clear distinction.
  • Missing error codes make root cause analysis difficult in production logs.

Deployment pipelines must include parser-level checks, not logical assertions. If the status field does not match literal `pass` or `fail` values, the LoopAgent may not break the loop as intended, potentially causing the system to reach max iterations. Teams should treat the interactive runtime as a primary method for verifying structured output compliance. Integrating the content_builder becomes prone to cascading format errors without this verification step.

Validating Vertex AI Environment Variables

Missing GOOGLE_CLOUD_PROJECT exports cause frequent deployment errors in agent tool access, often more so than code defects. Engineers must verify that GOOGLE_CLOUD_LOCATION is set to `global` and GOOGLE_GENAI_USE_VERTEXAI equals `true` before invoking the ADK runtime. These variables enable the Agent-to-Agent (A2A) protocol to locate remote services correctly. Without proper configuration, the Judge Agent cannot return the required JudgeFeedback schema, causing orchestration loops to stall indefinitely.

  • Define variables in a `.env` file to persist settings across shell sessions.
  • Execute `source.env` to load credentials into the active environment.
  • Test connectivity using `uv run adk run agents/judge` with simulated input.
  • Monitor logs for authentication timeouts that indicate missing exports.

Shell state reliance introduces friction because environment variables do not persist across new terminal sessions automatically. This constraint forces operators to re-source configuration files constantly, increasing the risk of human error during debugging cycles. Since 78% of coding agent sessions now involve multi-file edits, necessitating coordinated efforts across specialized agents, incorrect environment configuration prevents the system from initializing. The cost of this fragility is measurable in wasted compute cycles when loops fail silently due to authentication timeouts. Automating variable injection in CI/CD pipelines helps mitigate these manual deployment failures.

About

Sofia Berg serves as Research Editor at AI Agents News, where she specializes in translating complex multi-agent research into actionable insights for engineers. Her daily work involves rigorous analysis of agentic frameworks and evaluation benchmarks, making her uniquely qualified to guide readers through building a distributed Course Creation System. Unlike theoretical overviews, this tutorial reflects her deep familiarity with orchestration patterns and tool-use mechanics found in current literature. At AI Agents News, Berg constantly assesses how specialized roles like Researcher and Judge agents interact to solve real-world complexity. This direct experience ensures the guide accurately implements Pydantic for structured output and manages workflow communication without hype. By connecting academic concepts of multi-agent coordination to practical Python implementation, she provides the technical clarity builders need to move beyond simple chatbots toward reliable, autonomous systems.

Conclusion

Scaling multi-agent systems exposes a critical fragility where orchestration collapses not due to logic errors, but because environmental state fails to persist across sessions. As task complexity drives session durations from minutes to over twenty, the operational cost of manual configuration becomes unsustainable. Relying on developers to manually source `.env` files before every debugging cycle creates a bottleneck that stifles the very autonomy these systems promise. The real breaker at scale is this dependence on transient shell state rather than immutable infrastructure definitions.

Organizations must mandate that CI/CD pipelines inject all required variables like `GOOGLE_CLOUD_PROJECT` directly into the runtime environment, eliminating human reliance on local terminal setup. This shift should occur immediately for any production-bound deployment to prevent silent authentication timeouts that waste compute resources. Treat environment validation as a strict gate before any agent invocation begins.

Start this week by auditing your current deployment scripts to ensure they explicitly export `GOOGLE_GENAI_USE_VERTEXAI` and `GOOGLE_CLOUD_LOCATION` before invoking the ADK runtime. Hardcode these checks into your pipeline rather than trusting local shell sessions to remain consistent. This single step secures the foundation required for complex multi-agent coordination to function without constant manual intervention.

Frequently Asked Questions

Currently, 57% of organizations deploy multi-step agent workflows to handle distributed architectures. This majority shift means developers must prioritize strict role specialization over monolithic prompts to manage real-world complexity effectively.

Coding agent sessions have grown from an average duration of 4 minutes to 23 minutes. This increase reflects higher complexity and multi-file engagement, requiring coordinated efforts across specialized agents rather than single-context operations.

Approximately 78% of coding agent sessions now involve multi-file edits. This high rate necessitates coordinated efforts across specialized agents, making single-context operations insufficient for modern software development tasks and debugging cycles.

Approximately a portion of production multi-agent systems utilize the orchestrator-worker topology. This dominant pattern separates execution from management, requiring developers to distinctively design worker agents for tools and orchestrators for delegation logic.

The Judge Agent enforces a rigid Pydantic schema restricting output to pass or fail status values. This constraint prevents conversational filler, ensuring the LoopAgent can programmatically determine whether to retry research or proceed.

References