,

Multi-Agent Systems on Vertex AI with ADK and Agent Engine (Google Cloud Gen AI Series, Part 21)

One big agent with twenty tools rots fast. Here is how to split it into a coordinator and typed sub-agents with the ADK, choose deterministic versus LLM-driven flows, connect across boundaries with A2A, and deploy to Vertex AI Agent Engine.

Google Cloud Gen AI Series · Part 21 of 30

One agent, one system prompt, nineteen tools bolted on. It demos beautifully. Then a customer asks for a refund and a delivery date in the same message, the model reaches for the wrong tool, and you spend the afternoon adding another paragraph to a prompt that is already too long for the model to hold in its head. Every fix makes the next fix harder. The prompt is not the problem. The shape is.

Who this is for: you have built a single agent that calls tools and does function calling from Part 11, and you met the Agent Development Kit and Agent Builder at a high level in Part 13. Now one agent is doing too much. No prior multi-agent experience is assumed; every term is defined on first use. If you have never written an agent that calls a tool, do Part 11 first, then come back.
Key idea: A multi-agent system splits one overloaded agent into a coordinator and a handful of narrow specialists. The Agent Development Kit, ADK, gives you two ways to wire them: an LlmAgent that reads the request and delegates to a sub-agent, or a workflow agent that runs sub-agents in a fixed order you control. Use the LLM to decide when the path is open-ended, use SequentialAgent, ParallelAgent, or LoopAgent when the path is known. Ship the whole graph to Vertex AI Agent Engine, the managed runtime, and pay by the vCPU-hour. The A2A protocol handles the case where a specialist lives in another team or another cloud.

What splitting the agent actually buys you

A single agent degrades in a predictable way as you add tools. The instruction grows to cover every branch, the model has more chances to pick the wrong tool on any given turn, and you cannot test one capability without dragging the rest along. Splitting the work fixes all three. Each specialist agent carries a short instruction and three or four tools instead of twenty, so it stays reliable. A coordinator sits on top and decides who handles what. You test the returns agent on returns questions and leave the billing agent out of it entirely.

The cost is real and worth naming up front. More agents means more model calls per request, more latency, and more moving parts to trace when something goes wrong. A two-agent hop can double your token spend on a turn that a single agent would have answered directly. So the split is not free, and it is not always right. It earns its keep when one agent has grown past roughly half a dozen tools or when distinct capabilities need different models, different owners, or different guardrails. Below that line, keep one agent and save yourself the orchestration.

ADK in one paragraph

The Agent Development Kit is Google’s open-source framework for building agents in code. It reached 1.0, its first stable release, and now ships in Python, Java, Go, and TypeScript with the same core concepts across all four, so an agent prototyped in Python ports to a Java backend without rewriting the logic. ADK runs on your laptop for development and deploys to any container or to Vertex AI Agent Engine for production. The building block is an agent object: you give it a name, a model, an instruction, and a list of tools or sub-agents. Everything in this part is a way of composing those objects. It is not tied to Gemini alone, but on Google Cloud the default pairing is ADK plus Gemini plus Agent Engine, and that is the path I will follow.

Coordinator and sub-agents, the LLM-driven split

The most common pattern is a coordinator: a central LlmAgent, an agent whose control flow is driven by a language model, that reads each request and routes it to the right specialist. In ADK you build it by assigning specialists to the coordinator’s sub_agents list. The framework wires the parent-child relationship and exposes a transfer_to_agent action the model can call to hand control to a named sub-agent. The coordinator does not answer the question itself. Its whole job is to pick the specialist and step aside.

This is LLM-driven delegation, and it shines when you cannot predict the path. A support request might be about billing, orders, or returns, and the coordinator reads the language to decide. The trade is that the model makes the routing call, so routing is only as good as the instruction and the sub-agent descriptions. Give each specialist a one-line description of what it handles, because the coordinator reads those descriptions to choose. Vague descriptions produce vague routing. This is the same delegation idea as Amazon Bedrock’s supervisor agents on the AWS side, covered in the AWS multi-agent part, if you run both clouds.

flowchart TD
  U[User request] --> C[Coordinator LlmAgent]
  C --> B[Billing sub-agent]
  C --> O[Orders sub-agent]
  C --> R[Returns sub-agent]
  R -->|A2A call| W[Partner warehouse agent]
  B --> C
  O --> C
Figure 1. The coordinator routes each request to one specialist and control returns to it when the specialist finishes. Billing and orders are local sub-agents; the returns agent reaches an external partner agent over A2A, the cross-framework protocol described later. Short instructions on each node, not one giant prompt.

In practice

Give the coordinator a small, cheap-to-run model and the specialists whatever they need. Routing is a short classification task, so a fast model handles it well, and you do not want to pay reasoning-model prices just to pick a branch. Put the expensive model behind the one specialist that actually does hard reasoning. I have watched teams run their most costly model as the router and wonder where the bill went.

When should the flow be deterministic?

Letting the model route is the right call when the path is genuinely open. When you already know the order of steps, do not make the model rediscover it every turn. ADK gives you three workflow agents that run sub-agents on a fixed structure, no model reasoning involved in the control flow. SequentialAgent runs its sub-agents one after another, passing state down the chain, which suits a pipeline like extract, then summarize, then translate. ParallelAgent runs its sub-agents at the same time and gathers the results, which suits independent lookups that do not depend on each other. LoopAgent repeats a sub-agent until a condition holds, which suits a draft-and-critique cycle that runs until a reviewer approves.

The difference matters for cost, latency, and testability. A deterministic workflow is cheaper because the structure is free; no model call decides what runs next. It is faster because you skip the routing hop. And it is far easier to test, because the same input always takes the same path. My rule is simple: if you can draw the flow as a fixed diagram, use a workflow agent. If the next step depends on reading the user’s intent, use an LlmAgent to decide. Most real systems mix both, an LLM coordinator on top with deterministic pipelines underneath.

Agent typeControl flowReach for it when
LlmAgentModel decides the next stepPath depends on reading intent
SequentialAgentFixed order, one after anotherA known pipeline of steps
ParallelAgentConcurrent, results mergedIndependent lookups at once
LoopAgentRepeat until a condition holdsDraft, critique, revise cycles
AgentToolParent calls agent as a functionReusable skill, keep control
Table 1. The ADK composition primitives. The first is LLM-driven; the middle three are deterministic; the last turns an agent into a callable tool. Names verified against the ADK multi-agent documentation.

The latency gap between sequential and parallel is not academic. Take three specialist lookups that do not depend on each other, each averaging about 4.2 seconds. Run them in a SequentialAgent and the wall-clock time is their sum. Run them in a ParallelAgent and it is roughly the slowest one plus a small merge. Same work, very different wait.

Sequential vs parallel wall-clock timeThree independent sub-agents, each about 4.2 seconds0s5s10s14s12.6sSequentialAgent4.8sParallelAgent
Figure 2. Three independent 4.2 second lookups: sequential adds up to 12.6 seconds, parallel finishes in about 4.8. Only use ParallelAgent when the sub-tasks truly do not depend on each other, or you race a step against missing input.

Agent as a tool

There is a third way to compose agents that sits between delegation and workflow. AgentTool wraps an agent so its parent can call it like a function, with structured input and output, and keep control instead of handing it off. The difference from a sub-agent is who holds the conversation. With transfer_to_agent, the coordinator hands the user to the specialist and steps back. With AgentTool, the parent stays in charge, calls the wrapped agent to get one specific result, and carries on. Reach for AgentTool when a capability is a reusable skill, say a currency converter agent or a summarizer, that the parent needs mid-thought rather than a destination to route the whole request to.

The practical tell is whether control should come back. If the specialist owns the rest of the exchange, make it a sub-agent. If the parent needs an answer and then continues its own reasoning, wrap it as a tool. Getting this backwards is a common early mistake: teams route to a sub-agent for what should have been a quick tool call, then wonder why the coordinator lost the thread.

Here is the coordinator pattern in ADK Python. Two specialists, one router, wired through sub_agents.

from google.adk.agents import LlmAgent

billing = LlmAgent(
    name='billing',
    model='gemini-2.5-flash',
    description='Handles invoices, charges, and refunds.',
    instruction='Answer billing questions only. Never guess an order status.',
)

orders = LlmAgent(
    name='orders',
    model='gemini-2.5-flash',
    description='Tracks and updates order and delivery status.',
    instruction='Answer order and delivery questions only.',
)

coordinator = LlmAgent(
    name='coordinator',
    model='gemini-2.5-flash',
    instruction='Route each request to the right specialist. Do not answer yourself.',
    sub_agents=[billing, orders],
)

Expected behaviour: a billing question makes the coordinator emit a transfer_to_agent call to billing, which answers; an order question routes to orders. Failure mode: drop the "Do not answer yourself" line and the coordinator often answers directly from its own knowledge, the sub-agents never fire, and routing silently disappears. Model IDs shown are current 2.5-series strings.

Gotcha: a coordinator with no clear boundary between specialists will ping-pong. If billing and orders both look plausible for "where is my refund for the cancelled order", the model can transfer back and forth. Write disjoint descriptions, and add a short rule to the coordinator instruction for the overlap cases. Cap transfers if your framework version allows it, so a routing loop cannot run up a bill unwatched.

How A2A connects agents across frameworks

Everything so far assumes the agents live in one codebase. Real organizations do not work that way. The returns team might run their agent on a different stack, or a supplier might expose an agent you do not control. Agent2Agent, or A2A, is the open protocol for that case: a thin, framework-agnostic way for one agent to delegate to another over the network, regardless of what each is built on. It is to agents roughly what HTTP is to web servers. Google contributed it, and it is now an open project under the Linux Foundation, stable at v1.0 with more than 150 organizations running it in production.

The mechanics are worth knowing at a glance. Each A2A agent publishes an Agent Card, a small signed document that says what it does, where it lives, and how to authenticate. A client agent reads the card, then sends tasks and receives results or streamed updates. Inside your own ADK app you use sub_agents and AgentTool; across an organizational or cloud boundary you use A2A. The line between them is ownership. If you own the code and it deploys together, keep it in-process. If it belongs to another team or vendor, reach it over A2A rather than trying to import it.

Where the agents run in production

ADK runs your agent locally with a dev server and a web UI, which is enough to build and debug. Production is a different problem: you need a runtime that scales, holds conversation state, and does not fall over when traffic arrives. Vertex AI Agent Engine is Google’s managed answer, a runtime that hosts an ADK agent behind a managed endpoint so you do not run the servers yourself. You hand it the agent object and a requirements list, it packages and deploys, and you get a scalable endpoint with tracing built in.

Two managed pieces make it usable for real conversations. Sessions store the back-and-forth of a single conversation so the agent remembers what was said a moment ago. Memory Bank is a managed store for longer-term memory across sessions, generating and retrieving what matters about a user over time. Both went generally available this year alongside a regional expansion, so you can keep data in an EU, Asia, or Americas region as needed. Build on your laptop, deploy to Agent Engine, and let Sessions and Memory Bank carry the state you would otherwise wire up by hand.

Disclaimer: the call below creates a billable Agent Engine deployment and provisions cloud resources. Run it in a non-production project first, confirm the region and the requirements extras, and delete the deployment when you finish testing. Package extras change between releases; check the current install string before you run it.
from vertexai import agent_engines

# coordinator is the LlmAgent defined earlier
remote_app = agent_engines.create(
    agent_engine=coordinator,
    requirements=['google-cloud-aiplatform[adk,agent_engines]'],
)
print(remote_app.resource_name)

Expected: the call returns after a few minutes with a resource name you query for the deployed endpoint. Common failure: a dependency your agent imports is missing from the requirements list, so the build succeeds but the remote agent errors on first call. Pin every import your agent needs in requirements, not just the ADK extras.

What Agent Engine costs

Agent Engine bills the runtime by the resources it holds, not per request. The runtime is roughly $0.0864 per vCPU-hour and $0.0090 per GB-hour of memory, with a monthly free tier of about 50 vCPU-hours and 100 GB-hours. Model tokens are billed separately under the Gemini SKUs, and if you use the managed state, Sessions and Memory Bank add storage and read charges on top. The lever you control is how much compute you provision and whether the service stays always-on or scales down when idle.

Worked example

Run the coordinator plus two specialists as one always-on Agent Engine deployment sized at 2 vCPU and 4 GB, over a 730-hour month. Compute is 2 times 730 times $0.0864, about $126 for vCPU, plus 4 times 730 times $0.0090, about $26 for memory. That is roughly $152 a month gross; the free tier trims about $5, leaving near $147 for the runtime before any model tokens.

Double the size to 4 vCPU and 8 GB and the runtime roughly doubles to about $305. So the design call is concrete: right-size the container to real concurrency rather than provisioning headroom you never use, and let idle traffic scale down where the workload allows. The token bill, not the runtime, is usually the bigger number, which is exactly why a cheap router model matters.

Figures are stated assumptions for illustration, not a quoted price sheet. Check current Agent Engine rates for your region before you budget.
Runtime cost scales with the size you provisionAlways-on, 730-hour month, gross of the free tier$0100200320$761 vCPU / 2 GB$1522 vCPU / 4 GB$3054 vCPU / 8 GB
Figure 3. Runtime cost is close to linear in provisioned size, so the container you pick is a deliberate concurrency decision. The $152 middle bar is the worked-example configuration. Model tokens are billed on top and often exceed this.
ComponentRough rateBilled on
Runtime vCPU~$0.0864 per vCPU-hourProvisioned compute time
Runtime memory~$0.0090 per GB-hourProvisioned memory time
Sessions and memories~$0.25 per 1,000 storedStored events and memories
Memory Bank storage~$0.30 per GiB-monthRetained memory data
Model tokensPer the Gemini SKUEvery model call, separately
Table 2. Agent Engine cost components in 2026. Rates are approximate and region-dependent; treat them as a shape, not a quote, and verify against the current pricing page.

Failure modes I keep hitting

The first is over-splitting. Not every capability deserves its own agent. If two specialists always run together and share the same tools, they are one agent wearing two hats, and merging them cuts a model hop and a class of routing bugs. Start with the fewest agents that keep each instruction short, and split further only when a single agent gets unreliable. More agents is not more capable; it is more surface to break.

The second is routing you never measured. LLM-driven delegation is a classification problem, and classifiers are wrong sometimes. Build a small set of real requests with the specialist each should reach, and assert the coordinator routes them correctly. Run it in your pipeline so a prompt tweak that quietly breaks routing gets caught before users do. Grading the full answer, not just the routing, is the job of the evaluation service, which the next part covers.

The third is losing the thread across a transfer. When a coordinator hands off with transfer_to_agent, the specialist needs enough context to act, and state that lived only in the coordinator’s head does not travel by magic. Use Sessions to carry the conversation and be explicit about what each agent reads. The fourth is unbounded loops: a LoopAgent with a condition that never trips, or two agents that transfer back and forth, both burn tokens with nothing to show. Put a hard cap on iterations and transfers, and alert on it.

My take: most teams reach for multi-agent too early. A single well-scoped agent with good tools beats a tangle of four agents that spend half their tokens talking to each other. I split only when one agent’s instruction stops fitting in my head, and even then I start with two, not five. The framework makes adding agents cheap, which is exactly the trap.

One coordinator, typed sub-agents, Agent Engine for the runtime

If you take one shape away, take this one. Put a cheap LlmAgent coordinator on top with disjoint, well-described sub-agents beneath it, and drop to SequentialAgent, ParallelAgent, or LoopAgent wherever the path is fixed rather than making the model rediscover it. Wrap reusable skills as AgentTool so the parent keeps control. Reach across a team or cloud boundary with A2A, never by importing code you do not own. Then deploy the whole graph to Agent Engine, size the container to real concurrency, and let Sessions carry the state.

The single most valuable thing you can add before this reaches users is a routing test: a dozen real requests, the specialist each should hit, and an assertion in your pipeline. It runs in seconds and catches the failure that costs you a support queue. Next in the series we grade the whole system with the Gen AI evaluation service, which measures whether the answer, not just the routing, holds up. Write the routing test today, before you add a fourth agent.

Google Cloud Gen AI Series · Part 21 of 30
« Previous: Part 20  |  Guide  |  Next: Part 22 »

References

About The Author


Discover more from Journal of Intelligent Infrastructure – By Dr Pranay Jha

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure - By Dr Pranay Jha

Subscribe now to keep reading and get access to the full archive.

Continue reading