Thomas Piacentini
← Atelier

Chapitre

SendMessage

Texte original · anglais

SendMessage carries one message and a SendMessageConfiguration, and the agent answers with a Message or a Task that, by default, is terminal or interrupted.

Your planner sends code-reviewer two messages through the same method. The first asks which languages it reviews, and the answer arrives at once as a message. The second carries a diff, and the answer is a task with an id, a state, and later an artifact.

When you finish this section, you can build a SendMessage request and act on whichever shape comes back.

The request and its configuration

SendMessageRequest: a message, as Message defines it, an optional configuration, and metadata {{proto SendMessageRequest}}. The configuration has four fields {{proto SendMessageConfiguration}}:

  • acceptedOutputModes: the media types the client accepts.
  • taskPushNotificationConfig: a webhook, as push notification configs explains.
  • historyLength: how much history the returned task carries.
  • returnImmediately: whether the server answers before the task settles.

A send blocks unless returnImmediately is true:

Original source
label: the blocking default
source: spec §3.2.2
---
The operation must wait to return until the task reaches a terminal state or an interrupted state. The terminal states are `TASK_STATE_COMPLETED`, `TASK_STATE_FAILED`, `TASK_STATE_CANCELED`, and `TASK_STATE_REJECTED`. The interrupted states are `TASK_STATE_INPUT_REQUIRED` and `TASK_STATE_AUTH_REQUIRED`.

In 03-blocking-task.http, one SendMessage with no configuration gets task 3d8f7801 in TASK_STATE_COMPLETED, with every artifact {{spec §3.2.2}}. The client never sees TASK_STATE_SUBMITTED or TASK_STATE_WORKING. The specification sets no time limit, and a client that times out never learns the task id {{spec §3.4.2}}.

One sentence of the specification says the opposite (conflict D2): the operation must return at once with either task information or a response message {{spec §3.1.1}}. The proto makes false the default {{proto SendMessageConfiguration}}, and the proto is normative {{spec §1.4}}. The kit and the reference SDK block {{sdk src/a2a/server/request_handlers/default_request_handler_v2.py}}.

The SDKs disagree on whether historyLength in the configuration trims the returned task:

SDK configuration.historyLength on the returned task
a2a-python 1.2.2 applied to the returned Task and to task events of a stream, and a negative value is InvalidParamsError {{sdk src/a2a/server/request_handlers/default_request_handler_v2.py}}
a2a-go never applied, so the task returns with its full stored history {{sdk-go a2asrv/handler.go at v2.6.0}}
a2a-java applied to the returned Task and to each Task event of a stream, and a negative value is InvalidParamsError {{sdk-java requesthandlers/DefaultRequestHandler.java at v1.4.0.Final}}
a2a-js applied to the returned task and to task payloads of a stream, and 0 or less omits history {{sdk-js src/server/request_handler/default_request_handler.ts at v1.3.0}}
a2a-dotnet never read by the server, so the task returns with its full stored history {{sdk-dotnet src/A2A/Server/A2AServer.cs at v1.0.0-preview2}}
a2a-rs applied to the returned Task of a blocking send only, and a stream ignores it {{sdk-rust a2a-server/src/handler.rs at a2a-server-lf-v0.5.1}}

Message or Task

SendMessage returns a SendMessageResponse whose payload is a oneof of two members, task and message {{proto SendMessageResponse}}. The agent decides, and the specification gives it no rule. The agent is allowed to create a new Task and process the message asynchronously. It is also allowed to return a direct Message for a simple exchange {{spec §3.1.1}}.

