How to build a simple multi-agentic system using Google’s ADK

Agents are top of mind for enterprises, but often we find customers building one “super” agent – a jack of all trades – instead creating multiple agents that can specialize and work together. Monolithic agents often crumble under their own weight because of instruction overload, inaccurate outputs, and brittle systems that are impossible to scale. 

The good news: A team of specialized AI agents, each an expert in its domain, can deliver higher fidelity, better control, and true scalability.

The challenge: Building robust multi-agent workflows is complex. This is where Google’s Agent Development Kit (ADK) becomes essential. The ADK provides the framework to design, build, and orchestrate these sophisticated agentic systems, leveraging the power of Gemini. In this post, we’ll show you how you can build a multi-agentic workflow using ADK.

aside_block
<ListValue: [StructValue([(‘title’, ‘$300 in free credit to try Google Cloud AI and ML’), (‘body’, <wagtail.rich_text.RichText object at 0x3ec443549820>), (‘btn_text’, ‘Start building for free’), (‘href’, ‘http://console.cloud.google.com/freetrial?redirectPath=/vertex-ai/’), (‘image’, None)])]>

Step 1: Create specialized agents 

Instead of one monolithic agent trying to do everything and getting confused, we’ll break the problem down. We’re building a team of focused specialist agents, each with clear instructions for a single job. In this case, we’ll take a travel example: 

  • FlightAgent: Knows only about flights.

  • HotelAgent: An expert in accommodation.

  • SightseeingAgent: A dedicated tour guide.

code_block
<ListValue: [StructValue([(‘code’, ‘from google.adk.agents import LlmAgentrnrn# Flight Agent: Specializes in flight booking and informationrnflight_agent = LlmAgent(rn model=’gemini-2.0-flash’,rn name=”FlightAgent”,rn description=”Flight booking agent”,rn instruction=f”””You are a flight booking agent… You always return a valid JSON…”””)rnrn# Hotel Agent: Specializes in hotel booking and informationrnhotel_agent = LlmAgent(rn model=’gemini-2.0-flash’,rn name=”HotelAgent”,rn description=”Hotel booking agent”,rn instruction=f”””You are a hotel booking agent… You always return a valid JSON…”””)rnrn# Sightseeing Agent: Specializes in providing sightseeing recommendationsrnsightseeing_agent = LlmAgent(rn model=’gemini-2.0-flash’,rn name=”SightseeingAgent”,rn description=”Sightseeing information agent”,rn instruction=f”””You are a sightseeing information agent… You always return a valid JSON…”””)’), (‘language’, ‘lang-py’), (‘caption’, <wagtail.rich_text.RichText object at 0x3ec443096c70>)])]>

To manage these specialists, build a coordinator workflow. Then, create a TripPlanner root agent whose only job is to understand a user’s request and route it to the correct specialist.

code_block
<ListValue: [StructValue([(‘code’, ‘# Root agent acting as a Trip Planner coordinatorrnroot_agent = LlmAgent(rn model=’gemini-2.0-flash’,rn name=”TripPlanner”,rn instruction=f”””rn Acts as a comprehensive trip planner.rn – Use the FlightAgent to find and book flightsrn – Use the HotelAgent to find and book accommodationrn – Use the SightSeeingAgent to find information on places to visitrn …rn “””,rn sub_agents=[flight_agent, hotel_agent, sightseeing_agent] # The coordinator manages these sub-agentsrn)’), (‘language’, ‘lang-py’), (‘caption’, <wagtail.rich_text.RichText object at 0x3ec443096640>)])]>

1 Fig1

While this works beautifully for simple queries (e.g., “Find me a flight to Paris” is immediately dispatched to the FlightAgent), a new problem quickly becomes apparent. When asked, “Book a flight to Paris and then find a hotel,” the coordinator calls the FlightAgent and stops.  It has done its job of routing the initial request, but it cannot orchestrate a multi-step workflow. The manager is a great receptionist but a poor project manager

This limitation stems from how the system handles sub-agents. When the Root Agent calls the Flight Agent as a sub-agent, the responsibility for answering the user is completely transferred to the Flight Agent. The Root Agent is effectively out of the loop. All subsequent user input will be handled solely by the Flight Agent. This often leads to incomplete or irrelevant answers because the broader context of the initial multi-step request is lost, directly reflecting why the manager struggles as a “project manager” in these scenarios.

Step 2: Give your coordinator tools 

The coordinator needed an upgrade. It shouldn’t just forward a request; it needed the ability to use its specialists to complete a bigger project. This led to the next evolution: the Dispatcher Agent with Agent Tools.

Instead of treating the specialists as destinations, we will treat them as tools in the root agent’s toolbox. The root agent could then reason about a complex query and decide to use multiple tools to get the job done.

Using the ADK, the specialized agents are converted into AgentTools.

code_block
<ListValue: [StructValue([(‘code’, ‘from google.adk.agents import agent_toolrnrn# Convert specialized agents into AgentToolsrnflight_tool = agent_tool.AgentTool(agent=flight_agent)rnhotel_tool = agent_tool.AgentTool(agent=hotel_agent)rnsightseeing_tool = agent_tool.AgentTool(agent=sightseeing_agent)rnrn# Root agent now uses these agents as toolsrnroot_agent = LlmAgent(rn model=’gemini-2.0-flash’,rn name=”TripPlanner”,rn instruction=f”””Acts as a comprehensive trip planner…rn Based on the user request, sequentially invoke the tools to gather all necessary trip details…”””,rn tools=[flight_tool, hotel_tool, sightseeing_tool] # The root agent can use these toolsrn)’), (‘language’, ‘lang-py’), (‘caption’, <wagtail.rich_text.RichText object at 0x3ec4430969d0>)])]>

1 Fig2

This is a game-changer. When the complex query “Book a flight to Paris and then find a hotel” is run, the root agent understands and it intelligently calls the flight_tool, gets the result, and then calls the hotel_tool. It can also suggest two top places to visit using Sightseeing_tool. The to-and-fro communication between the root agent and its specialist tools enabled a true multi-step workflow.

However, as the system worked, an inefficiency became noticeable. It found the flight, then it found the hotel. These two tasks are independent. Why couldn’t they be done at the same time?

Step 3: Implement parallel execution 

The system is smart, but it’s not as fast as it could be. For tasks that don’t depend on each other, they can be run concurrently to save time.

The ADK provides a ParallelAgent  for this. We use this to fetch flight and hotel details simultaneously. Then, a SequentialAgent is used to orchestrate the entire workflow. It first gets the sightseeing info , then “fan-out” to the parallel agent for flights and hotels, and finally, “gather” all the results with a TripSummaryAgent.

code_block
<ListValue: [StructValue([(‘code’, ‘from google.adk.agents import SequentialAgent, ParallelAgentrnrn# 1. Create a parallel agent for concurrent tasksrnplan_parallel = ParallelAgent(rn name=”ParallelTripPlanner”,rn sub_agents=[flight_agent, hotel_agent], # These run in parallelrn)rnrn# 2. Create a summary agent to gather resultsrntrip_summary = LlmAgent(rn name=”TripSummaryAgent”,rn instruction=”Summarize the trip details from the flight, hotel, and sightseeing agents…”,rn output_key=”trip_summary”)rnrn# 3. Create a sequential agent to orchestrate the full workflowrnroot_agent = SequentialAgent(rn name=”PlanTripWorkflow”,rn # Run tasks in a specific order, including the parallel steprn sub_agents=[sightseeing_agent, plan_parallel, trip_summary])’), (‘language’, ‘lang-py’), (‘caption’, <wagtail.rich_text.RichText object at 0x3ec4430961c0>)])]>

We now have an optimized workflow. The system is now not only handling complex queries, but it is doing so efficiently. It is close to the finish line, but one final doubt remains. Is the final summary good? Does it always meet the strict quality guidelines?

Step 4: Create feedback loops 

A feedback loop is needed for the system to review its own work.

The idea is to add two more agents to the sequence:

  1. TripSummaryReviewer: An agent whose only job is to evaluate the summary generated by the TripSummaryAgent. It checks for completeness and structure, outputting a simple “pass” or “fail.”

  2. ValidateTripSummaryAgent: A custom agent that checks the reviewer’s status and provides the final, validated output or an error message.

This pattern works by having agents communicate through a shared state. The TripSummaryAgent writes its output to the trip_summary key, and the TripSummaryReviewer reads from that same key to perform its critique.

code_block
<ListValue: [StructValue([(‘code’, ‘# Agent to check if the trip summary meets quality standardsrntrip_summary_reviewer = LlmAgent(rn name=”TripSummaryReviewer”,rn instruction=f”””Review the trip summary in {{trip_summary}}.rn If the summary meets quality standards, output ‘pass’. If not, output ‘fail'”””,rn output_key=”review_status”, # Writes its verdict to a new keyrn)rnrn# Custom agent to check the status and provide feedbackrnrnclass ValidateTripSummary(BaseAgent):rn async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]:rn status = ctx.session.state.get(“review_status”, “fail”)rn review = ctx.session.state.get(“trip_summary”, None)rn if status == “pass”:rn yield Event(author=self.name, content=Content(parts=[Part(text=f”Trip summary review passed: {review}”)]))rn else:rn yield Event(rn content=Content(parts=[Part(author=self.name,rn text=”Trip summary review failed. Please provide a valid requirements”)]))rnValidateTripSummaryAgent = ValidateTripSummary(rn name=”ValidateTripSummary”,rn description=”Validates the trip summary review status and provides feedback based on the review outcome.”,)rnrn# The final, self-regulating workflowrnroot_agent = SequentialAgent(rn name=”PlanTripWorkflow”,rn sub_agents=[rn sightseeing_agent,rn plan_parallel,rn trip_summary,rn trip_summary_reviewer,rn ValidateTripSummaryAgent() # The final validation step rn])’), (‘language’, ”), (‘caption’, <wagtail.rich_text.RichText object at 0x3ec443096df0>)])]>

3 Fig3

With this final piece in place,, our AI system is no longer a single, confused genius but a highly efficient, self-regulating team of specialists. It can handle complex, multi-step queries with parallel execution for speed and a final review process for quality assurance.

Get started 

Ready to build your own multi-agent workflows? Here’s how to get started: