Handing Ticket Analysis to a Specialist Agent with A2A: A Complete Task Handoff
A support system has two agents: a coordinator agent facing the user, deciding “who should look at this ticket”; and a ticket-analysis agent, a specialist at finding root causes and proposing fixes. The handoff between them is A2A. Where the ticket data lives, which tools get called, and how permissions are granted happen inside the specialist (that side is MCP — see the boundary at the end); A2A governs what happens between the two agents: discovery, task state, messages, artifacts, and recovery.
Prerequisites: state and message semantics in the Task runtime guide , idempotency and recovery patterns in Task idempotency and recovery . This page walks them through one scenario.
Walkthrough overview
sequenceDiagram
participant U as User
participant C as Coordinator (A2A client)
participant S as Ticket analyst (A2A server)
U->>C: "What is wrong with this ticket?"
C->>S: fetch /.well-known/agent-card.json (discovery)
C->>S: message/send (analysis request, messageId=m1)
S-->>C: Task(SUBMITTED)
S-->>C: Task(WORKING)
S-->>C: Task(INPUT_REQUIRED): missing reproduction steps
C->>S: message/send (supplement, messageId=m2, same contextId)
S-->>C: Task(WORKING)
S-->>C: Artifact: structured report (chunked, lastChunk)
S-->>C: Task(COMPLETED) + concluding Message
C-->>U: Root cause and recommendation
1. Discovery: select by skill, not by name
The coordinator reads /.well-known/agent-card.json and decides on three things:
| What to check | The judgment in this scenario |
|---|---|
skills | Is there a ticket / root-cause-analysis skill? A skill’s tags are more reliable than its name |
capabilities | At least one of streaming or pushNotifications is true, so a long analysis is not a blind wait |
securitySchemes / securityRequirements | Do we hold credentials matching what the agent requires? |
Discovery mechanics and card fields are in Agent discovery and the Agent Card implementation guide . The coordinator should cache the card and honor its cache window instead of rediscovering on every handoff.
2. Starting: put the idempotency key in the first message
{
"jsonrpc": "2.0",
"id": "req-101",
"method": "message/send",
"params": {
"message": {
"role": "role_user",
"parts": [{ "kind": "text", "text": "Analyze ticket TCK-4821: customer reports order status not updating after checkout" }],
"messageId": "coord-4f21-m1",
"contextId": "ctx-tck4821"
}
}
}Two decisions are made now and never changed:
messageIdembeds the upstream identifier (the ticket number): on a timeout, resend verbatim with the same value; the server, with(contextId), recognizes the duplicate and does not re-run the analysis;contextIdis named after the ticket: every later task for this ticket (re-analysis, supplements, escalation) shares one context, keeping history and ownership traceable.
The server returns a Task (SUBMITTED or WORKING); record the taskId — every later operation targets it.
3. Tracking: each state maps to one coordinator action
| Task state | What the coordinator does |
|---|---|
SUBMITTED / WORKING | Wait; receive passively if subscribed to the stream or push, otherwise GetTask with backoff |
INPUT_REQUIRED | Ask the user for information, then send it back to the same task as a new messageId |
AUTH_REQUIRED | Complete the authorization the status message describes (approver or out-of-band), per section 4 of Security implementation |
COMPLETED | Read the artifacts and the final Message; hand them to the user |
FAILED / CANCELED / REJECTED | Decide by reason: retry (new task, same contextId), switch agents, or escalate |
The scenario pauses once at INPUT_REQUIRED: the analyst needs reproduction steps. The coordinator fetches them from the user and continues on the same task:
{
"jsonrpc": "2.0",
"id": "req-102",
"method": "message/send",
"params": {
"message": {
"role": "role_user",
"parts": [{ "kind": "text", "text": "Reproduction: refresh the order page twice within 30 s of placing the order" }],
"messageId": "coord-4f21-m2",
"taskId": "task-9c31",
"contextId": "ctx-tck4821"
}
}
}The messageId is new (this is new input, not a resend); taskId and contextId are unchanged.
4. Receiving: the report is an Artifact, the conclusion is a Message
The result arrives in two layers:
- Artifact (structured report): root cause, confidence, affected-order list, recommended actions. Streamed in chunks ending with
lastChunk: true; the coordinator aggregates byartifactIdand assumes nothing about chunk counts; - Message (final statement): a natural-language conclusion the coordinator relays to the user.
{
"jsonrpc": "2.0",
"id": "req-103",
"method": "tasks/get",
"params": { "taskId": "task-9c31" }
}The report in Task.artifacts together with the conclusion in Task.status.message forms the complete delivery. Field details are on the Artifact object page
and the Message object page
.
5. Recovery: the coordinator’s checklist
Any step can break; check item by item:
| Observation | Action |
|---|---|
message/send timed out; delivery unknown | Resend verbatim (same messageId); the server returns the first result idempotently |
| Event stream dropped mid-way | Reconnect with SubscribeToTask; when resume is unsupported, deduplicate repeated events by sequence |
| Polling only | GetTask with backoff until terminal; a dropped stream is not a failed task |
WORKING past the ticket’s time limit | CancelTask, then start a new task under the same contextId (terminal tasks cannot restart) |
| More authorization needed along the chain | Move your own task to AUTH_REQUIRED and delegate upward; bind credentials to the originator |
| Analyst unreachable | Retry once on another interface from the cached card; if it still fails, switch agents per discovery and record the reason in a new contextId |
The full recovery decision tree and probes are in Task idempotency and recovery ; hardening the push channel is in Push receiver hardening .
Boundary: the MCP side
Internally, the ticket-analysis agent uses MCP to read ticket-system data, call controlled tools (refund lookup, order-status API), and map permission failures to A2A’s AUTH_REQUIRED or FAILED. That is MCP’s side of the boundary — MCP Servers/Clients, tools and resources, least privilege, call failures and observable results — covered in the corresponding practice page on the MCP site
. The division between the two protocols is summarized in A2A and MCP
.
Related pages
- Task runtime guide : state rules and event-stream validation
- Task idempotency and recovery : the idempotency and recovery patterns referenced here
- Agent discovery : card discovery mechanics
- A2A and MCP compared : the boundary between the two protocols
Version and verification notes
Aligned with A2A v1.0.1. Message, task, and artifact fields follow the data structures
; the sequence diagram and checklist are composed of this scenario’s message flow and can serve directly as an acceptance checklist. The messageId and contextId value patterns are this site’s recommendations; the protocol requires only that the sender generates them.
Sources: A2A data structures , Life of a task .