Agent Card
A service profile for an agent
A small public document tells callers the agent’s name, endpoint, skills, protocol version, security requirements, and whether it supports streaming or notifications.
A2A gives an AI agent a standard way to introduce itself to another agent, accept a job or task, report progress, ask for more information, and return useful results—even when the other agent runs in a different AI service, uses a different programming language or agent framework, or is deployed in another cloud. Neither agent needs to understand how the other is built. They use the A2A standard to communicate and work together.
ADK-Rust A2A reference architecture
The network contract stays separate from the agent's model, tools, memory, and internal orchestration.
01
The agent or application asking another agent to do work.
Decides which remote specialist should receive the task.
Discovers the card, chooses streaming, and sends protocol messages.
02
The public contract every compatible client can understand.
Name, skills, endpoint, protocol version, security, and declared capabilities.
Version, content type, IDs, message parts, metadata, identity, and limits.
Dispatches JSON-RPC or REST operations through one shared implementation.
03
The framework executes the task and produces typed progress.
Creates the invocation context and streams agent events.
LLM, workflow, graph, custom, or multi-agent logic.
Rust tools and MCP
Sessions and memory
04
A validated state machine makes long-running work inspectable.
Status, context, history, artifacts, metadata, and push configuration.
Only valid transitions are accepted; terminal tasks stay terminal.
05
The caller chooses how it wants to receive change.
Return the complete task and its artifacts.
Task first, then status and artifact updates.
06
Long tasks can report later without holding one connection open.
A client listens to updates for an existing task.
Authenticated delivery with retry and SSRF checks.
Start with the idea
Inside one ADK-Rust process, agents can share Rust types, sessions, tools, and direct function calls. Once an agent belongs to another service—or another organisation—those private implementation details are no longer a safe contract.
A2A defines the shared language at that boundary. The caller discovers what the remote agent offers, sends a structured message, follows a task through known states, and receives artifacts without needing to know which model, framework, or tools run behind the endpoint.
ADK-Rust implements A2A v1 in adk-server and connects the protocol directly to the Runner. The same agent you test locally can become a network service, while RemoteA2aAgent lets another ADK-Rust system consume it through the normal Agent interface.
Agent Card
A small public document tells callers the agent’s name, endpoint, skills, protocol version, security requirements, and whether it supports streaming or notifications.
Message
A message carries a role, a unique ID, and one or more parts such as text or structured data. The unique ID makes safe retries possible.
Task
A task gives remote work an ID, a conversation context, a state, message history, metadata, and any artifacts produced along the way.
Artifact
An artifact is a named result attached to the task: a report, decision, plan, generated file, or other structured output another system can use.
Follow one job
A caller can wait for one response, open a live event stream, subscribe to an existing task, or register a webhook for later updates. Task identity and context keep every delivery mode attached to the same piece of work.
Task state machine
A2A does more than return text. It gives work an identity, a conversation context, a current state, history, and artifacts. ADK-Rust validates every transition before saving it.
SUBMITTEDThe server accepted the task.
WORKINGThe agent is executing it.
INPUT REQUIREDThe agent needs more information.
AUTH REQUIREDThe caller must provide authorization.
COMPLETEDWork and artifacts are ready.
FAILEDExecution ended with an error.
CANCELEDAn allowed cancellation ended the task.
REJECTEDThe server declined the task.
INPUT_REQUIRED, the caller sends another message with the same contextId. ADK-Rust finds the task, returns it to WORKING, appends the message, and continues the same job.Real-world logic flow
A customer tells the company's support agent that an order arrived damaged. The support agent remains responsible for the conversation: it understands the request, keeps the customer informed, collects any missing evidence, and presents the final decision. It does not have direct access to warehouse stock, delivery records, or replacement policy.
That operational information belongs to a separately deployed fulfilment agent. Through A2A, the support agent can discover what the fulfilment agent does, send it the investigation as a task, follow its progress, answer requests for more information, and receive the final replacement decision. Each service keeps its own code, data, tools, model provider, and security controls; they share only the messages, task state, and artifacts defined by the A2A contract.
A support coordinator reads the fulfilment agent’s card and confirms it handles order investigation and streams progress.
It sends the order number, the customer’s request, a unique messageId, and a new conversation context.
The remote ADK-Rust agent checks order tools, inventory, delivery history, and company policy without exposing those internals to the caller.
If proof of damage is required, the task becomes INPUT_REQUIRED instead of guessing or silently failing.
The coordinator supplies the approved evidence using the same contextId; history and task identity remain intact.
The task completes with status history and an artifact containing the replacement decision and next steps.
Sequence diagram
Read from top to bottom. The A2A service owns the network contract and task record. The fulfilment agent owns its reasoning and private tools.
Calling agent
Protocol boundary
ADK-Rust Runner
Orders · inventory · policy
GET /.well-known/agent-card.json
Discover skills and streaming
Agent Card
Fulfilment capability published
SendStreamingMessage
ORD-1042 · messageId msg-order-1042
Runner invocation
Create task and execute agent
order.lookup + policy.check
Use private business tools
Evidence required
Damage photo is missing
INPUT_REQUIRED
Persist state and context
SSE status update
Ask support for evidence
SendMessage · same contextId
Approved photo reference
Resume task
INPUT_REQUIRED → WORKING
inventory.reserve
Reserve replacement stock
Artifact + COMPLETED
Replacement decision
Final SSE events
Auditable result returned
Code and wire responses
These examples use the same v1 operation names, message fields, task states, and SSE response shapes as the ADK-Rust implementation. IDs and order details are fixed so the whole conversation is easy to follow.
let fulfilment = LlmAgentBuilder::new("fulfilment")
.description("Investigates orders and delivery")
.model(model)
.instruction(
"Use the approved order, inventory, and policy tools. Ask for evidence when the replacement policy requires it."
)
.build()?;
A2aServer::builder()
.agent(Arc::new(fulfilment))
.agent_card_name("Fulfilment Agent")
.streaming(true)
.build()?
.serve()
.await?;let fulfilment = RemoteA2aAgent::builder("fulfilment")
.description("Investigates orders and delivery")
.agent_url("https://agents.example.com/fulfilment")
.streaming(true)
.build()?;
let support = LlmAgentBuilder::new("support")
.model(model)
.instruction("Own the customer conversation.")
.sub_agent(Arc::new(fulfilment))
.build()?;{
"jsonrpc": "2.0",
"id": "replace-1",
"method": "SendStreamingMessage",
"params": {
"message": {
"messageId": "msg-order-1042",
"role": "ROLE_USER",
"parts": [{
"text": "Investigate damaged order ORD-1042 and prepare a replacement decision."
}]
}
}
}data: {
"jsonrpc":"2.0", "id":"replace-1",
"result":{"task":{
"id":"task-order-1042",
"contextId":"ctx-order-1042",
"status":{"state":"TASK_STATE_SUBMITTED"}
}}
}
data: {
"jsonrpc":"2.0", "id":"replace-1",
"result":{"statusUpdate":{
"taskId":"task-order-1042",
"contextId":"ctx-order-1042",
"status":{"state":"TASK_STATE_WORKING"}
}}
}{
"statusUpdate": {
"taskId": "task-order-1042",
"contextId": "ctx-order-1042",
"status": {
"state": "TASK_STATE_INPUT_REQUIRED",
"message": {
"role": "ROLE_AGENT",
"parts": [{
"text": "Please provide a photo showing the damaged item."
}]
}
}
}
}{
"jsonrpc": "2.0",
"id": "replace-2",
"method": "SendMessage",
"params": {
"message": {
"messageId": "msg-order-1042-photo",
"contextId": "ctx-order-1042",
"role": "ROLE_USER",
"parts": [{
"text": "Evidence received and approved: damage-photo-8841.jpg"
}]
}
}
}{
"artifactUpdate": {
"taskId": "task-order-1042",
"contextId": "ctx-order-1042",
"artifact": {
"artifactId": "replacement-decision-1042",
"name": "Replacement decision",
"parts": [{
"text": "Approved. Reserve SKU-RED-42 and dispatch with priority shipping."
}]
}
}
}
{
"statusUpdate": {
"taskId": "task-order-1042",
"contextId": "ctx-order-1042",
"status": { "state": "TASK_STATE_COMPLETED" }
}
}The A2A v1 API
ADK-Rust implements all 11 operations in A2A v1. For a short job, call SendMessage and wait for the task result. When the work may take longer, use SendStreamingMessage to receive the task first, followed by live status and artifact events over SSE.
The returned task ID lets your client reconnect without submitting the job again. It can fetch the latest state, list related tasks, subscribe to further updates, or request cancellation. If the client cannot keep a connection open, it can register an authenticated webhook and let the remote agent deliver later changes.
Send a message and receive either a complete task or a live stream.
SendMessageSendStreamingMessageRead current work, list matching tasks, or cancel a task when its state permits it.
GetTaskListTasksCancelTaskOpen an SSE subscription for updates from a task that already exists.
SubscribeToTaskCreate, read, list, and remove authenticated webhook destinations for a task.
CreatePushConfigGetPushConfigListPushConfigsDeletePushConfigRequest the extended version of an agent’s published service profile.
GetExtendedAgentCardJSON-RPC and REST routes dispatch into the same RequestHandler, so task behavior does not drift between transports.
Incoming A2A messages become ADK-Rust content. The Runner executes the selected agent and converts its events into task status and artifacts.
A repeated messageId returns the existing task rather than running the same work twice. Production deployments should persist this mapping across restarts.
The first SSE event is the complete Task. Later events carry status and artifact updates, so a client always knows what the stream belongs to.
RemoteA2aAgent implements the ADK-Rust Agent trait. A coordinator can include a network service in its agent hierarchy and receive normal framework events.
Version negotiation, message validation, bearer authentication, rate limiting, audit interceptors, webhook authentication, and SSRF checks can guard the service.
Choose the right boundary
A2A introduces a network, a public contract, authentication, failure handling, and remote task state. Those costs are valuable when they create real independence; they are unnecessary when every agent already lives in the same process.
Local sub-agent
Remote A2A agent
From Rust agent to network service
The convenience server creates an Axum application with an agent card, sessions, A2A routes, and streaming enabled. The remote adapter discovers that service and converts its task updates back into ADK-Rust events.
use adk_server::a2a::convenience::A2aServer;
let app = A2aServer::quick_start(agent);
let listener = tokio::net::TcpListener::bind(
"0.0.0.0:8080"
).await?;
axum::serve(listener, app).await?;use adk_server::a2a::RemoteA2aAgent;
let fulfilment = RemoteA2aAgent::builder("fulfilment")
.description("Investigates orders and delivery")
.agent_url("https://agents.example.com/fulfilment")
.build()?;
let coordinator = LlmAgentBuilder::new("support")
.model(model)
.sub_agent(Arc::new(fulfilment))
.build()?;RequestHandler::with_runner path to choose the task store, push sender, Agent Card, Runner services, JSON-RPC route, REST router, and version-negotiation middleware explicitly.Before production
ADK-Rust supplies the v1 types, handlers, lifecycle, clients, validation, delivery primitives, and integration with the Runner. Production readiness still depends on the storage, identity, network, observability, and operating policies selected for your service.
Advertise only the skills, bindings, streaming, push delivery, and security schemes the deployment really supports.
The included InMemoryTaskStore and in-memory idempotency map are useful for development. Long-running production work needs storage that survives process restarts.
Authenticate inbound calls and protect outbound push delivery. The HTTP push sender supports bearer credentials, notification tokens, retries, and private-address rejection.
Remote work can outlive a request. Define client timeouts, cancellation policy, retry behavior, and what happens when either service becomes unavailable.
Connect the agent system
Begin with one honest Agent Card and one useful task. Add streaming, multi-turn input, task persistence, subscriptions, authenticated push, and boundary controls as the work becomes longer and the service becomes more important.