Google ADK, an agent framework for teams outside Python
Most agent libraries are born in Python and stay there. A team working in Java or TypeScript then chooses between a separate service in another language and writing everything from scratch.
Google ADK ships in five languages in parallel: Python, TypeScript, Go, Java, and Kotlin, with the same conceptual model in each. The first four have stable releases; the Kotlin one is the youngest and still before version 1.0. The framework is open source, and its natural deployment home is Google's infrastructure, though it is not tied to it.
The conceptual model
An agent is a model with instructions and a set of tools. Nothing surprising, and that is how every library in this category works.
pip install google-adkfrom google.adk.agents import Agent
def check_status(number: str) -> dict:
"""Returns the status of the order with the given number.
Call this when a customer asks about their order.
"""
return {"status": db.status(number)}
agent = Agent(
name="support",
model="gemini-2.5-flash",
instruction="You answer questions about orders. No preamble.",
tools=[check_status],
)A tool is an ordinary function whose schema comes from type annotations and the docstring. A sentence saying when to call the function improves accuracy more than a change to the system instruction, and that holds across every agent library.
It gets more interesting at composition. An agent can hold sub agents it delegates to, and the framework ships flow patterns: sequential execution, parallel execution, and looping until a condition holds.
Sub agents and flows
This is the main difference from libraries where everything reduces to one agent with a tool list.
from google.adk.agents import SequentialAgent, ParallelAgent
gathering = ParallelAgent(
name="gathering",
sub_agents=[docs_agent, issues_agent, code_agent],
)
flow = SequentialAgent(
name="analysis",
sub_agents=[gathering, synthesis_agent],
)Parallel execution has a concrete purpose here. Three independent searches run together take as long as the longest rather than as long as their sum. On tasks spanning several sources the difference registers.
Sequential execution describes stages where one result feeds the next. Loop execution repeats a step until a condition holds, which suits refining a result until it satisfies.
Beware of overdoing it, though. Five agents passing a task around is a system nobody will follow, and diagnosing a bad answer means walking five traces. Three roles suffice for most applications.
The agent exchange protocol
The framework natively supports a protocol letting agents from different platforms talk to each other. That answers a problem larger organisations meet.
The situation runs like this: one team built an agent in this framework, another in LangGraph, a third in a cloud vendor's tool. Without a shared protocol, connecting them means writing wrappers for every pair.
The protocol defines how an agent advertises its capabilities and how it accepts tasks from others. A remote agent then looks to your agent like an ordinary tool, whatever it was written in.
Distinguish that from the tool exposure protocol described in the piece on MCP. The latter connects an agent to tools; the former connects agents to each other. Both appear in one system and do not exclude each other.
Within a single team the mechanism is surplus. It earns its keep once parts of a system are built independently and nobody controls them all.
Session state and memory
An agent answering single questions needs nothing beyond its input. An agent holding a conversation must remember what was said, and an agent running a multi stage task must store intermediate results.
The framework separates those into three levels worth distinguishing. Session state holds the current conversation. User state holds durable things, preferences for instance. Application state spans every session and holds shared configuration.
from google.adk.sessions import InMemorySessionService
sessions = InMemorySessionService()
session = await sessions.create_session(app_name="support", user_id="customer-118")The in memory variant suits tests and disappears with the process. Production needs a durable service, available in the managed environment or built yourself over a database.
Think through what belongs in state and what belongs in the application database. Session state is convenient and tempting, so things that ought to live in a normal data model end up there. The rule is simple: state holds what concerns the conversation's course, and the database holds what is a fact about the customer regardless of whether a conversation is running.
Callbacks and flow control
The framework lets you attach your own code before a model call, before a tool call, and after each. It is the middleware equivalent familiar from servers and serves the same purposes.
Three uses recur most. The first is cost limits, where a callback halts work past a threshold. The second is approving irreversible actions, where code pauses the tool call and asks for consent. The third is input filtering, meaning rejecting out of scope requests before the costlier model runs.
That last pattern deserves attention on economic grounds. A cheap check with a light model rejects off topic requests before the agent with tool access starts. You save tokens and shrink the surface where things can go wrong.
Remember that a model based callback is not a safeguard in the systems security sense. It protects against typical misbehaviour rather than against a deliberate attack, so keep enforcing permissions in tool code.
Traces and evaluation
The framework collects a record of each run: model calls, tool calls, handovers between agents. That works locally in the inspection tool and in the cloud environment after deployment.
Diagnosing an agent without a trace comes down to guessing why it did something odd. With a trace you see exactly which tool it called, with what arguments, and what came back. That is the same principle as with LangSmith and other observability tools.
Beyond traces there is an evaluation mechanism over a case set. You define questions with expected results and run them after every instruction change to catch a regression.
Thirty cases suffice to see the difference between variants. Collect them from real traffic, especially from situations where the agent failed, since those recur most. A set built from easy cases passes every time and says nothing about whether a change helped.
Deployment
An agent written in this framework is an ordinary program, so it runs wherever you run everything else: in a container, in a serverless function, on your own server.
The vendor's recommended route is a managed environment in its cloud, built for running agents. It lifts scaling, session maintenance, and trace collection off you at the cost of tying you to one platform.
Separate that decision from the framework choice. The framework itself is open source and needs no such cloud, so you can write an agent in it and deploy it elsewhere. The managed environment's convenience is real and it is not compulsory.
The third route is deploying in a container at any provider. You lose ready session management and gain independence plus the ability to keep data where the rules require. Sessions then have to rest on your own database, which with an existing back end is usually a matter of one table rather than a separate project.
Ready and custom tools
Beyond your own functions the framework ships built in tools and ways to connect tools that live elsewhere.
The built in ones cover web search and code execution in a sandbox. Both run on the vendor's side, so you need not host them, at the cost of some processing happening outside your infrastructure.
The second route connects servers exposing tools through a shared protocol. Ready integrations, with a code repository or an issue tracker for instance, then attach without writing your own wrappers.
The third is using tools from other agent libraries. The framework can wrap a tool written for another library, which helps during migration or when the integration you need exists only there.
The practical order runs against intuition. First check whether the tool already exists in one of those three places. Writing your own wrapper around a programming interface somebody already wrapped is work done twice, and maintenance falls to you.
Mind the count, though. Every connected tool occupies context space and lowers selection accuracy, so attaching everything available worsens the result rather than improving it.
ADK against the alternatives
| Option | Strength | Weakness | Pick it when |
|---|---|---|---|
| Google ADK | Five languages, flow patterns, exchange protocol | Best fitted to one cloud | Team outside Python, deployment at that vendor |
| OpenAI Agents SDK | Few concepts, tracing with no setup | Oriented towards one vendor | A team of roles passing a conversation |
| LangGraph | Full control over flow, durable state | More work on simple cases | Process with branching and pauses |
| PydanticAI | Typed output, tests without a model | Python only | Result feeding straight into code |
For a team working in Java or Go the first row is often the only sensible choice, since the alternatives would mean a separate Python service. That is a real argument invisible in comparisons focused on capability.
For a Python team the choice is wider and worth comparing across several libraries on your own task. The differences concern convenience and ecosystem, since all rest on the same tool calling mechanism model vendors expose.
This is a good moment to name the thing that gets lost in library comparisons. An agent's quality is decided by tool descriptions, the boundaries set in the instruction, and the case set you measure changes against. Those three carry across libraries almost unchanged, so the work put into them does not perish in a migration.
The framework choice affects something else: how easily an agent slots into an existing system and what deployment looks like. For a team working in Java and deploying with that vendor the difference registers from day one, and for a Python team with its own infrastructure it is considerably smaller.
Common mistakes
The first is tool descriptions written for a developer. The model reads the same text and needs a sentence about when to call the function, not a description of the implementation.
The second is too many sub agents. Five levels of nesting yields a system where diagnosing a bad answer means walking every trace in turn.
The third is no step cap. An agent calling tools in a loop burns as many tokens as you allow, and on a top tier model that gets expensive.
The fourth is passing a user identifier in tool arguments. The model can supply somebody else's, so identity should come from run context.
The fifth is deploying with no case set. An instruction change improving one conversation usually breaks another, and without a set nobody notices.
The sixth is reaching for the agent exchange protocol within one team. It solves collaboration between independent teams and, on a single codebase, adds complexity with no benefit.
FAQ
Which languages does ADK support?
Python, TypeScript, Go, Java, and Kotlin, with the same conceptual model in each and a separate set of documentation pages for every one of them. That is its main distinction from libraries available in Python alone, since it lets a team work in the language the rest of the system already uses. Just check the release number of the variant you pick, since the Kotlin one arrived last and sits before version 1.0.
Does ADK require Google's cloud?
No, the framework is open source and an agent runs in a container or a serverless function at any provider. The vendor's managed environment simplifies scaling and trace collection but is a choice rather than a condition.
How does the A2A protocol differ from MCP?
The protocol described in the piece on MCP connects an agent to tools and data sources. The agent exchange protocol connects agents to each other, including ones written in other frameworks. Both appear in one system.
Does it work with models other than Gemini?
Yes, the model layer supports various vendors, though integration with this vendor's models runs deepest. When comparing, run your own cases through several models, since differences depend on the task more than on benchmarks.
When should I pick something else?
When you work in Python and want typed output or tests without calling a model, since better fitted libraries exist for that. When you need full control over a flow with pauses for human decisions, a graph based solution is the right direction.
Documentation sits on the project site, and the exchange protocol support in a separate chapter.