Google’s Agent Development Kit does not give you one kind of agent — it gives you four, and picking the wrong one is the most common way an ADK system ends up slow, flaky, or impossible to test. There is the LlmAgent, a reasoning, tool-using model that decides what to do next; the Workflow agents — SequentialAgent, ParallelAgent, LoopAgent — that orchestrate other agents with deterministic, coded control flow; the Custom agent, a subclass of BaseAgent where you write arbitrary control logic yourself; and Agent-as-a-Tool, where one agent calls another and gets its answer back like a function result. This article is the map, not the deep dive: it stays at the ‘which type when’ altitude, gives you a decision table that maps the shape of your problem to the right agent, and shows a code sketch of each so you can recognize them on sight. The sibling pieces go deep on the LlmAgent and on workflow internals; this one keeps you from reaching for the wrong tool in the first place.

The four families, and the one question that separates them

Every ADK agent is a BaseAgent under the hood — that is the common base class, and it is why any agent can be a sub-agent of any other. On top of that base sit four practical families you actually instantiate. The LlmAgent (often just written Agent) wraps a model and a toolset and lets the model reason turn by turn. The three Workflow agents — Sequential, Parallel, Loop — hold sub-agents and run them in a fixed, coded pattern with no model in the driver’s seat. A Custom agent is your own subclass of BaseAgent for control flow that none of the built-in primitives express. And Agent-as-a-Tool is not a new class so much as a composition move: you wrap an agent so another agent can call it as a function.

The single question that sorts nearly every decision is this: who decides what happens next — the model, or your code? If a decision is a judgment call (‘is this a refund or a complaint?’), you want a model deciding, so it lives in an LlmAgent. If a decision is structural and known in advance (‘research, then write, then review’), you want code deciding, so it lives in a Workflow or Custom agent. Hold that axis in mind and the rest of this article is mostly filling in the corners.

Advertisement

LlmAgent: reach for it when the next step is a judgment call

The LlmAgent is the reasoning core and the type most people start with. You give it a model, an instruction, and a set of tools, and on each turn the model reads the context and chooses: call a tool, ask a question, or answer. That autonomy is exactly the point — use an LlmAgent whenever the path through the task is not knowable ahead of time and depends on the content of the input. Triage, conversational assistants, open-ended tool use, ‘figure out which of these ten functions to call’ — all LlmAgent territory.

from google.adk.agents import LlmAgent

support = LlmAgent(
    name="support",
    model="gemini-2.0-flash",
    instruction="Help the user. Use tools to look up orders and issue refunds.",
    tools=[lookup_order, issue_refund],
)

The cost of that flexibility is nondeterminism: the model may choose differently on identical inputs, it burns a model call on every decision, and its reliability degrades as you pile on instructions and tools. This article stays out of the LlmAgent’s internals — how to write instructions, wire output_key, or tune tools lives in the dedicated LlmAgent piece. Here the only thing that matters is the trigger: reach for an LlmAgent when you genuinely want the model to decide. When you already know the sequence, you are about to overpay for a decision you could have coded.

Workflow agents: reach for them when the shape is fixed

The three Workflow agents exist so you do not burn model calls rediscovering a structure you already know. They contain sub-agents and run them in a deterministic pattern — no LLM decides the control flow. Which of the three you pick is itself a small decision keyed to the shape of the work:

  • SequentialAgent — fixed ordered stages, each building on the last (gather → analyze → write).
  • ParallelAgent — independent subtasks you want to run at once and then combine (three researchers, then a merge).
  • LoopAgent — repeat a body until it is good enough or a bound is hit (draft → critique → revise, iterate).
from google.adk.agents import SequentialAgent, ParallelAgent

gather = ParallelAgent(name="gather", sub_agents=[web_researcher, kb_researcher])
pipeline = SequentialAgent(
    name="report",
    sub_agents=[gather, synthesizer, reviewer],
)

Because a Workflow agent is itself a BaseAgent, they nest freely — a Sequential can contain a Parallel that contains a Loop, all wrapping LlmAgent leaves. That is the composability payoff. The mechanics of how state passes between stages and how a loop terminates are covered in depth in the workflow-agents article; for the taxonomy, the trigger is enough: if the control flow is knowable in advance, encode it in a Workflow agent so it becomes a guarantee instead of something you hope the model chooses.

Custom agents: reach for BaseAgent when no primitive fits

Sometimes the control flow you need is real but not sequential, not parallel, and not a plain loop. You need a conditional branch (‘if the validator passed, ship; otherwise escalate to a human’), an early exit, a state-machine, or integration with an external system mid-flow. That is the Custom agent: you subclass BaseAgent and write the orchestration yourself, calling child agents where you want them and applying ordinary Python logic in between.

from google.adk.agents import BaseAgent
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events import Event

class ConditionalReview(BaseAgent):
    async def _run_async_impl(self, ctx: InvocationContext):
        async for e in self.drafter.run_async(ctx):
            yield e
        if ctx.session.state.get("score", 0) < 7:
            async for e in self.reviser.run_async(ctx):
                yield e   # only revise when the draft is weak

The shape to memorize: override _run_async_impl(self, ctx), drive each child with async for event in child.run_async(ctx): yield event, and put your branching logic in between. This is the most powerful and the least guarded type — you own the flow entirely, which also means you own the bugs. The right instinct is to reach for a Custom agent only after the built-in Workflow primitives genuinely cannot express your flow, because a Sequential or Loop you get for free is easier to read and test than a hand-written orchestrator.

Agent-as-a-Tool: reach for it to borrow an answer, not hand over control

The fourth move is composition by Agent-as-a-Tool. You wrap an agent so that a parent LlmAgent can call it exactly like a function: the parent’s model emits a call, the wrapped agent runs to completion in a nested invocation, and its final answer comes back as the tool result. Crucially, control never leaves the parent — it delegates a subtask, receives the output, and continues its own reasoning.

from google.adk.agents import LlmAgent
from google.adk.tools.agent_tool import AgentTool

summarizer = LlmAgent(name="summarizer", model="gemini-2.0-flash",
                      instruction="Summarize the given text in 3 bullets.")

assistant = LlmAgent(
    name="assistant", model="gemini-2.0-flash",
    instruction="Answer questions. Use the summarizer tool for long documents.",
    tools=[AgentTool(agent=summarizer)],
)

This is the ‘data’ kind of composition, and it is worth contrasting in one sentence with the ‘control’ kind: LLM transfer (via sub_agents) hands the conversation to a specialist that then owns it, whereas AgentTool keeps the conversation and just borrows a result — the full transfer-versus-tool story belongs to the multi-agent article. Reach for Agent-as-a-Tool when a parent needs a specialist’s answer as an ingredient in its own work, and does not want to give up the wheel.

The organizing axis: reasoned control versus coded control

Lay the four types on one line and a pattern appears. The LlmAgent puts control in the model — maximum flexibility, maximum nondeterminism. The Workflow agents put control in code, using a small fixed vocabulary (order, fan-out, repeat) — less flexible, fully deterministic. The Custom agent also puts control in code but with the full expressiveness of Python — maximum control, maximum responsibility. And Agent-as-a-Tool is orthogonal: it is how you let a reasoned agent reach into any of the others without ceding control.

The practical guidance that falls out of this axis is use the most constrained type that still expresses your problem. Determinism, low cost, and testability all increase as you move from LlmAgent toward Workflow and Custom agents, because a decision made in code is one you can read, assert on, and never pay a model call for. So do not default to a big autonomous LlmAgent for a task whose steps you can already name; and do not hand-roll a Custom agent for a flow that is plainly a Sequential. Spend the model’s judgment where judgment is actually required, and spend code everywhere else. Most robust ADK systems are mostly coded structure with LlmAgents at the leaves — reasoning where it counts, determinism everywhere it is free.

The decision table: problem shape → agent type

This is the heart of the article. Read the left column as a description of your problem and take the type on the right.

The shape of your problemAgent type
The next step depends on the input and is a judgment callLlmAgent (with tools)
Routing among specialists is itself a judgment callLlmAgent with sub_agents (transfer)
Fixed ordered stages, each building on the lastSequentialAgent
Independent subtasks; you need all results, faster togetherParallelAgent
Iterate until ‘good enough’ or a validator passesLoopAgent
Conditional branches, early exit, or a state machineCustom agent (subclass BaseAgent)
A parent needs a specialist’s answer but must keep controlAgent-as-a-Tool (AgentTool)
A specialist should own the conversation from here onLLM transfer via sub_agents

Two reading tips. First, these are not mutually exclusive — a single system routinely uses several rows at once, which the next section makes concrete. Second, when two rows seem to fit, prefer the more constrained one: if a flow is ‘fixed ordered stages’ it does not matter that an LlmAgent could also drive it — the SequentialAgent is cheaper and guaranteed. The table rewards you for noticing when a task only looks like it needs judgment.

Advertisement

Real systems mix all four

The types are not competing product tiers; they are building blocks that nest. Because everything is a BaseAgent, a Workflow agent can hold LlmAgents, an LlmAgent can carry other agents as tools, and a Custom agent can drive any of them. A realistic customer-support system might look like this: a root LlmAgent does judgment-based triage and can transfer to specialists; one specialist is a SequentialAgent that verifies an account, looks up an order, and drafts a reply; the drafting step is a LoopAgent that revises until a critic is satisfied; and a translation LlmAgent is attached as an AgentTool so any reply can be localized without ceding control.

Seen that way, the decision table is not applied once but at every node of a tree. You ask ‘what is the shape of this sub-problem?’ and drop in the matching type, then ask the same question one level down. The skill is not memorizing four classes — it is developing the reflex to decompose a system into sub-problems each of which has an obvious shape, so each node gets the cheapest type that fits. Systems built this way stay debuggable, because the deterministic scaffolding is code you can read while the model’s judgment is confined to the few places it is genuinely needed.

Custom agent or Workflow agent? Where the line sits

