Skip to content
abhinavbhatia
Sheet 02 / 05 — Blogs← Archive

Production-ready agents, Part 6.5: Multi-agent topologies beyond a router

A router is only one way to connect agents. This part maps eight multi-agent topologies on two simple axes, shows each one on the Acme system with its ADK building block and its main risk, and explains how to combine them without losing control of cost, loops and ownership.

In Part 5 we built the router for Acme Consumer Brands, and in Part 6 we connected it to the marketing and supply specialists over A2A. That gives us one working shape: code routes the question, remote specialists answer, and a writer model turns their findings into one checked answer.

Once a system like this is running, the requests start. The pricing team wants its own agent. Someone asks whether a second model could review the writer’s draft. A partner asks whether their agent could talk to ours. Each request is really a question about topology, the shape of how agents are connected, and each shape has a different cost, a different failure mode and a different owner.

This in-between part steps back from the build. I map the common topologies on two axes, explain eight of them on the Acme example, show how they combine, and finish with a decision guide.

If you prefer to watch first, here is the heart of this part as a short animated explainer.

Video: eight ways to connect agents, and how to combine them without losing control, in three minutes. The narration is a synthetic voice; the script and animation are mine.

Where are we in the architecture?

This part steps back and compares multi-agent topologies, using the router and its two specialists as the example. Part 1.25 walks through the whole picture, layer by layer.

ACME ARCHITECTURE · PART 6.5 LOCAL FIRST: ADK WEB · AGENT RUNTIME IN PART 11 each agent is its own engine with its own identity A2A A2A Shopper Acme's customers P11.75 GE for CX customer chat, voice, reps P11.75 Legal edition industry skills, connectors P11.75 User Maria in marketing P1 Gemini Enterprise where people use agents P12 Other front doors own app (AG-UI), Chat, Slack P12.5 Plugins + Model Armor · checks around every step P9 Router agent Workflow: extract, route, write P1 Supply agent Databricks specialist P3 Marketing agent BigQuery specialist P2 Code sandbox runs generated code P3.5 Skills versioned in Agent Registry P4 Sessions + Memory Bank what it remembers about each user P8 AGENT GATEWAY · EGRESS (FORWARD PROXY) · P12 Gemini models the reasoning engine P1 Other models Model Garden P13 Knowledge RAG and search P14 BigQuery marketing data P2 Databricks supply data P3 Market watch managed agent, public web P1.75 Agent Identity one per agent P7 Auth manager acts for the user P7 Secret Manager no secrets in code P11 Human approval for the one write P9 Cloud Build CI on every change P11 Eval gate blocks a worse agent P10 Cloud Trace one per question P11 Cloud Logging redacted events P11 Agent Analytics BigQuery plugin P9 IDENTITY AND SAFETY SHIPPING MODELS, KNOWLEDGE, DATA OBSERVABILITY deploy this part earlier parts later parts Pn = the part that adds it

where Part 6.5 sits in the Acme architecture: highlighted boxes are the subject of this part, solid boxes come from earlier parts, faded boxes arrive later

What do I need to know first?

These are the terms the rest of the post uses.

  • Multi-agent system. An application in which more than one agent (a model with instructions, tools and a loop, as defined in Part 1) works on the same request.
  • Orchestration. Deciding which agent or step runs next and passing work between them. The part of the system that does this is the orchestrator.
  • Delegation. One agent handing part of a task to another agent. Transfer (also called a handoff) is delegation where control moves to the other agent, so it answers instead of the caller.
  • Control. Which agent currently holds the turn, and so whose reply the user will see.
  • State. The data a request carries between steps. In ADK this is session state, a dictionary of named keys stored with the conversation.
  • In-process or remote. In-process agents run inside one application and call each other in memory. Remote agents run as separate services and are called over the network, in this series with A2A (the Agent2Agent protocol from Part 6).

As a reminder, this is the router we built, in five lines:

question
  -> extract       (model, Flash)  rewrite the question as one self-contained line
  -> route         (code)          marketing | supply | both | decline
  -> specialist(s) (remote, A2A)   both = run in parallel, then a JoinNode
  -> writer        (model, Pro)    one answer from the findings
  -> number check  (code)          after-model callback; replace the answer if a figure is unmatched

Part 5 explains every node and Part 6 every A2A call.

What is a multi-agent topology?

The short answer: a topology is who calls whom, who holds the data, and who decides the next step. The third of those, code or a model, matters far more than how many agents there are.

In simple words, a topology is the shape of a multi-agent system: who calls whom. The word is used the same way in networking, where a star or a mesh topology describes how machines are wired together.

A more precise definition, and the one I use, is that a multi-agent topology is the arrangement of agents together with three rules:

  1. Who calls whom. This is the control flow: which agent starts, which agents it can reach, and whether control comes back afterwards.
  2. Who holds the state. This is the data flow: whether agents share one session state, pass messages, or keep their own private state.
  3. Who decides the next step. This is the decision: a piece of code, or a model reading the conversation.

The third rule matters most, because it decides how predictable the system is. Anthropic’s widely read guide Building effective agents draws the same line. It calls systems where “LLMs and tools are orchestrated through predefined code paths” workflows, and systems where models direct their own process agents. It also recommends “finding the simplest solution possible, and only increasing complexity when needed”.

That gives two axes that place almost every topology:

  • Vertical: who decides the next step? Code (a fixed order, a route function, a loop counter) or a model (a coordinator reading the request and choosing).
  • Horizontal: where do the agents run? In-process, sharing memory and session state, or remote, as separate services over A2A.
EIGHT TOPOLOGIES ON TWO AXES A MODEL decides the next step CODE decides the next step IN-PROCESS one app, calls in memory REMOTE OVER A2A separate services, calls over the network hierarchical supervisor a model picks a sub-agent agent as a tool caller keeps control handoff / swarm control moves on sequential fixed order parallel fan-out, gather loop and critic code repeats; a check may stop it mesh over A2A peers in other teams or companies Acme router (Parts 5 and 6) code routes; specialists are remote shared state / blackboard how in-process agents pass data A model choosing remote peers is the least predictable corner. Acme keeps every decision in code and only the work is distributed.

eight multi-agent topologies on two axes: whether code or a model decides the next step, and whether the agents run in one process or as remote services over A2A; the Acme router sits where code decides and the specialists are remote

The Acme router sits in the bottom right: every decision is code, and only the work is distributed. That is a deliberate choice from Part 5, where the series rule was set: Models read and write language. Code makes the decisions.

Before Acme, here are the same three rules on something everyone has seen: a table ordering a starter and a main in a small restaurant.

Restaurant A (a fixed rule decides)
  1. Who calls whom   the waiter passes the ticket to the grill and the pastry station; dishes come
                      back to the pass, and the waiter carries them out
  2. Who holds state  the order ticket on the rail; each station keeps its own prep notes
  3. Who decides      the till: anything marked "grill" prints at the grill, every time

Restaurant B (a person decides)
  1. Who calls whom   the head chef reads each ticket and calls out who cooks what
  2. Who holds state  one shared whiteboard the whole kitchen writes on
  3. Who decides      the head chef, judging each ticket, so a busy night can go differently

The stations, the waiter and the ticket are the agents and the state. The till printing by rule is code deciding; the head chef judging is a model deciding. Same kitchen, same cooks, a very different amount of predictability.

How does this apply to Acme? Here are the three rules applied to one question, Maria’s “Compare the Acme Pure Refill Launch spend with what the stockouts cost us”:

Acme router (code decides, remote specialists)
  1. Who calls whom   the router calls marketing and supply over A2A; control comes back to the router
  2. Who holds state  the router: marketing_findings and supply_findings in its session state;
                      each specialist keeps its own private session
  3. Who decides      route(), a Python function, returns "both"

The same question under a coordinator (model decides, in-process)
  1. Who calls whom   the coordinator model calls transfer_to_agent(agent_name="supply"), and maybe
                      marketing too, if it thinks of it
  2. Who holds state  one shared session state for all of them
  3. Who decides      the coordinator model, reading the question and the agents' descriptions

Only rule 3 moves the system on the map: the same agents with a model deciding sit in the top half instead of the bottom. That is the move Part 5 decided against. The common misconception is that adding agents is what makes a system “multi-agent” and harder to run; in practice, handing the decision to a model is what changes how you test it, as Restaurant B shows.

Different frameworks use different names for the same shapes. ADK’s workflows overview lists four ways to build them: graph-based workflows and dynamic workflows (new in ADK 2.0), collaborative workflows (a coordinator agent with sub-agents) and template workflows (SequentialAgent, ParallelAgent and LoopAgent). LangChain’s multi-agent guide uses subagents, handoffs, skills, router and custom workflow. I use the common industry names below, with the ADK building block for each. One honest note: as of October 2026, ADK’s documentation says that for Python and Go the template workflow agents “have been superseded” by graph-based and dynamic workflows. They still work and explain each pattern clearly, so I name both.

Where do subagents, graphs, hierarchies and crews fit?

Those four words come up in every framework comparison, and they are not four rival architectures. They are framework vocabulary, not new shapes, in the way “lift” and “elevator” name the same machine. Each is a framework’s name for one or more of the eight topologies below. Here is the map, with an Acme example of each:

Term you hear        What it is                                 Topology below      Acme example
-------------------  -----------------------------------------  ------------------  ------------------------------
Subagent             an agent another agent hands work to;      3 (hierarchical)    a pricing desk agent that hands
                     either it takes over the conversation      or 4 (agent as      "margin" questions to a margin
                     (ADK sub_agents, transfer_to_agent) or     a tool)             agent; or the supply agent
                     it is called like a tool and returns                           calling contract_reader as a
                     (ADK AgentTool)                                                tool
Graph                steps and arrows that code defines;        1, 2 and 5,         the router: front -> extract
                     ADK Workflow, LangGraph StateGraph         joined by code      -> route -> specialists ->
                                                                                    writer (Part 5)
Hierarchy            a supervisor model that picks which        3 (hierarchical)    rejected for the main path:
                     agent below it acts, and agents below      with 6 (handoff)    a model would choose between
                     can have their own agents                  when control        marketing and supply
                                                                moves sideways
Crew                 CrewAI's team of agents with roles and     1 (sequential       "analyst writes, reviewer
                     tasks, run by a Process: sequentially,     process) or 3       checks" as a sequential crew;
                     or hierarchically with a manager           (hierarchical       a manager agent delegating
                     model or agent that delegates              process)            tasks is the hierarchical one

So when someone asks “should we use a crew or a graph?”, the real question underneath is rule 3 from above: who decides what runs next, code or a model? A sequential crew and a graph both let code decide the order. A hierarchical crew, a supervisor and an ADK coordinator with sub_agents all let a model decide. Acme’s router is a graph because the decision about which team answers is a business rule (Part 5); models work only inside the steps. Part 1.75 shows the same step written in ADK, LangGraph, CrewAI and three other frameworks.

What are the eight topologies?

Each topology below is explained the same way: what it is, a simple everyday example, how it applies to Acme, when to choose it, the ADK building block, and its main risk. The diagrams use the same colours throughout: blue boxes call a model, white boxes are code, and dashed boxes are remote agents reached over A2A.

1. Sequential pipeline

What it is. A sequential pipeline runs steps in a fixed order, and each step’s output becomes the next step’s input. Anthropic calls the model-only version prompt chaining.

A simple example. A car wash: soap, then rinse, then dry. Rinsing before the soap is pointless and drying before the rinse is worse, so each stage waits for the one before and takes the car exactly as that stage left it. The stages are the steps, the car is the data passed along, and the fixed order is written into the building, not chosen per car.

How does this apply to Acme? The router’s main line is a sequential pipeline: extract, then route, then a specialist, then the writer. No step can start before the one before it has finished, because each needs the previous result.

1 · SEQUENTIAL PIPELINE: EACH STEP FEEDS THE NEXT model code remote (A2A) extract rewrite question route pick the team supply agent find the figures writer draft the answer output of one step becomes the input of the next (node_input, or a state key) ADK SequentialAgent(sub_agents=[...]), or a graph Workflow with edges ("START", a, b, c) RISK one slow or failing step holds up every step after it

a sequential pipeline: extract, then route, then the supply agent, then the writer, each step passing its output to the next

When to choose it. When the steps are known in advance and each depends on the one before. It is the easiest shape to test, because every run takes the same path.

The ADK building block. The template version is SequentialAgent, which runs its sub_agents in order. In ADK 2.0 the same thing is a graph workflow whose edge lists the nodes in order, such as ("START", extract, route). Steps pass data either directly (each node receives the previous output as node_input) or through session state.

The main risk. Waiting time adds up, and one slow or failing step holds up everything after it.

2. Parallel fan-out and gather

What it is. A fan-out sends independent work to several agents at the same time; a gather (or fan-in) waits for all of them and combines the results. Anthropic calls this parallelization and describes two kinds: sectioning (different subtasks in parallel) and voting (the same task several times, to compare answers).

A simple example. Cooking rice (20 minutes) and a curry (15 minutes) for dinner. On two hobs at once, dinner is ready at 20 minutes, the slower dish, not 35. Plating waits until both pans are done. If both cooks scribble their notes on the same line of one recipe card, the second note rubs out the first. The hobs are the branches, plating is the gather, and the recipe card is session state.

How does this apply to Acme? The router’s both branch already does this. When Maria asks “Compare the Acme Pure Refill Launch spend with what the stockouts cost us”, marketing reads BigQuery and supply reads Databricks at the same time, and a JoinNode waits for both.

2 · PARALLEL FAN-OUT AND GATHER: INDEPENDENT WORK AT THE SAME TIME model code remote (A2A) route value: "both" marketing agent BigQuery spend supply agent Databricks penalties JoinNode waits for both writer one answer supply takes longer, so the join waits ADK ParallelAgent(sub_agents=[...]), or a tuple of nodes in a Workflow edge plus a JoinNode RISK branches share nothing while running; two writing the same state key overwrite each other

parallel fan-out and gather: the route sends one question to the marketing and supply agents at the same time, a JoinNode waits for both, and the writer combines them

When to choose it. When the subtasks are independent and the user is waiting: they wait for the slowest branch instead of the sum of all of them.

The ADK building block. ParallelAgent runs its sub-agents concurrently, usually followed by an agent that combines their outputs. In a graph workflow, a tuple of nodes in an edge fans out and a JoinNode fans in.

The main risk. ADK’s documentation is explicit that each branch runs independently, with “no automatic sharing of conversation history or state between these branches during execution”. So two branches must write to distinct state keys (marketing_findings and supply_findings), or one result silently overwrites the other.

Here is that failure on Maria’s compare question (toy figures, the same ones as the Try it below):

Both branches write "findings"
  marketing   state["findings"] = "Acme Pure spend: EUR 283,590"
  supply      state["findings"] = "Service penalties: EUR 35,600"   (finishes last, wins)
  writer      sees only the penalties; the spend is gone, and no error is raised

Each branch writes its own key
  marketing   state["marketing_findings"] = "Acme Pure spend: EUR 283,590"
  supply      state["supply_findings"]    = "Service penalties: EUR 35,600"
  writer      sees both and can compare them

Which branch “wins” depends on which finishes last, so the bug can come and go between runs. That is why the key names are fixed in code, not left to each agent.

3. Hierarchical supervisor

What it is. A supervisor (ADK calls it a coordinator) is an LLM agent that reads each request and delegates it to one of its sub-agents. Sub-agents can have their own sub-agents, which makes a tree, so the pattern is also called hierarchical task decomposition. Anthropic’s name for the flexible version is orchestrator-workers: the orchestrator decides the subtasks at run time instead of following a fixed list.

A simple example. A family doctor (a GP) hears your symptoms and decides, there and then, which specialist you need: a cardiologist one day, a dermatologist the next. Either the specialist takes over your care and you deal with them from now on, or they send a report back and you return to the GP. The GP is the supervisor, the specialists are sub-agents, and “takes over” versus “reports back” is exactly the "chat" versus "task" mode below. The GP’s choice is a judgement, so two GPs may refer the same patient differently.

How does this apply to Acme? At Acme, this is how it would work with a pricing desk agent. Pricing questions vary a lot: one needs promotion history, another competitor prices, another the margin model. A supervisor model reads the question and passes it to the right sub-agent.

3 · HIERARCHICAL SUPERVISOR: A MODEL DELEGATES TO SUB-AGENTS model code remote (A2A) pricing desk LLM agent with sub_agents promo history competitor prices margin model transfer_to_agent back to the parent ADK Agent(sub_agents=[...]); the model delegates by name; mode="task" or "single_turn" returns RISK every request pays for the supervisor's call, and it may pick a different sub-agent next time

a hierarchical supervisor: the pricing desk agent reads the request, transfers it to one of its sub-agents, and control comes back to the parent when the sub-agent finishes

When to choose it. When you cannot list the subtasks in advance and the choice depends on understanding the request. If a rule can make the choice, a route function is cheaper and more predictable, which is the argument Part 5 made for the top of the router. The usual mistake is to reach for a supervisor simply because there are several agents; a hospital receptionist sending every “broken arm” to fracture clinic needs a rule, not a GP.

The ADK building block. An Agent with sub_agents=[...]. The model delegates by calling transfer_to_agent(agent_name=...), and ADK switches control to that agent, as the multi-agent patterns guide shows. ADK 2.0 collaboration modes add a mode setting on each sub-agent: "chat" (the default) keeps control until it transfers back, "task" returns automatically when the task is finished, and "single_turn" returns at once with its result and can run in parallel. One honest gap: as of October 2026 the documentation says task mode is disabled inside graph-based workflows in ADK Python 2.0.0, and is expected to come back in a later release.

Where does mode go: on the child or the parent? On the child. ADK has no separate sub-agent class and no is_sub_agent=True flag; every agent is an ordinary Agent(...). An agent only becomes a sub-agent when a parent lists it in sub_agents=[...]. Settings about how the child behaves once it has control, mode, disallow_transfer_to_parent and disallow_transfer_to_peers, are part of the child’s definition. The parent owns only the list:

from google.adk.agents import Agent

# Step 1: an ordinary agent. Mode and transfer limits live HERE, on the child.
margin_model = Agent(
    name="margin_model",
    model="gemini-2.5-flash",
    description="Computes promotion margin for one brand and month.",
    instruction="Compute the margin, then finish the task.",
    mode="task",                       # hand control back to the parent when done
    disallow_transfer_to_peers=True,   # never hop sideways to promo_history
)
promo_history = Agent(
    name="promo_history",
    model="gemini-2.5-flash",
    description="Past promotions and their sales uplift.",
)

# Step 2: listing them in the PARENT's sub_agents is what makes them sub-agents.
pricing_desk = Agent(
    name="pricing_desk",
    model="gemini-2.5-flash",
    instruction="Send each pricing question to the right specialist.",
    sub_agents=[margin_model, promo_history],
)

When you wire agents together, ADK enforces a few tree rules. I checked each one on ADK Python 2.13.0 by building this tree and printing what each agent can reach:

  • One parent only. Listing margin_model in a second parent’s sub_agents raises a ValueError when the agent is built: “Agent margin_model already has a parent agent, current parent: pricing_desk”.
  • The child’s mode decides how the parent reaches it. A "chat" sub-agent is reached with transfer_to_agent. A "task" or "single_turn" sub-agent is not offered to transfer_to_agent at all. Instead, ADK adds a tool named after the child to the parent, so pricing_desk calls margin_model(request=...) like a function and gets control back. In the test, pricing_desk’s transfer targets were ['promo_history'] and its tools were ['margin_model'].
  • Transfers are scoped to the tree around the agent. transfer_to_agent offers only three kinds of target: the agent’s own chat-mode children, its parent (unless disallow_transfer_to_parent=True) and its chat-mode siblings (unless disallow_transfer_to_peers=True). ADK lists exactly those names in the instruction and as an enum (a fixed list of allowed values) in the tool’s schema, so the model is never shown any other agent. An agent defined in the same file but never put in the tree is out of scope. A name that is not in the tree at all fails at run time with “Agent … not found in the agent tree”. One honest nuance: the narrowing happens in what the model is offered. The run-time lookup itself only checks that the name exists somewhere in the tree.
  • Nesting is just more of the same. If margin_model needs a helper, give it sub_agents=[tax_calculator]. The test printed each agent’s transfer targets:
pricing_desk    -> promo_history                 (margin_model is a tool, because it is in task mode)
margin_model    -> tax_calculator, pricing_desk  (no peers: disallow_transfer_to_peers=True)
promo_history   -> pricing_desk                  (margin_model is task mode, so not a peer target)
tax_calculator  -> margin_model                  (its parent; it has no siblings)
pricing_desk can never transfer straight to tax_calculator: a grandchild is not in its scope.

The main risk. Every request pays for an extra model call before any work starts, and the supervisor can choose differently on different days.

Here is the pricing desk on one question, “What margin did Harvest Row make on last month’s promotion?”:

1. pricing_desk (model)  reads the question, calls transfer_to_agent(agent_name="margin_model")
2. margin_model          answers with the margin
3a. mode "chat"          margin_model keeps the turn: Maria's next message goes straight to it
3b. mode "task"          when margin_model finishes, control returns to pricing_desk, which
                         takes Maria's next message
4. Another day           the same question, and the model transfers to promo_history instead

Step 1 is the extra model call, and step 4 is the risk: the choice was made by a model, so a unit test cannot pin it down. Only an eval over many runs can tell you how often it picks right.

4. Agent as a tool

What it is. In agent as a tool, one agent calls another exactly as it would call a function. The inner agent does its job and returns a result, and the caller keeps control: it reads the result, decides what to do and answers the user itself.

A simple example. A sales manager gets a letter in German, asks a translator colleague for an English version, reads it, and writes and signs the reply herself. The translator never speaks to the customer. If the manager instead put the translator on the phone with the customer, the translator would own that conversation. The first is agent as a tool; the second is a sub-agent transfer.

How does this apply to Acme? At Acme, this is how it would work: the supply agent needs to explain why a penalty applies, which means reading the service-level clause in a retailer contract. Reading contract text is a language task, so the supply agent calls a small contract reader agent as a tool. The supply agent still owns the answer it sends back to the router, as the manager owns her reply.

4 · AGENT AS A TOOL: THE CALLER KEEPS CONTROL model code remote (A2A) supply agent caller; still answers the router AgentTool contract reader reads penalty clauses contract_reader(request=...) result comes back as a tool response ADK tools=[AgentTool(agent=contract_reader)]; skip_summarization=True returns its output as is RISK the inner agent's model calls are hidden inside one tool call, so cost and time are easy to miss

agent as a tool: the supply agent calls a contract reader agent wrapped in AgentTool, gets its answer back as a tool response, and keeps control of the conversation

When to choose it. When the inner job is bounded (one question in, one result out) and the caller must stay responsible for the final answer. LangChain now recommends this tool-calling style for most supervisors; the README of its langgraph-supervisor library says it recommends “using the supervisor pattern directly via tools” because it gives more control over context.

The ADK building block. Wrap the inner agent in AgentTool and list it in the caller’s tools. ADK’s documentation spells out the difference from a sub-agent: with a tool, the inner agent’s “answer is passed back to Agent A”, which “retains control”; with a sub-agent, “the responsibility of answering the user is completely transferred to Agent B”. AgentTool also accepts skip_summarization=True, which returns the inner agent’s output without the caller’s model rewording it.

The main risk. The inner agent’s model calls are hidden behind one tool call, so its cost and time are easy to overlook. Count each AgentTool as a full agent.

Here is one call through the contract reader, with the clause text kept generic:

1. Router -> supply      "Why did Acme Pure pay service penalties in Western Europe?"
2. Supply (model)        finds the penalty figure with its own query, then calls
                         contract_reader(request="Which clause sets the OTIF penalty for this retailer?")
3. contract_reader       returns, as a tool response: a short summary of the service-level clause
4. Supply (model)        combines the clause with its own EUR figure into one finding
5. Supply -> router      one finding: the figure and the reason, owned by supply

As a sub-agent instead, step 3 would transfer control: the contract reader would answer the router itself, with the clause and no penalty figure. With skip_summarization=True, step 3’s text would come back word for word, without the supply model rewording it.

5. Loop and critic

What it is. A loop repeats steps until a condition is met. The most common version pairs a generator with a critic (a second model that reviews the draft and gives feedback), and repeats until the critic is satisfied. Anthropic calls this evaluator-optimizer; ADK’s guide calls it iterative refinement.

A simple example. A reporter files a story, the editor sends back one note (“lead with the result”), the reporter revises, and at the print deadline the story runs whether or not the editor is delighted. The reporter is the generator, the editor is the critic, and the deadline is the cap. Note who stops the loop: not the editor’s mood, but the clock.

How does this apply to Acme? The writer’s after-model callback already checks every figure, and that stays in code. A critic could check the things code cannot: does the draft answer the question that was asked, and does it lead with the answer? If not, the writer gets one more try with the critique, and then the loop stops whatever happens.

5 · LOOP AND CRITIC: DRAFT, REVIEW, REVISE, WITH A HARD CAP model code remote (A2A) writer draft (Pro) critic clear? answers it? gate critique OK or 2 rounds? deliver revise: critique saved in state done ADK LoopAgent(sub_agents=[...], max_iterations=2) with escalate, or a back-edge in a Workflow RISK a loop with no hard cap can run until the run limit (max_llm_calls, 500 by default) stops it

loop and critic: the writer drafts, a critic reviews, and a code gate either sends the draft back with the critique or delivers it, with a hard cap of two rounds

When to choose it. When there are clear review criteria and a second attempt measurably improves the result. If code can check the property (a number, a format, a schema), use code, as Part 5 argued.

The ADK building block. LoopAgent repeats its sub_agents and takes max_iterations. A sub-agent stops the loop early by escalating: a tool sets tool_context.actions.escalate = True, or a custom agent yields an event with escalate=True. In a graph workflow a loop is a back-edge, an edge from a later node back to an earlier one, chosen by a route value.

The main risk. A loop that never ends. ADK’s documentation is direct about it: “the LoopAgent itself does not inherently decide when to stop looping”, so you must set the stopping rule. Always set a hard cap in code. ADK’s run configuration has a backstop, max_llm_calls, which defaults to 500 model calls per run, far too many for one business question.

Two quick cases show both stopping rules:

LoopAgent(sub_agents=[writer, critic], max_iterations=2)
  Round 1   writer drafts; the critic is satisfied, so its tool sets
            tool_context.actions.escalate = True
  Result    the loop stops after one round (2 model calls)

The same loop with no max_iterations and a critic that is never satisfied
  Each round   2 model calls (writer + critic)
  Stops at     about round 250, when the run reaches the default max_llm_calls of 500

The first loop was stopped by code reacting to the critic; the second was stopped only by a backstop that was never meant for one question. Set the cap yourself.

6. Handoff and swarm

What it is. In a handoff, one agent transfers control to another, and the other agent then talks to the user directly. When several peer agents can all hand off to each other, the result is often called a swarm. LangChain’s langgraph-swarm library describes it as agents that “dynamically hand off control to one another based on their specializations”, where the system “remembers which agent was last active”.

A simple example. You ring a call centre about a parcel, and halfway through mention a wrong charge. “Let me put you through to billing.” From then on you talk to billing. If you then ask where the parcel is, billing cannot see the courier system, and nobody is checking the whole call. The transfer is the handoff, billing is the new active agent, and “nobody owns the whole call” is the risk below.

How does this apply to Acme? Maria asks the marketing agent about a campaign, then asks about penalties. In a swarm, the marketing agent would hand her over to the supply agent, and her next message would go straight to supply. Acme does not do this, and the reason is ownership: the writer and its number check would no longer sit on every answer.

6 · HANDOFF / SWARM: CONTROL MOVES TO ANOTHER AGENT model code remote (A2A) Maria marketing marketing agent active first supply agent active after handoff transfer_to_agent her next message goes straight to supply ADK peer sub_agents in chat mode; disallow_transfer_to_peers=True switches it off RISK nobody owns the whole answer, and the path differs from one conversation to the next

handoff or swarm: Maria starts with the marketing agent, which transfers control to the supply agent, and her next message goes straight to supply

When to choose it. When a conversation genuinely changes owner and the new owner needs a long, multi-turn exchange with the user, as in customer service, where a triage agent passes a caller to a billing agent for the rest of the call.

The ADK building block. Peer sub-agents of the same parent, in the default "chat" mode, which ADK describes as “agent controls until manual handoff” and returns “via transfer”. Two settings on an Agent restrict it: disallow_transfer_to_parent and disallow_transfer_to_peers.

The main risk. Nobody owns the whole answer, and the path differs from one conversation to the next, which makes evaluation and audit hard.

Here is why Acme rejects it, as a three-turn swarm:

Turn 1  Maria: "What was Verde's CTR in Western Europe last month?"
        marketing answers
Turn 2  Maria: "And what did the stockouts cost?"
        marketing calls transfer_to_agent(agent_name="supply")
        supply answers Maria directly: no writer, no number check
Turn 3  Maria: "And Verde's spend?"
        goes to supply, the last active agent, which has no BigQuery tool

Setting disallow_transfer_to_peers=True on the marketing agent removes the turn 2 transfer. Acme goes further and has no peer transfers at all: every turn goes through the router, the writer and the check.

7. Shared state, or blackboard

What it is. In a shared state design, sometimes called a blackboard, agents do not call each other directly. Each one writes its result to a named key in a shared store, and later agents read the keys they need. This is less a separate shape than the way in-process agents pass data, which is why it spans the whole left half of the map.

A simple example. A team planning an event around a meeting-room whiteboard: the venue person writes under “Venue”, the caterer under “Food”, and the organiser reads both headings to write the invitation without asking who wrote what. Scrap paper is thrown away after each task, the whiteboard is wiped after the meeting, a personal notebook lasts for good, and the office notice board is read by everyone. The headings are state keys, and the four kinds of paper are the scopes described below (temp:, no prefix, user: and app:).

How does this apply to Acme? The router already works this way. route writes question, route_label and route_reason; the specialists’ findings land in marketing_findings and supply_findings; and the writer’s instruction reads them with placeholders such as {marketing_findings?}.

7 · SHARED STATE / BLACKBOARD: AGENTS MEET IN SESSION STATE model code remote (A2A) session.state question route_label marketing_findings supply_findings critique route question, route_label critic writes critique marketing agent its findings supply agent its findings writer reads {key?} ADK output_key="marketing_findings", Event(state={...}), {marketing_findings?} in an instruction RISK any agent can read any key, so a careless key name leaks data or one agent overwrites another

shared state or blackboard: the route, the specialists and the critic write named keys into session state, and the writer reads them through placeholders in its instruction

When to choose it. When several in-process agents each contribute a piece of the answer. The writer does not need to know which agent produced a key.

The ADK building block. Session state. An Agent with output_key="marketing_findings" saves its final text to that key; a function node can return Event(state={...}); and an instruction reads a key with {key} or {key?}. Key prefixes set the scope: no prefix means this session, user: means every session for this user, app: means every user, and temp: means this one invocation only. ADK notes that sub-agents called by a parent share the same invocation, and therefore the same temp: state. Part 8 draws how such a write travels from the node, on an event, to the saved session.

Here is how the prefixes play out across two of Maria’s sessions, using keys from this series:

Session 1, question 1
  user:home_team = "marketing"   (user scope: saved for every session of Maria's)
  question       = "Compare the Acme Pure Refill Launch spend with ..."   (this session)
  temp:rounds    = 2             (the critic gate's counter: this question only)
Session 1, question 2
  temp:rounds starts again at 0; question is overwritten
Session 2, a new chat
  user:home_team = "marketing"   (still there)
  question       = not set until Maria asks something

Picking the prefix is picking how long a value lives: a team for as long as Maria exists, a question for one chat, a loop counter for one question.

The main risk. Any agent can read any key, and a key written for one question is still there for the next one unless something clears or scopes it. The Try it section below shows exactly this. Across A2A there is no shared state at all: a remote agent keeps its own session, and data travels only in messages and metadata, as Part 6 showed.

8. Mesh over A2A

What it is. In a mesh, agents run as independent services, often owned by different teams or companies, and call each other over the network. Each agent publishes an agent card describing what it does and how to reach it. The A2A specification (latest released version 1.0.0 as of October 2026) is designed for exactly this: communication between “independent, potentially opaque AI agent systems”, which collaborate “without needing to share their internal thoughts, plans, or tool implementations”.

A simple example. Separate companies phoning each other: a shop rings its supplier, who rings a haulier. Each company has a published number and a list of what it can do (the agent card), and none sees inside the others’ offices. If the supplier’s receptionist says “let me check with the haulier” and the haulier’s says “let me check with the supplier”, the caller waits on hold until someone gives up.

How does this apply to Acme? Today Acme is a hub and spoke (a star): the router calls each specialist, and the specialists call nobody. It would become a mesh if, say, the supply agent called a retailer’s replenishment agent or a carrier’s delivery-slot agent. That is a possible future, not part of this series.

8 · MESH OVER A2A: SPECIALISTS ACROSS TEAMS AND COMPANIES model code remote (A2A) ACME CONSUMER BRANDS A RETAILER (ILLUSTRATIVE) A CARRIER (ILLUSTRATIVE) router Workflow marketing agent agent card supply agent agent card replenishment agent agent card delivery-slot agent agent card A2A A2A A2A A2A Each arrow is a network call: an agent card first, then a message with metadata. The supply agent calling outside Acme is a possible future, not part of the series build. ADK RemoteA2aAgent(name=..., agent_card=URL), used as a sub-agent or a workflow node (experimental) RISK every hop adds latency and a contract; a slow partner agent slows every answer that needs it

a mesh over A2A: the router calls Acme's marketing and supply agents, and the supply agent could in turn call agents run by a retailer and a carrier, each found through its agent card

When to choose it. ADK’s introduction to A2A lists the cases: a separate service, a different team or organisation, a different language or framework, or the need for a formal contract. It advises against A2A for internal code organisation, low-latency internal steps and sub-agents that need shared state.

The ADK building block. RemoteA2aAgent, given a name and the URL of the remote agent_card, used as a sub-agent or a workflow node, as in Part 6. It lives in google.adk.agents.remote_a2a_agent and needs the a2a extra (pip install "google-adk[a2a]"); I checked the import and constructor on ADK Python 2.12.0, which also accepts a description, a timeout and your own httpx_client. ADK still marks A2A support in Python as experimental as of October 2026, and constructing one prints a warning saying so.

The main risk. Every hop adds network latency and a contract that both sides must keep stable. A slow partner agent slows every answer that needs it, and nothing central stops agent A calling B calling A. Identity must also travel across every hop, which is the subject of Part 7.

Here is an illustrative cycle, using the possible future above:

1. Router -> Acme supply             timeout=300, as in Part 6
2. Acme supply -> retailer's agent   "What are next week's replenishment orders?"
3. Retailer's agent -> Acme supply   "What is our OTIF this month?" (to plan the orders)
4. Acme supply -> retailer's agent   ... and round again

Nothing in A2A breaks this cycle. Maria waits until the router’s 300 seconds run out and then gets no supply finding, while the calls inside the cycle can keep running and costing money. Each hop must also carry who is asking, and decide again whether to trust it (Part 7). A mesh needs its own hop limits and timeouts on every agent, not just at the top.

How do topologies combine?

Real systems nest topologies: a graph contains a fan-out, a branch contains a supervisor, a loop wraps the writer. The skill is keeping each decision in the right place. A restaurant does the same: the till routes tickets by rule, the starter and main cook in parallel, the head chef improvises only on the specials board, and the pass tastes each plate once before it goes out. Rules at the top, judgement in one small corner, a check on everything. How does this apply to Acme? Here is a worked extension of the Acme router with the same layout:

  • Keeps: code routes at the top. route gains a fourth value, pricing.
  • Adds a supervisor inside one branch: the pricing desk, which calls two agents as tools, promotion history and margin.
  • Adds a critic loop on the writer: a critic model reviews each draft, and a code gate sends it back at most once.
  • Keeps the number check on every draft, because it is the writer’s after-model callback, so it runs every time the writer runs.
ONE GRAPH, FOUR TOPOLOGIES: THE ACME ROUTER WITH A PRICING DESK AND A CRITIC model code remote (A2A) new in this post extract Flash route code marketing supply both (fan-out) pricing desk supervisor (model) NEW promo AgentTool margin AgentTool writer Pro number check critic model NEW gate rounds < 2? NEW answer revise done Code still makes every routing and stopping decision. Models write, review and pick pricing tools inside one branch. The number check runs on every draft the writer makes.

the Acme router extended: code still routes at the top, a new pricing desk supervisor calls two agent tools inside its branch, and a critic loop with a code gate reviews the writer's draft at most twice before the answer goes out

In code, the extension is a few new nodes and edges. This is an illustration I wrote for this post, not code from the series repository; front, the decline branch and the model settings are left out. I did run it on ADK Python 2.12.0 with a stand-in model that returns fixed text (no API key, no network): the graph builds, output_key works on Agent nodes inside a Workflow (the writer’s text lands in draft, the critic’s in critique), and the gate ends the loop as soon as the critique starts with “OK”:

# Illustrative only: the Acme router plus a pricing desk and a critic loop.
pricing_desk = Agent(
    name="pricing_desk",
    model=Gemini(model=PRO_MODEL),
    instruction="Answer the pricing question with your tools. Never invent a price.",
    tools=[AgentTool(agent=promo_history), AgentTool(agent=margin_model)],   # caller keeps control
    output_key="pricing_findings",
)

critic = Agent(
    name="critic",
    model=Gemini(model=FLASH_MODEL),
    instruction=(
        "Reply OK if the draft answers {question?} and leads with the answer. "
        "Otherwise reply with one line of feedback. Draft: {draft?}"
    ),
    output_key="critique",
)

def gate(ctx, node_input):
    """Code decides when the loop stops: the critic is happy, or two rounds are done."""
    rounds = ctx.state.get("temp:rounds", 0) + 1
    done = ctx.state.get("critique", "").startswith("OK") or rounds >= 2
    return Event(output=node_input, route="done" if done else "revise", state={"temp:rounds": rounds})

root_agent = Workflow(
    name="router",
    edges=[
        ("START", extract, route),
        (route, {"marketing": marketing, "supply": supply, "both": both, "pricing": pricing_desk}),
        (marketing, writer), (supply, writer), (both, writer), (pricing_desk, writer),
        (writer, critic, gate),                      # writer keeps output_key="draft"
        (gate, {"revise": writer, "done": deliver}),  # back-edge with a hard cap
    ],
)
  • route is still code. Adding a specialist means adding a route value and an edge, and a unit test for the new value.
  • The pricing desk is the only place a model chooses, and its choice is limited to two tools inside one branch. If it picks badly, only pricing answers are affected.
  • The gate is code, and its round counter uses a temp: key, so it starts again at zero for every question.
  • Cost is visible. A pricing question now makes at least six model calls at the router level (extract, the pricing desk twice, a tool agent, the writer and the critic), and up to nine with both tools and a second draft. Today’s router makes two. That should be a decision, not a surprise.

What goes wrong as topologies grow?

Each topology is reasonable on its own. Problems appear when they stack up:

  • Cost multiplies. In How we built our multi-agent research system, Anthropic reports that agents use about 4 times more tokens than chat, and multi-agent systems about 15 times more. Measure cost per question by route, as Part 11 does.

  • Loops never end. A critic that is never satisfied, or two agents transferring back and forth, runs until something stops it. Cap every loop in code, lower max_llm_calls, and keep the kill switch and rate limit from Part 9.

  • Context leaks between agents. Every agent can read every state key, and history can carry one agent’s data into another’s prompt. Use distinct keys, temp: for anything that belongs to one question, and include_contents="none" for an Agent that should work only from its instruction and the current turn. Across A2A, send only what the remote agent needs.

    For example, take the critic from the worked extension above. By default it sees the conversation history, including the previous question’s penalty findings, and may judge today’s draft against them. With include_contents="none" it sees only its instruction, filled in with {question?} and {draft?}, so it can judge only the draft it was given.

  • Ownership blurs. With handoffs and supervisors, it is unclear which agent, and so which team, owns an answer. Keep one owner per answer; in Acme that is the writer, checked by code.

  • Tracing gets harder. One question can touch eight agents in three services. Give each node a clear name; ADK records a path such as router@1/writer@2 for the second pass through the writer. Part 11 covers traces, and Part 10 the evals every new route needs.

Which topology should I choose?

The bottom line: pick the shape with the fewest model-made decisions that still does the job. The trap is splitting into agents because it looks modular; every split adds model calls, hops and a new place for answers to go wrong.

Start with the simplest shape that does the job, and move down this list only when the one above it cannot:

  1. One agent with good tools. Anthropic notes that for many applications, optimising single model calls with retrieval is enough.
  2. Sequential pipeline. Choose it when the steps are known and ordered.
  3. Parallel fan-out and gather. Choose it when the steps are independent and the user is waiting.
  4. Loop with a critic. Choose it when a review measurably improves the answer and code cannot do the review. Always cap it.
  5. Agent as a tool. Choose it when a bounded language task deserves its own agent but the caller must keep ownership.
  6. Hierarchical supervisor. Choose it when the subtasks really cannot be predicted. Keep it inside a branch, not at the top.
  7. Handoff or swarm. Choose it only when a conversation genuinely changes owner for many turns.
  8. Mesh over A2A. Choose it when the other agent belongs to another team, language or company. Ownership and permissions are good reasons to split; looking modular is not.

Here are the three requests from the start of this post, put through the list:

  • “The pricing team wants its own agent.” The questions are pricing questions, which a rule can spot, so step 2 or 3 is enough at the top: a new pricing route value and one more branch, as in the worked extension. Step 6 applies only inside that branch, if pricing questions really cannot be predicted.
  • “A second model should review the writer’s draft.” Code already checks the numbers, and only the “does it answer the question” part needs a model, so this is step 4: a critic loop capped in code.
  • “A partner’s agent wants to talk to ours.” The other agent belongs to another company, so this is step 8: A2A, with the identity checks from Part 7.

The series rule settles most arguments: Models read and write language. Code makes the decisions. If a choice can be written as a rule (who owns a metric, when to stop, which branch to take), write it in code. Use a model where the task is understanding or writing language: rewriting a question, reading a contract, reviewing a draft for clarity.

My view: in enterprise systems, supervisors and swarms are best kept small and deep inside a graph that code controls, and users should still see one agent. For Acme, that is the router we publish to Gemini Enterprise in Part 12, whatever shape sits behind it.

To lock it in, here are the eight shapes with the everyday picture used above:

Topology             What it is                                   Everyday picture
-------------------  -------------------------------------------  ------------------------------------
Sequential pipeline  fixed steps, each feeds the next             a car wash: soap, rinse, dry
Fan-out and gather   independent steps at once, then combine      rice and curry on two hobs
Supervisor           a model picks a sub-agent at run time        a GP referring to a specialist
Agent as a tool      call an agent, get a result, keep control    a manager using a translator
Loop and critic      draft, review, revise, capped in code        a reporter, an editor, a deadline
Handoff or swarm     control moves to another agent for good      "putting you through to billing"
Shared state         agents write and read named keys             headings on a meeting whiteboard
Mesh over A2A        independent services calling each other      companies phoning each other

What does each step of the ladder look like in a real company?

Acme only needs a few of these shapes, so here is one real-world job for each step, outside Acme. For each one I give the shape, why it fits, and why the step above it on the ladder would not be enough. The companies and timings are illustrative; the reasoning is the point.

1. One agent IT helpdesk: reset password, check VPN, search IT docs agent reset vpn docs one loop, three tools 2. Sequential Medical scribe: notes, then ICD-10 codes, then a letter extract code letter each step needs the one before it; order never changes 3. Parallel and gather Loan underwriting: credit, legal, cash flow, one memo in credit legal cash memo wait for the slowest only 4. Loop and critic Bank promo email checked for compliance, max 2 rewrites writer critic gate (code) capped in code 5. Agent as a tool Portfolio agent asks a tax agent; it still owns the reply portfolio tax agent call, result back, caller writes 6. Supervisor Ask-data desk: a model splits an open question: churn, tickets supervisor churn tickets a model decides who works 7. Handoff Airline: triage hands over to rebooking, then baggage triage rebook bags traveller the traveller talks to whoever owns the conversation now 8. Mesh over A2A Corporate travel agent books with other companies' agents your company other companies travel airline hotel A2A only an Agent Card and auth cross the boundary THE LADDER, ONE REAL JOB PER STEP (TOP LEFT IS SIMPLEST) Blue boxes call a model. White boxes are code, tools or people.

eight topologies, eight real jobs: an IT helpdesk agent with three tools; a medical scribe pipeline from notes to billing codes to a patient letter; loan underwriting checks run in parallel and gathered into one memo; a bank's marketing copy reviewed by a compliance critic at most twice; a portfolio agent calling a tax agent as a tool; an analytics supervisor delegating to churn and support sub-agents; an airline triage agent handing the traveller to rebooking and then baggage; and a travel agent talking to other companies' agents over A2A

1. One agent with good tools: an internal IT helpdesk. The agent resets passwords, checks VPN status and searches the IT documentation, with three narrow tools: reset_password, check_vpn_status and search_it_docs. One model in one loop handles most requests, such as “My VPN keeps dropping and I am locked out”: it checks the VPN, then offers a reset. Splitting this into three agents would add a routing call and more tokens to every request without making any answer more accurate. There is no simpler step, and nothing here needs a harder one.

2. Sequential pipeline: a medical scribe and billing pipeline. Step one extracts structured notes (symptoms, diagnosis, medicines) from a consultation transcript. Step two maps the diagnosis to ICD-10 billing codes, the WHO’s standard classification of diseases. Step three writes a plain-English letter for the patient. In ADK this is a SequentialAgent or a Workflow with edges ("START", extract, code, letter), and each step reads the one before through state (output_key and a {notes?} placeholder). It fits because every step depends on the previous output and the order never changes: you cannot code a diagnosis you have not extracted yet. One agent with tools would work, but the order would then depend on the model; here code fixes it. A clinician still reviews the notes and the codes before anything is billed or sent.

3. Parallel fan-out and gather: commercial loan underwriting. When a business applies for a loan, three checks run at the same time: a credit agent reads credit bureau scores and debt ratios, a legal agent searches court records and sanctions lists, and a cash-flow agent analyses twelve months of bank statements. A gather step (ParallelAgent, or a JoinNode in a Workflow) waits for all three, and a writer turns them into one risk memo. A pipeline would give the same memo, but slower, because none of the checks needs another’s result:

SAME THREE CHECKS, TWO SHAPES (ILLUSTRATIVE TIMINGS, SECONDS) one after another total about 37 s credit 15 legal 12 cash 10 side by side total about 15 s credit 15 legal 12 cash 10 gather: the memo starts here Parallel pays only when the checks are independent; if legal needs the credit result, it is a pipeline.

why the underwriting checks run in parallel: three independent checks of about 15, 12 and 10 seconds take about 37 seconds one after another, but only as long as the slowest, about 15 seconds, when they run side by side and are gathered into one memo (illustrative timings)

The loan officer waits for the slowest check, not for all three in turn. The credit decision itself stays with a person.

4. Loop and critic: a bank’s marketing copy. A writer agent drafts a promotional email for a new credit card. A critic agent reviews it against financial promotion rules; in the UK, the FCA requires promotions to be “fair, clear and not misleading”. Is the interest rate as prominent as the offer? Does it imply guaranteed approval? A code gate sends the critique back for a rewrite, at most twice, then stops either way. It fits because “misleading” is a judgement about language that no regular expression can make, and one targeted rewrite fixes most slips. A pipeline cannot express “try again”, and the cap keeps cost and time bounded. A compliance officer still signs off before anything is sent.

5. Agent as a tool: a portfolio agent asking a tax agent. A client asks a wealth-management agent “Should I sell my tech shares to buy bonds now?”. The portfolio agent calls a tax agent through AgentTool, as a function: “What is the capital gains impact of selling 500 shares?”. The tax agent runs its own instruction and tax-band lookups and returns a number. The portfolio agent weighs that against the client’s goals and writes the reply. It fits because tax needs its own prompt and tools, but the caller must keep the conversation: the tax figure is one input, not the answer. A loop or pipeline would hand the answer to whichever step ran last.

6. Hierarchical supervisor: an “ask data” analytics desk. An executive asks “Why did third-quarter churn spike in Europe compared with North America, and did support tickets rise beforehand?”. A supervisor model breaks the question down and delegates to a churn sub-agent (CRM data) and a support sub-agent (the ticket system), then combines their answers. In ADK the sub-agents sit in sub_agents=[...] with mode="task" or "single_turn", so the supervisor calls them and gets control back. It fits because the space of questions is open: no if/else can anticipate every combination. Where questions can be classified by a rule, as at the top of Acme’s router, use code instead (step 2 or 3).

7. Handoff and swarm: an airline’s customer service. A traveller writes to a triage agent: “My flight was cancelled and my bag is stuck.”. Triage hands the conversation to a rebooking agent, which needs several turns with the traveller (“The 4 pm via another hub, or tomorrow’s direct flight? Window or aisle?”). When the traveller asks “Now what about my bag?”, rebooking hands over to a baggage agent for its own multi-turn conversation. In ADK these are sub_agents in mode="chat", which keep the conversation until they transfer it. It fits because the conversation genuinely changes owner for many turns; routing every seat preference back through a supervisor and a writer would be slow and awkward. A supervisor would keep pulling control back after every reply.

8. Mesh over A2A: corporate travel across companies. A company’s travel agent, built with ADK on Google Cloud, books a trip by calling an airline’s booking agent and a hotel group’s reservation agent, each built by a different company, in a different language, on a different cloud. It reads their Agent Cards and calls them over A2A, authenticating with whichever scheme each card declares, for example OAuth 2.0. It fits because the agents share no code, memory or database, and never will; a network contract is the only option. Agent as a tool would need the other company’s agent in your process, which is impossible here.

The pattern across all eight: each step up the ladder buys one specific capability (fixed order, speed, a second opinion, a specialist the caller controls, open-ended delegation, a change of owner, a company boundary) and costs latency, tokens and testability. Take the step only when you need that capability.

Try it

This small program builds four topologies at once with no model at all, so it runs without any API key: a code route, a parallel fan-out with a JoinNode, a critic loop with a hard cap, and shared state between all of them. Plain Python functions stand in for the agents, and their figures are toy data. It runs on ADK Python 2.12; install it with pip install "google-adk>=2.0".

import asyncio

from google.adk import Event, Workflow
from google.adk.apps import App
from google.adk.runners import InMemoryRunner
from google.adk.workflow import JoinNode

MAX_ROUNDS = 2


def route(node_input: str):
    """Code picks the path: no model, no tokens, same answer every time."""
    q = node_input.lower()
    if "spend" in q and "penalt" in q:
        target = "both"
    elif "penalt" in q:
        target = "supply"
    else:
        target = "marketing"
    return Event(output=node_input, route=target, state={"question": node_input})


def marketing(node_input: str):
    text = "Acme Pure spend: EUR 283,590"
    return Event(output=text, state={"marketing_findings": text})


def supply(node_input: str):
    text = "Service penalties: EUR 35,600"
    return Event(output=text, state={"supply_findings": text})


def writer(ctx, node_input):
    """Stands in for the writer model: it drafts from whatever is in state."""
    rounds = ctx.state.get("temp:rounds", 0) + 1
    parts = [ctx.state.get("marketing_findings"), ctx.state.get("supply_findings")]
    draft = "; ".join(p for p in parts if p)
    if rounds == 1:
        draft = "Here is what I found. " + draft      # wordy first draft
    return Event(output=draft, state={"draft": draft, "temp:rounds": rounds})


def critic(ctx, node_input: str):
    """Stands in for the critic model: it asks for the answer to lead."""
    ok = not node_input.startswith("Here is what I found")
    return Event(output=node_input, state={"critique": "OK" if ok else "Lead with the figures."})


def gate(ctx, node_input: str):
    """Code decides when the loop stops: the critic is happy, or the cap is reached."""
    done = ctx.state["critique"] == "OK" or ctx.state["temp:rounds"] >= MAX_ROUNDS
    return Event(output=node_input, route="done" if done else "revise")


def deliver(ctx, node_input: str):
    return f"{node_input} (after {ctx.state['temp:rounds']} round(s))"


both = Workflow(name="both", edges=[("START", (marketing, supply), JoinNode(name="merge"))])

root_agent = Workflow(
    name="topologies",
    edges=[
        ("START", route),
        (route, {"marketing": marketing, "supply": supply, "both": both}),   # code routes
        (marketing, writer),
        (supply, writer),
        (both, writer),                                                    # fan-out, then gather
        (writer, critic, gate),
        (gate, {"revise": writer, "done": deliver}),                       # loop with a cap
    ],
)

app = App(name="topologies", root_agent=root_agent)


async def main():
    runner = InMemoryRunner(app=app)
    events = await runner.run_debug(
        [
            "Compare our Acme Pure spend with the service penalties",
            "What were the service penalties?",
        ],
        quiet=True,
    )
    for e in events:
        if e.output is not None:
            print(f"{e.node_info.path:42} {e.output}")


asyncio.run(main())

Run it and look at the output:

topologies@1/route@1                       Compare our Acme Pure spend with the service penalties
topologies@1/both@1/marketing@1            Acme Pure spend: EUR 283,590
topologies@1/both@1/supply@1               Service penalties: EUR 35,600
topologies@1/both@1/merge@1                {'marketing': 'Acme Pure spend: EUR 283,590', 'supply': 'Service penalties: EUR 35,600'}
topologies@1/writer@1                      Here is what I found. Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/critic@1                      Here is what I found. Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/gate@1                        Here is what I found. Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/writer@2                      Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/critic@2                      Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/gate@2                        Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/deliver@1                     Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600 (after 2 round(s))
topologies@1/route@1                       What were the service penalties?
topologies@1/supply@1                      Service penalties: EUR 35,600
topologies@1/writer@1                      Here is what I found. Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/critic@1                      Here is what I found. Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/gate@1                        Here is what I found. Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/writer@2                      Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/critic@2                      Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/gate@2                        Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600
topologies@1/deliver@1                     Acme Pure spend: EUR 283,590; Service penalties: EUR 35,600 (after 2 round(s))
  • The route and the fan-out. The first question mentions spend and penalties, so route sends it down both, and marketing and supply run as two branches before merge gathers them.
  • The loop. The first draft fails the critic, so gate sends it back; writer@2 and critic@2 are the second round, and deliver runs after it. Change MAX_ROUNDS to 1 and the first draft goes out.
  • The leak. The second question only asks about penalties, and only supply runs, yet the answer still contains the Acme Pure spend. The marketing finding from the first question is still in session state. Rename both findings keys to temp:marketing_findings and temp:supply_findings (in the specialists and in writer) and run it again: the second answer now contains only the penalties. Acme’s real router uses the other fix: its route step writes marketing_findings and supply_findings back to empty strings at the start of every question (Part 5), so the writer never sees the previous question’s findings. Either works; what matters is that something clears them on purpose.

What can go wrong

  • A supervisor at the top. A model choosing the route on every question adds cost and removes the guarantee that only supply computes penalties. Keep the top-level route in code.
  • A critic prompt as the stopping rule. “Stop when it is good” is not a cap. Count rounds in code.
  • An agent tool that grows into a hidden tree. If an AgentTool gains its own agent tools, one tool call hides a whole topology. Draw it and count its calls.
  • The same agent as a sub-agent and a tool. The model then has two ways to reach it, with different ownership. Pick one.
  • Splitting into remote agents too early. Every A2A hop is latency, a contract and a deployment.

What code did we write in this part?

Every part ends with the same map of Acme’s build: which files exist so far, and the code this part added, explained line by line. The map grows with each part.

This part adds no code to Acme’s build. It compares eight multi-agent topologies and places the router among them; the snippets are illustrations, including a worked extension with a pricing desk supervisor and a critic loop that the post says is not part of the series repository, and a toy program with no model that shows the topologies running.

Where does it live?

router/                                             the router agent: extract, route, write
  app/agent.py                                      P5          the graph: front, extract, route, both, decline, edges
  app/remote.py                                     P6          A2A handles for marketing and supply, metadata, capture
  app/writer.py                                     P5          writer agent and its after-model number check
marketing/                                          the marketing specialist, on BigQuery
  app/agent.py                                      P1,2,3,4    marketing agent with its skill in a SkillToolset
  app/app_utils/a2a.py                              P6          A2A endpoint and agent card (1.0 and 0.3) for marketing
  app/skills/marketing-kpis/SKILL.md                P4          marketing know-how: which tool, definitions, story checks
  app/skills/marketing-kpis/references/tables.md    P4          tables, grain, measures and the join rule, read on demand
  app/tools.py                                      P1,2        three narrow tools: measures, unserved demand, SQL fallback
  tests/unit/test_marketing.py                      P2          unit tests for the tools, with a fake context and database
supply/                                             the supply specialist, on Databricks
  app/agent.py                                      P3          the supply Agent, Databricks specialist, behind sql_guard
  app/app_utils/a2a.py                              P6          A2A endpoint and agent card (1.0 and 0.3) for supply
  app/skills/supply-catalog/SKILL.md                P4          supply know-how, given only to the supply agent
  app/tools.py                                      P3          supply tools such as query_supply_measures, run as the user
shared/                                             code all three services share
  adk_common.py                                     P3,6        shared ADK helpers, now with the A2A request converter
  engines.py                                        P3          run_bigquery and run_databricks: guard again, caps, as the user
  factcheck.py                                      P5          ungrounded_numbers: finds figures not in the findings
  registry.py                                       P2          reads semantic.yaml: measure names, computable measures
  routing.py                                        P1          classify: picks the specialist in code, no model
  semantic.yaml                                     P1,2        registry of measures, owners and published formulas
  skill_source.py                                   P4          loads a skill from disk or a pinned registry revision
  sqlguard.py                                       P3          guard_sql read-only allow-list; redact for log lines

+ added in this part   ~ changed in this part   Pn = the parts that touched the file

The Try it shows why findings must be cleared between questions; Acme’s router already does this in its route step, from Part 5.

What did we learn?

  • A multi-agent topology is the shape of a system: who calls whom, who holds the state and, most importantly, who decides the next step.
  • Two axes place almost every topology: whether code or a model decides the next step, and whether the agents run in one process or as remote services over A2A.
  • The eight common topologies are sequential, parallel fan-out and gather, hierarchical supervisor, agent as a tool, loop and critic, handoff or swarm, shared state, and a mesh over A2A, and ADK has a building block for each.
  • Real systems combine topologies, and the safe way to do it is to keep the top-level route and every stopping rule in code, and let models choose only inside a bounded branch.
  • Growing topologies multiply cost, risk endless loops, leak context between agents, blur ownership and make tracing harder, so each one needs a cap, scoped state, one owner per answer and a clear trace.
  • The simplest topology that works is the right one, and the series rule still decides: models read and write language, code makes the decisions.

What comes next?

Every topology in this post passes work from one agent to another, and in a mesh that work crosses services and even companies. That raises the question of whose permissions each agent uses. Part 7: Who is asking? Identity for agents answers it. Every call an agent makes carries two identities, the agent’s own and the user’s, and Part 7 explains both: Agent Identity on Google Cloud, how OAuth 2.0 works, and how the supply agent queries Databricks as the signed-in user while keeping tokens away from the model.

Sources

Opinions are my own. Every fact in this post comes from the public documentation linked above. Acme Consumer Brands and its data are fictional.