In the kit, test-runner and deployer create a task for every message. code-reviewer answers with a message when the request has no text/x-diff part and its text ends with a question mark (the kit's agent code). Once a task exists, the choice is over: from then on, the agent returns only Task objects in response to messages {{docs life-of-a-task}}.

A message is the whole answer, with no id to poll, cancel, or subscribe to. It carries a contextId {{proto Message}}, so keep it for a follow-up. A task is a handle on work that can outlive the call. Figure puts the two answers from code-reviewer side by side. SendStreamingMessage makes the same choice in its first event, as SendStreamingMessage and SubscribeToTask shows.

Fig. 4.1one method, two result shapesdecision
The same `SendMessage` to `code-reviewer` returns a message for a question and a task for a diff, and only the task gives the client something to follow.
The same SendMessage to code-reviewer returns a message for a question and a task for a diff, and only the task gives the client something to follow. Read top down. The agent's own logic picks the shape, here the review_quick rule of the kit's agent code. Left, the captured message reply. Right, the captured input-required task. Ids are shortened to 8 characters.

Original SVG

Original source
id: fig-message-or-task
kind: decision
title: one method, two result shapes
claim: The same `SendMessage` to `code-reviewer` returns a message for a question and a task for a diff, and only the task gives the client something to follow.
caption: Read top down. The agent's own logic picks the shape, here the review_quick rule of the kit's agent code. Left, the captured message reply. Right, the captured input-required task. Ids are shortened to 8 characters.

Returning at once and polling with GetTask

returnImmediately: a boolean in SendMessageConfiguration that asks the server to answer as soon as the task exists. The operation must return right after it creates the task, even while the work goes on {{spec §3.2.2}}. The flag has no effect on a direct Message reply, on streaming operations, or on push notification configs {{spec §3.2.2}}.

Polling: reading a task with GetTask until its state tells you to stop. In 04-polling.http the kit sends with returnImmediately, then polls task d0c28826 twice with historyLength 0:

a send that returns at once, then two pollsjsonFull source
    "configuration": {
      "returnImmediately": true
    }
…
        "state": "TASK_STATE_SUBMITTED",
…
  "method": "GetTask",
  "params": {
    "id": "d0c28826-28b8-4b15-8b53-4e01ae7a5a3c",
    "historyLength": 0
  }
…
      "state": "TASK_STATE_WORKING",
…
            "text": "collected 12 items\n"
…
      "state": "TASK_STATE_COMPLETED",
The send and the first GetTask request are cut to their key lines. The second GetTask request matches the first apart from its JSON-RPC id. Each reply is cut to its state, and the first poll also shows its one log part.
Original source
title: a send that returns at once, then two polls
source: capture/out/04-polling.http
lang: json
note: The send and the first GetTask request are cut to their key lines. The second GetTask request matches the first apart from its JSON-RPC id. Each reply is cut to its state, and the first poll also shows its one log part.
---
    "configuration": {
      "returnImmediately": true
    }
…
        "state": "TASK_STATE_SUBMITTED",
…
  "method": "GetTask",
  "params": {
    "id": "d0c28826-28b8-4b15-8b53-4e01ae7a5a3c",
    "historyLength": 0
  }
…
      "state": "TASK_STATE_WORKING",
…
            "text": "collected 12 items\n"
…
      "state": "TASK_STATE_COMPLETED",

The send returns TASK_STATE_SUBMITTED, while the specification's examples name TASK_STATE_WORKING and TASK_STATE_INPUT_REQUIRED {{spec §3.2.2}}, so switch on the state you get. The first poll finds TASK_STATE_WORKING and one log part. The second finds TASK_STATE_COMPLETED with every artifact. Figure sets the blocking call beside the immediate return and its two polls.

Fig. 4.2a blocking call and a call that returns at oncetimeline
A blocking send holds one request open until this task completes, while `returnImmediately` answers at `TASK_STATE_SUBMITTED` and leaves the client to poll.
A blocking send holds one request open until this task completes, while returnImmediately answers at TASK_STATE_SUBMITTED and leaves the client to poll. Both tasks are aligned at the request and run through the same states on the kit's clock. Filled squares are states a reply showed the client, and hollow squares are states it never saw. Green ticks are artifact updates, and violet bars are open requests. From capture/out/03-blocking-task.http and 04-polling.http.

Original SVG

Original source
id: fig-blocking-and-return
kind: timeline
title: a blocking call and a call that returns at once
claim: A blocking send holds one request open until this task completes, while `returnImmediately` answers at `TASK_STATE_SUBMITTED` and leaves the client to poll.
caption: Both tasks are aligned at the request and run through the same states on the kit's clock. Filled squares are states a reply showed the client, and hollow squares are states it never saw. Green ticks are artifact updates, and violet bars are open requests. From capture/out/03-blocking-task.http and 04-polling.http.

Dispatch on the result

The kit's planner tests which member is present before it touches either:

the planner's dispatch on a SendMessage resultpythonFull source
        while True:
            if "error" in reply:
                self.note(f"  error {reply['error']['code']}: {reply['error']['message']}")
                return None
            result = reply["result"]
            if "message" in result:
                self.note("  direct reply, no task to follow")
                return result["message"]
            task = result["task"] if "task" in result else result
            state = task["status"]["state"]
            self.note(f"  task {task['id'][:8]} is {state}")
            if state in TERMINAL:
                return task
The top of the loop in Planner.delegate. The interrupted states are handled below this cut.
Original source
title: the planner's dispatch on a SendMessage result
source: capture/planner.py
lang: python
note: The top of the loop in Planner.delegate. The interrupted states are handled below this cut.
---
        while True:
            if "error" in reply:
                self.note(f"  error {reply['error']['code']}: {reply['error']['message']}")
                return None
            result = reply["result"]
            if "message" in result:
                self.note("  direct reply, no task to follow")
                return result["message"]
            task = result["task"] if "task" in result else result
            state = task["status"]["state"]
            self.note(f"  task {task['id'][:8]} is {state}")
            if state in TERMINAL:
                return task

The planner checks error first, because JSON-RPC errors arrive inside an HTTP 200 response. Then it checks message and stops. Only then does it branch on the task's state, as Task, TaskStatus, and TaskState defines it. A client walks through the rest of this loop.

Original source
- Test for `message` and `task` in every `SendMessage` result, and never assume one shape for an agent.
- Set `returnImmediately: true` whenever you plan to poll, subscribe, or wait for a webhook.
- Switch on the state of every reply, including an early `TASK_STATE_SUBMITTED` from a blocking send.

Sources: spec §1.4, §3.1.1, §3.2.2, §3.4.2 (research/sources/specification.md); proto SendMessageRequest, SendMessageConfiguration, SendMessageResponse, Message (research/sources/a2a.proto); docs docs/topics/life-of-a-task.md at v1.0.1; sdk src/a2a/server/request_handlers/default_request_handler_v2.py (a2a-python 1.2.2); sdk-go a2asrv/handler.go at v2.6.0; sdk-java server-common/src/main/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandler.java at v1.4.0.Final; sdk-js src/server/request_handler/default_request_handler.ts at v1.3.0; sdk-dotnet src/A2A/Server/A2AServer.cs at v1.0.0-preview2; sdk-rust a2a-server/src/handler.rs at a2a-server-lf-v0.5.1; capture/out/02-message-reply.http, 03-blocking-task.http, 04-polling.http, 06-input-required.http; capture/agents.py, capture/planner.py

Code et livrables 1
Ce que vous en retenez

Enregistré dans votre carnet sur cet appareil.

Sources et limites

Les repères de lecture sont déclaratifs. Les résultats de tests sont enregistrés séparément ; ils valent pour les cas exécutés.

AI Engineering from Scratch · 3f9715c · MIT

Télécharger ce fichier ↓