The trickiest boundary in practice is between a Custom agent and the Workflow primitives, because both put control in code. The distinguishing test is whether your control flow needs to inspect intermediate results and branch on them. A SequentialAgent always runs every child in order; a ParallelAgent always runs all children; a LoopAgent repeats until an escalation or a bound. None of them can say ‘run child B only if child A produced a low score,’ or ‘pick one of three sub-pipelines based on a computed value,’ or ‘call an external API and route on its response.’ The moment your orchestration contains an if that depends on runtime state, you have left Workflow territory and entered Custom-agent territory.

The corollary keeps you honest in the other direction: if your flow is just ‘these steps, in this order’ with no data-dependent branching, a Custom agent is over-engineering — you are hand-writing something a SequentialAgent gives you for free, with less code to test and fewer bugs to own. And note a middle path: sometimes the branch is itself a judgment call, in which case the cleaner design is not a Custom agent at all but an LlmAgent making the decision. Reserve the Custom agent for deterministic branching that code should own but no primitive expresses.

Cost, latency, and testability across the four types

The choice is not only about correctness; each type has a different operational profile, and those profiles reinforce the ‘most constrained type that fits’ rule.

TypeModel callsLatency shapeTestability
LlmAgentOne per decision turnVariable; grows with tool loopsHardest — nondeterministic output
SequentialAgentSum of its childrenAdditive across stagesEasy — assert the order and each stage
ParallelAgentSum of its childrenMax of its branches (concurrent)Easy — stub branches, check the merge
LoopAgentChildren × iterationsGrows with iteration countMedium — bound iterations, test exit
Custom (BaseAgent)Whatever you invokeWhatever you writeMedium — unit-test the branch logic
Agent-as-a-ToolParent + nested runAdds the nested call inlineEasy — test the wrapped agent alone

Two takeaways. First, ParallelAgent is the one lever that reduces latency rather than adding to it — when subtasks are truly independent, running them concurrently turns a sum into a max. Second, the coded types are dramatically easier to test because you can drive them with stub sub-agents and assert on the control flow directly, whereas an autonomous LlmAgent demands trajectory-style evals that check the path it took. If a reviewer, a finance team, or your own on-call future self will need to trust this system, that testability gap is a real design input, not an afterthought.

Common mischoices, and the smell that gives each away

Most ADK trouble traces back to a handful of type mismatches. The everything-agent: one giant LlmAgent with twenty tools and a three-page instruction, asked to run a fixed pipeline it rediscovers on every request. The smell is a cost curve that climbs and tool-selection accuracy that falls; the fix is to lift the known structure into a Workflow agent and shrink each LlmAgent’s charter. The premature Custom agent: a hand-written BaseAgent that only ever runs its children in order. The smell is orchestration code with no data-dependent if in it; the fix is to delete it and use a SequentialAgent.

The transfer/tool confusion: using LLM transfer when you wanted an answer back (the specialist ‘captures’ the conversation and never returns), or using AgentTool when you wanted a genuine hand-off (the parent laboriously relays every message from a specialist it should have transferred to). The smell is a parent that either goes silent or turns into a switchboard. And the loop with no bound in mind: reaching for a LoopAgent without a clear exit condition, so refinement either stops too early or never converges. Each mischoice is really the same error — picking a type whose control model does not match who should actually be deciding.

A worked mapping: from problem statement to agent tree

Make it concrete. Suppose the brief is: ‘Given a support email, classify it, and for billing issues pull the customer’s invoices, draft a reply, keep revising the reply until it passes a tone-and-accuracy check, and translate it if the customer wrote in another language.’ Walk the clauses through the decision table.

‘Classify it’ is a judgment call → LlmAgent as the root/router. ‘For billing issues …’ is data-dependent branching → either a transfer to a billing specialist or, if the branch logic is deterministic, a Custom agent; here transfer is natural because the classification is already a model decision. ‘Pull invoices, then draft a reply’ is fixed ordered stages → SequentialAgent. ‘Keep revising until it passes a check’ is iterate-until-good-enough → LoopAgent as one stage of that sequence. ‘Translate it if needed’ is borrowing a specialist’s answer without giving up control → a translation LlmAgent attached as an AgentTool. The finished tree uses all four families, each dropped in where a clause’s shape demanded it — which is exactly what ‘choosing the right agent type’ looks like in practice: not one decision, but one per node, each made with the same table.

ADK gives you four agent families, and the whole game is matching each to the shape of the sub-problem in front of you. Use an LlmAgent where the next step is a genuine judgment call; use a Workflow agent (Sequential for ordered stages, Parallel for independent subtasks, Loop for iterate-until-done) where the structure is known in advance; subclass BaseAgent into a Custom agent only when you need data-dependent branching no primitive expresses; and compose with Agent-as-a-Tool when a parent needs a specialist’s answer but must keep control. The organizing axis is who decides what happens next — the model or your code — and the rule that follows is to pick the most constrained type that still expresses the problem, because determinism, low cost, and testability all come for free the moment a decision lives in code instead of in a model call. Real systems nest all four; apply the decision table once per node and let each part of the tree get the cheapest type that fits.