CancelTask and terminal states
Four terminal states end a task, the agent chooses three of them and CancelTask asks for the fourth, and no later call moves a task out of one.
The planner asks test-runner to run the unit tests at commit 9f3c2e1. One test fails, and the task comes back in TASK_STATE_COMPLETED. A planner that reads only the state deploys broken code, and a planner that reads the artifact stops the release.
When you finish this section, you can tell the four endings apart, call CancelTask, and predict what a finished task answers.
Terminal states
Terminal state: one of the four states that end a task: TASK_STATE_COMPLETED, TASK_STATE_FAILED, TASK_STATE_CANCELED, and TASK_STATE_REJECTED. The proto marks each one as terminal.
A terminal task never changes again: once it is completed, canceled, rejected, or failed, it cannot restart. Figure 4.6 sets the four endings side by side.
The agent's three endings
Task 3d8f7801 ran the suite at 9f3c2e1. One of twelve tests failed, and the task still ended in TASK_STATE_COMPLETED:
"task": {
"id": "3d8f7801-f421-4e4e-aa2d-8b6da17d9281",
…
"state": "TASK_STATE_COMPLETED",
…
"text": "1 failed, 11 passed"
…
"artifactId": "summary",
"name": "summary.json",
"parts": [
{
"data": {
"passed": 11,
"failed": 1,Completed means that a task finished successfully, and the job was to run the suite. Whether the code is good is a result, and results travel in artifacts. The kit's planner reads the failed count in summary.json before a deploy.
Commit deadbee does not exist, so task 8ae21dcb ended in TASK_STATE_FAILED, which means that the task finished with an error. The same request fails the same way, so fix the input and start a new task. If the agent crashes, the reference SDK stores the task as failed, yet a blocking send gets an error. Treat that error as an unknown outcome.
The planner asked deployer for a production deploy. Task 23cf3710 went straight to TASK_STATE_REJECTED, which means that the agent decided not to do the task. A loop that stops only on completed, failed, and canceled polls a rejected task forever. The same request gets the same refusal, so follow the status message: "Production deploys are outside this agent's policy. Use the release train."
CancelTask
CancelTask: the operation that asks the server to stop a task, given its id. Task f22ed7af was running the full suite when the planner called it. The reply was the task in TASK_STATE_CANCELED, with the two log chunks it had already written.
A cancel is a request: the server tries to cancel the task, but success is not guaranteed. The specification does not say which unfinished states a server must cancel, and the reference SDK cancels any task that is not terminal.
A second CancelTask gets TaskNotCancelableError, -32002, from the kit and the reference SDK, as §3.1.5 lists for a finished task. Section 3.3.1 disagrees: it calls Cancel Task idempotent, so several cancel requests have the same effect. A repeat on a deleted task is allowed to return TaskNotFoundError.
So after a cancel that errors, call GetTask. Canceled means an earlier cancel worked, another terminal state means the task ended first, and running means the server cannot stop it.
Calls on a finished task
A finished task still answers. GetTask returns the final task. SendMessage with its taskId gets UnsupportedOperationError, -32004, and SubscribeToTask gets the same error. CancelTask gets TaskNotCancelableError. UnsupportedOperationError also covers unrelated failures, so read its details, as Errors explains.
The official SDKs send the same error, with two differences in the other bindings:
| SDK | JSON-RPC | HTTP+JSON | gRPC |
|---|---|---|---|
| a2a-python 1.2.2 | -32004 | 400 | FAILED_PRECONDITION |
| a2a-go | -32004 | 400 | FAILED_PRECONDITION |
| a2a-java | -32004 | 400 | UNIMPLEMENTED |
| a2a-js | -32004 | 400 | FAILED_PRECONDITION |
| a2a-dotnet | -32004 | 400, a problem-details body | none at this release |
| a2a-rs | -32004 | 400 | FAILED_PRECONDITION |
a2a-java answers gRPC with UNIMPLEMENTED where §5.4 maps the error to FAILED_PRECONDITION. a2a-go in its experimental cluster mode answers InvalidParamsError, -32602, instead.
To go on, start a new task in the same context, naming the finished one in referenceTaskIds, as contextId shows.
A2A protocol 1.0.1 · 3303592 · 2026-05-28 · Edition 2026.10