4.4

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.

Fig. 4.6four ways a task endscomparison
Four ways a task ends Four cards, one per terminal state from the kit. Completed: test-runner task 3d8f7801, chosen by the agent because the suite ran, status message 1 failed, 11 passed, with test-log.txt and summary.json. Failed: task 8ae21dcb, chosen by the agent because commit deadbee does not exist, no artifacts. Canceled: task f22ed7af, chosen by the client with CancelTask, two log chunks kept. Rejected: deployer task 23cf3710, refused by policy, no artifacts. Below, a message to the canceled task gets UnsupportedOperationError and a second cancel gets TaskNotCancelableError. FOUR TERMINAL STATES FROM THE KIT TASK_STATE_COMPLETED test-runner, task 3d8f7801 chosen by the agent: the suite ran status message 1 failed, 11 passed artifacts: test-log.txt, summary.json TASK_STATE_FAILED test-runner, task 8ae21dcb chosen by the agent: no such commit status message Commit deadbee does not exist in payments-api. artifacts: none TASK_STATE_CANCELED test-runner, task f22ed7af chosen by the client: CancelTask status message Canceled at the client's request. artifacts: test-log.txt, two chunks TASK_STATE_REJECTED deployer, task 23cf3710 chosen by the agent: its policy status message Production deploys are outside this agent's policy. Use the release train. artifacts: none AFTER THE END calls to the canceled task f22ed7af, from 10-cancel.http SendMessage, taskId f22ed7af UnsupportedOperationError, -32004 CancelTask, a second time TaskNotCancelableError, -32002
The agent chose three of these endings and the client one, and once a task ends, the server refuses any further message or cancel. Each card is one task from the kit: who chose the ending, the final status message, and the artifacts left behind. The bottom rows show what the canceled task answers afterwards. Ids are shortened to 8 characters.

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:

a completed task that reports a failing testjsonFull log
    "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,
Cut to the state, the status message, and the start of the summary.json data part.

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:

SDKJSON-RPCHTTP+JSONgRPC
a2a-python 1.2.2-32004400FAILED_PRECONDITION
a2a-go-32004400FAILED_PRECONDITION
a2a-java-32004400UNIMPLEMENTED
a2a-js-32004400FAILED_PRECONDITION
a2a-dotnet-32004400, a problem-details bodynone at this release
a2a-rs-32004400FAILED_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