n4nAI

A2A task lifecycle: states, artifacts, and streaming

Defines the a2a task lifecycle: the state machine, artifacts, and streaming model that let autonomous agents coordinate work reliably over the A2A protocol.

n4n Team5 min read1,064 words

Audio narration

Coming soon — every post will get a voice note here.

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. submittedworking → (input-requiredworking) → 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-required lets 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.

  1. Planner sends submitted task with query.
  2. Research agent moves to working, streams progress.
  3. It hits a paywalled source and emits input-required with a message asking for an API key.
  4. Planner supplies the key; task returns to working.
  5. Agent produces an artifact report.pdf and transitions to completed.
# 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.

Tagsa2atask-lifecyclestreamingdefinition

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All agent-to-agent (a2a) communication protocols posts →