Skip to content

Quickstart: Consuming a remote agent via A2A

Supported in ADKKotlinExperimental

This quickstart covers the most common starting point for any developer: "There is a remote agent, how do I let my ADK agent use it via A2A?". This is crucial for building complex multi-agent systems where different agents need to collaborate and interact.

Overview

This sample demonstrates the Agent2Agent (A2A) architecture in the Agent Development Kit (ADK) for Kotlin, showing how a local agent delegates part of a task to an agent running elsewhere.

┌─────────────────┐         ┌────────────────────────┐
│   Root Agent    │────────▶│   Remote Prime Agent   │
│   (Local)       │◀────────│   (localhost:9090)     │
└─────────────────┘         └────────────────────────┘
  • Root Agent (root_agent): The local orchestrator that delegates to sub-agents
  • Prime Agent (prime_agent): A remote A2A agent that checks whether a number is prime, running on a separate A2A server

Add the A2A dependency

A2A support ships in a separate artifact. The A2A SDK client is needed on the compile classpath as well, because A2AAgent's httpClient parameter defaults to JdkA2AHttpClient():

build.gradle.kts
implementation("com.google.adk:google-adk-kotlin-a2a:0.7.0")
implementation("org.a2aproject.sdk:a2a-java-sdk-client:1.0.0.Final")

Start a remote agent server

To consume a remote agent you first need one running. adk-kotlin cannot expose an agent over A2A yet, so the server has to come from elsewhere — A2A is a wire protocol, so any language will do.

The A2A protocol requires each agent to publish an agent card describing what it does, served at the well-known path:

http://localhost:9090/.well-known/agent-card.json

The Kotlin client reads A2A 1.0 cards, so the card must carry a supportedInterfaces array whose entries each have a protocolBinding. Cards written for A2A 0.3 omit it, and A2AAgent rejects them with AgentCardResolutionError: Failed to parse agent card. That includes the a2a_server sample in adk-java and the remote_a2a sample in adk-python as they stand today, so neither works as the server for this page yet.

A minimal card the client accepts looks like this:

.well-known/agent-card.json
{
  "name": "check_prime_agent",
  "description": "Checks whether numbers are prime.",
  "version": "1.0.0",
  "url": "http://localhost:9090",
  "preferredTransport": "JSONRPC",
  "capabilities": { "streaming": true },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["application/json"],
  "skills": [],
  "supportedInterfaces": [
    { "protocolBinding": "JSONRPC", "url": "http://localhost:9090" }
  ]
}

Check the card is reachable before you continue:

curl http://localhost:9090/.well-known/agent-card.json

Connect to the remote agent

A2AAgent fetches that card and reads the remote agent's description from it, along with whether the remote supports streaming. The name you pass is this agent's identifier in your own agent tree, independent of the name the card advertises. It is a suspending function, so call it from a coroutine:

A2AConsumer.kt
// A2AAgent is a suspending factory: it fetches the remote agent's card from
// <url>/.well-known/agent-card.json and takes the description and streaming
// capability from it. The name is yours -- it identifies this agent in your
// tree, independent of the name the card advertises. The constructor of the
// returned agent is internal, so this factory is the only way to build one.
val primeAgent =
    A2AAgent(
        name = "prime_agent",
        agentCardUrl = "http://localhost:9090",
    )

If you already hold an AgentCard — for example one you resolved yourself, or a static card checked into your configuration — there is a non-suspending overload that takes it directly, A2AAgent(name = ..., agentCard = ...).

Use it as a sub-agent

The returned agent is a BaseAgent, so it goes into subAgents exactly like a local one. ADK handles the A2A protocol over the wire:

A2AConsumer.kt
// The remote agent is a BaseAgent, so it goes in subAgents like any local one.
// ADK handles the A2A wire protocol from here.
val rootAgent =
    LlmAgent(
        name = "root_agent",
        model = Gemini(name = "gemini-flash-latest"),
        instruction =
            Instruction(
                "You are a helpful assistant that can check prime numbers " +
                    "by delegating to prime_agent.",
            ),
        subAgents = listOf(primeAgent),
    )

Next Steps

Exposing a Kotlin agent over A2A is not yet supported; adk-kotlin currently provides the consuming side only. To expose an agent, see the quickstarts for the other languages: