Thomas Piacentini
← Workshop

Chapter

CancelTask and terminal states

Original text · English

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 {{proto TaskState}}.

A terminal task never changes again: once it is completed, canceled, rejected, or failed, it cannot restart {{docs life-of-a-task}}. Figure sets the four endings side by side.

Fig. 4.6four ways a task endscomparison
The agent chose three of these endings and the client one, and once a task ends, the server refuses any further message or cancel.
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. From capture/out/03-blocking-task.http, 09-failed.http, 10-cancel.http, and 08-rejected.http.

Original SVG

Original source
id: fig-task-endings
kind: comparison
title: four ways a task ends
claim: The agent chose three of these endings and the client one, and once a task ends, the server refuses any further message or cancel.
caption: 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. From capture/out/03-blocking-task.http, 09-failed.http, 10-cancel.http, and 08-rejected.http.

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 source
    "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.
Original source
title: a completed task that reports a failing test
source: capture/out/03-blocking-task.http
lang: json
note: Cut to the state, the status message, and the start of the summary.json data part.
---
    "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 {{proto TaskState}}, and the job was to run the suite. Whether the code is good is a result, and results travel in artifacts {{spec §3.7}}. 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 {{proto TaskState}}. 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 {{sdk src/a2a/server/agent_execution/active_task.py}}. 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 {{proto TaskState}}. 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 {{proto CancelTaskRequest}}. 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 {{spec §3.1.5}}. The specification does not say which unfinished states a server must cancel, and the reference SDK cancels any task that is not terminal {{sdk src/a2a/server/request_handlers/default_request_handler_v2.py}}.

A second CancelTask gets TaskNotCancelableError, -32002, from the kit and the reference SDK {{sdk src/a2a/server/request_handlers/default_request_handler_v2.py}}, as §3.1.5 lists for a finished task {{spec §3.1.5}}. Section 3.3.1 disagrees: it calls Cancel Task idempotent, so several cancel requests have the same effect {{spec §3.3.1}}. A repeat on a deleted task is allowed to return TaskNotFoundError {{spec §3.3.1}}.

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 {{spec §3.1.5}}.

Calls on a finished task

A finished task still answers. GetTask returns the final task {{spec §3.1.3}}. SendMessage with its taskId gets UnsupportedOperationError, -32004 {{spec §3.1.1}}, and SubscribeToTask gets the same error {{spec §3.1.6}}. CancelTask gets TaskNotCancelableError {{spec §3.1.5}}. 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 {{sdk src/a2a/server/agent_execution/active_task.py}} {{sdk src/a2a/utils/errors.py}} -32004 400 FAILED_PRECONDITION
a2a-go {{sdk-go a2asrv/agentexec.go at v2.6.0}} -32004 400 FAILED_PRECONDITION
a2a-java {{sdk-java server-common/src/main/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandler.java at v1.4.0.Final}} -32004 400 UNIMPLEMENTED
a2a-js {{sdk-js src/server/request_handler/default_request_handler.ts at v1.3.0}} -32004 400 FAILED_PRECONDITION
a2a-dotnet {{sdk-dotnet src/A2A/Server/A2AServer.cs at v1.0.0-preview2}} -32004 400, a problem-details body none at this release
a2a-rs {{sdk-rust a2a-server/src/handler.rs at a2a-server-lf-v0.5.1}} -32004 400 FAILED_PRECONDITION

a2a-java answers gRPC with UNIMPLEMENTED where §5.4 maps the error to FAILED_PRECONDITION {{spec §5.4}}. a2a-go in its experimental cluster mode answers InvalidParamsError, -32602, instead {{sdk-go internal/taskexec/distributed_manager.go at v2.6.0}}.

To go on, start a new task in the same context, naming the finished one in referenceTaskIds {{spec §3.4.3}}, as contextId shows.

Original source
- Stop every wait or poll loop on all four terminal states, `TASK_STATE_REJECTED` included.
- Read the artifacts for the result, because a completed test run can carry a failing test.
- After a cancel that errors, call `GetTask` and act on the state it returns.
- Start a new task in the same context, with `referenceTaskIds`, instead of messaging a finished task.

Sources: spec §3.1.1, §3.1.3, §3.1.5, §3.1.6, §3.3.1, §3.4.3, §3.7, §5.4 (research/sources/specification.md); proto TaskState, CancelTaskRequest (research/sources/a2a.proto); docs life-of-a-task (research/sources/docs.md); sdk src/a2a/server/agent_execution/active_task.py, src/a2a/server/request_handlers/default_request_handler_v2.py, src/a2a/utils/errors.py (a2a-python 1.2.2); sdk-go a2asrv/agentexec.go, internal/taskexec/distributed_manager.go (a2a-go v2.6.0); sdk-java server-common/src/main/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandler.java (a2a-java v1.4.0.Final); sdk-js src/server/request_handler/default_request_handler.ts (a2a-js v1.3.0); sdk-dotnet src/A2A/Server/A2AServer.cs (a2a-dotnet v1.0.0-preview2); sdk-rust a2a-server/src/handler.rs (a2a-rs a2a-server-lf-v0.5.1); capture/out/03-blocking-task.http, 08-rejected.http, 09-failed.http, 10-cancel.http, 14-resubscribe.http; capture/planner.py

Code and artifacts 1
What you take away

Saved in your notebook on this device.

Sources and limits

Reading checkpoints are self-reported. Test results are recorded separately and apply to the executed cases.

AI Engineering from Scratch · 3f9715c · MIT

Download this file ↓