The a2a task lifecycle is the formal state machine and data contract that governs how autonomous agents submit, progress, and resolve units of work under the Agent-to-Agent (A2A) protocol. It specifies explicit task states, the artifacts produced as outputs, and how incremental updates stream between peers so that neither side has to guess whether a job is done.
What the a2a task lifecycle specifies
At its core, the a2a task lifecycle separates the control plane from the data plane. The control plane is the task object: an identifier, a state, timestamps, and metadata. The data plane carries messages and artifacts.
A task is not a function call. It is a long-lived coordination primitive that can span minutes, require human input, or fail partially. The protocol borrows from job scheduling but adds first-class streaming and artifact handling so agents can collaborate without tight coupling.
Core states and transitions
A2A defines a small set of terminal and non-terminal states. The enum is fixed; agents extend it only through metadata, not by inventing new states.
| State | Terminal? | Meaning |
|---|---|---|
submitted |
No | Task accepted by remote agent, not yet processed |
working |
No | Agent actively processing |
input-required |
No | Agent needs additional data from caller |
completed |
Yes | Final artifacts produced successfully |
failed |
Yes | Unrecoverable error |
canceled |
Yes | Caller aborted |
Transitions follow a directed graph. submitted → working → (input-required ⇄ working) → completed. Any non-terminal state can go to failed or canceled.
{
"taskId": "t-9f3a",
"state": "working",
"createdAt": "2025-04-12T10:22:01Z",
"updatedAt": "2025-04-12T10:22:14Z",
"metadata": { "delegate": "search-agent" }
}
Non-terminal state handling
A caller must treat input-required as a pause, not a failure. The original task id is reused when sending supplementary input. working may repeat after input-required; the caller should merge streamed artifacts rather than replace local state.
If a canceled or failed event arrives after working, the caller must stop processing further chunks and roll back any partial side effects where possible. The lifecycle does not imply distributed transactions—your agent owns compensation logic.
Artifacts versus messages
A common point of confusion: A2A messages are conversational turns; artifacts are durable outputs attached to the task. Messages may contain text or structured data for negotiation. Artifacts are the deliverables—a generated report, a CSV file, a diff.
Artifacts are referenced by URI or inline payload and are immutable once the task reaches a terminal state. They can be chunked during streaming to avoid large payloads.
{
"taskId": "t-9f3a",
"artifacts": [
{
"artifactId": "a-1",
"name": "summary.md",
"mimeType": "text/markdown",
"content": "# Findings\n- Latency p99 increased 30%"
}
]
}
Artifact chunking
For large outputs, the agent sends artifact-update events with chunk indices. The caller reassembles by artifactId. Missing chunks should trigger a get-artifact request, not a task retry.
An agent that only sends messages but never produces artifacts forces the caller to parse conversation history. That breaks composability. Emit artifacts.
Streaming updates
The a2a task lifecycle is built for asynchronous work. Polling is allowed but wasteful. The protocol supports server-sent events (SSE) or websocket channels where the remote agent pushes state changes and artifact chunks.
A minimal SSE stream looks like:
event: task-status
data: {"taskId":"t-9f3a","state":"working"}
event: artifact-update
data: {"taskId":"t-9f3a","artifactId":"a-1","chunk":0,"content":"# Findings\n"}
event: task-status
data: {"taskId":"t-9f3a","state":"completed"}
In Python, a client can consume this with standard libraries:
import requests
url = "https://agent.example/a2a/tasks/t-9f3a/stream"
with requests.get(url, stream=True) as r:
for line in r.iter_lines():
if line.startswith(b"data:"):
print(line[5:].decode()) # parse JSON, update local state
Client responsibilities
The caller must handle three realities: events may arrive out of order, a stream may close prematurely, and a task may already be terminal when the stream connects. Always reconcile against a get-task baseline before applying deltas.
Streaming matters because agents often run LLM inference that itself streams tokens. Propagating those chunks preserves responsiveness without inventing a new protocol per model.
Why the lifecycle matters
Without a shared a2a task lifecycle, multi-agent systems degenerate into bespoke HTTP calls with ad-hoc “done” flags. You get stuck debugging whether a 200 response meant success or just acceptance.
The lifecycle gives you:
- Cancellation: callers can abort wasted work.
- Resumability:
input-requiredlets a task pause for credentials or clarification. - Observability: state transitions are explicit events you can log and alert on.
- Backpressure: streaming lets the caller throttle or disconnect early.
Resilience patterns
Engineers should code defensively around state transitions. Assume you may receive a failed event after working. Assume canceled can arrive mid-stream. Idempotency keys on task ids prevent duplicate work if the caller retries.
When agents themselves call LLMs, they benefit from an inference layer that matches the same resilience. For example, an OpenAI-compatible gateway such as n4n.ai fronts 240+ models with automatic fallback when a provider is degraded, letting your agent focus on task state rather than model outages. That separation keeps the a2a task lifecycle clean: the agent handles protocol, the gateway handles inference variability.
Concrete example: delegated research
Suppose a planner agent needs competitive analysis. It opens a task with a research agent.
- Planner sends
submittedtask with query. - Research agent moves to
working, streams progress. - It hits a paywalled source and emits
input-requiredwith a message asking for an API key. - Planner supplies the key; task returns to
working. - Agent produces an artifact
report.pdfand transitions tocompleted.
# planner side (simplified)
task = client.create_task(agent="research", input="Analyze ACME pricing")
for event in client.stream(task.id):
if event.state == "input-required":
client.send_input(task.id, {"api_key": vault.get("x")})
if event.state == "completed":
report = client.get_artifact(task.id, "report.pdf")
The wire exchange looks like:
{"taskId":"t-9f3a","state":"submitted"}
{"taskId":"t-9f3a","state":"working"}
{"taskId":"t-9f3a","state":"input-required","message":"need api_key"}
{"taskId":"t-9f3a","state":"working"}
{"taskId":"t-9f3a","artifactId":"a-1","chunk":0,"content":"%PDF-1.4..."}
{"taskId":"t-9f3a","state":"completed"}
This flow is impossible to express cleanly with plain request/response. The a2a task lifecycle makes the pause explicit and the artifact durable.
Common misconceptions
“A2A is just RPC.” Wrong. RPC assumes short-lived request/response. The a2a task lifecycle assumes the remote may go offline, ask questions, and stream partial results.
“Artifacts are optional.” They are the point. If you return only messages, you force every caller to implement a parser for your conversation style.
“Streaming is a nice-to-have.” For long LLM jobs, streaming is the only way to keep the caller alive and provide cancellation. Non-streaming A2A is a degraded mode.
“Each provider invents its own states.” The protocol fixes the state enum. Custom metadata is allowed, but the core lifecycle is shared.
“Once submitted, the task is immutable.” Inputs can be appended during input-required. Artifacts are append-only until terminal.
“The caller can ignore state and just wait for artifacts.” Ignoring failed or canceled leads to hanging clients and leaked compute. The state machine is the contract.
Implementing against the lifecycle
Test with a fake agent that randomly emits input-required and failed. If your client handles those without crashing, your lifecycle implementation is real.
Keep task ids opaque. Never encode state in the id. Use metadata for routing hints, but do not assume the remote honors them—A2A allows ignoring unknown metadata.
For artifact assembly, maintain a dict keyed by artifactId with a list of chunks. On terminal state, validate that all expected chunks arrived; if not, issue a targeted get-artifact rather than recreating the task.
The a2a task lifecycle is the contract that turns agent collaboration from fragile chat into reliable distributed work. Learn the states, emit artifacts, stream updates, and stop treating agents like functions. Everything else is optimization.