feat: add generation field to Task for ordering, long-polling, and optimistic concurrency#1810
feat: add generation field to Task for ordering, long-polling, and optimistic concurrency#1810Tehsmash wants to merge 8 commits into
Conversation
…and optimistic concurrency Adds a monotonically increasing `generation` field to `Task` that the server increments on every state-changing mutation (TaskState transition or Artifact addition/update). Proto changes: - `Task.generation`: the current generation of the task snapshot. - `TaskStatusUpdateEvent.generation` / `TaskArtifactUpdateEvent.generation`: the post-mutation generation, allowing clients to detect missed events by observing gaps in the sequence. - `GetTaskRequest.current_generation`: enables efficient long-polling; the server SHOULD hold the request until the task's generation exceeds the supplied value. - `SendMessageConfiguration.if_generation_match`: optimistic concurrency guard; the server MUST reject the request (HTTP 412 / gRPC ABORTED) if the task's generation does not match, preventing lost-update races. Spec documentation changes: - New Section 3.2.7 Task Generation Semantics covering the generation field, event ordering/missed-event detection, long-polling behaviour, optimistic concurrency, and backwards compatibility. - Section 3.1.3 Get Task: cross-reference to new generation semantics section for currentGeneration long-polling. - Section 3.2.2 SendMessageConfiguration: document ifGenerationMatch optimistic concurrency behaviour. - Section 3.3.2 Error Handling: add TaskGenerationMismatchError. - Section 3.5.1: add long-polling as an update delivery mechanism. - Section 3.5.2: note generation-based event ordering. - Section 5.4: add TaskGenerationMismatchError error code mappings (JSON-RPC -32010 / gRPC ABORTED / HTTP 412). - Section 11.5: add currentGeneration query parameter example. - Section 11.6: add 412 Precondition Failed error response example. Closes a2aproject#1794 Signed-off-by: Sam Betts <1769706+Tehsmash@users.noreply.github.com>
There was a problem hiding this comment.
Code Review
This pull request introduces task generation semantics to the A2A protocol, adding a monotonically increasing generation field to tasks and events. This enables reliable event ordering, missed-event detection, optimistic concurrency control via a new ifGenerationMatch field, and efficient long-polling through the currentGeneration parameter. The review feedback suggests initializing the generation counter at 1 instead of 0 to distinguish between a new task and a server that does not support generations. Additionally, it is recommended to map the TaskGenerationMismatchError to HTTP 409 Conflict to maintain consistency with canonical gRPC-to-HTTP status mappings.
| | `ExtendedAgentCardNotConfiguredError` | `-32007` | `FAILED_PRECONDITION` | `400 Bad Request` | | ||
| | `ExtensionSupportRequiredError` | `-32008` | `FAILED_PRECONDITION` | `400 Bad Request` | | ||
| | `VersionNotSupportedError` | `-32009` | `FAILED_PRECONDITION` | `400 Bad Request` | | ||
| | `TaskGenerationMismatchError` | `-32010` | `ABORTED` | `412 Precondition Failed` | |
There was a problem hiding this comment.
The mapping of gRPC ABORTED to HTTP 412 Precondition Failed deviates from the canonical gRPC mapping (where ABORTED maps to 409 Conflict). Per the general rules of this repository, compatibility with gRPC transcoding systems should be prioritized by using canonical mappings. While 412 is semantically appropriate for If-Match failures in REST, using ABORTED will likely result in 409 being generated by standard transcoders.
| | `TaskGenerationMismatchError` | `-32010` | `ABORTED` | `412 Precondition Failed` | | |
| | TaskGenerationMismatchError | -32010 | ABORTED | 409 Conflict | |
References
- When mapping gRPC statuses to HTTP statuses, prioritize compatibility with gRPC transcoding systems by using the canonical mapping, even if a more specific HTTP status code is available.
- Document that clients receiving two consecutive events with identical generation values (including two 0s) MUST conclude the server does not implement generation and fall back to stream ordering / timestamps. This replaces the earlier sentinel-value approach with a robust detection rule and covers the backward-compat case cleanly. - Change TaskGenerationMismatchError HTTP mapping from 412 Precondition Failed to 409 Conflict to align with the canonical gRPC ABORTED to HTTP 409 transcoding mapping used by standard gRPC-HTTP proxies. Updates all four references in the spec and the proto comment. Signed-off-by: Sam Betts <1769706+Tehsmash@users.noreply.github.com>
|
|
||
| <span id="327-task-generation-semantics"></span> | ||
|
|
||
| The `generation` field on [`Task`](#411-task) is a monotonically increasing integer maintained by the server. It is initialised to `0` when a task is created and **MUST** be incremented by the server on every state-changing mutation: |
There was a problem hiding this comment.
monotonically -> sequentially. increasing is always monotonic but not always sequential which is important here. in line 511 we mention that this is assumed on the client, but we never outright say that the server should act in that way, i would make is as explicit as possible, something like:
Events produced by the server MUST have sequential numbering, meaning each change in the generation of a Task MUST by an increase by one and must map 1:1 to a produced Event.
There was a problem hiding this comment.
A what-if question. Along with generation, can we have generationIDs ? It would be a list of integers.
|
|
||
| **Backwards Compatibility:** | ||
|
|
||
| The `generation` field was introduced in version **1.1** of this specification. The field defaults to `0` in the Protocol Buffer encoding (proto3 default for `int64`). Clients that do not read or send `generation` continue to interoperate correctly with servers that support it. Servers that have not yet implemented `generation` will return `0` on all events and responses; clients will detect this via the duplicate-generation rule above and gracefully fall back. |
There was a problem hiding this comment.
I don't think we have 1.1 but may be we should have :)
Latest Released Version 1.0.0
There was a problem hiding this comment.
The current release is 1.0.0 and we're not going back to change that, this change would only be included if we cut a 1.1 because it involves a proto change.
There was a problem hiding this comment.
We should start planning for that.
Replace "monotonically increasing" with "sequentially increasing" and make the server obligation explicit: generation MUST be incremented by exactly 1 per mutation, with each mutation mapping 1:1 to one emitted event. Update both the spec prose and the proto comment accordingly. Signed-off-by: Sam Betts <1769706+Tehsmash@users.noreply.github.com>
Starting at 1 means generation=0 is an unambiguous sentinel detectable from a single event, rather than requiring two consecutive events with the same value. Update the detection rule accordingly: any event carrying generation=0 signals a non-implementing server. Also update the backwards-compatibility note and the proto comment. Signed-off-by: Sam Betts <1769706+Tehsmash@users.noreply.github.com>
|
Added a git commit to fix the merge conflict. For |
|
Should this be part of 1.1 or 2.0 ? (vote yes for 1.1) /vote |
Vote created@msampathkumar has called for a vote on The members of the following teams have binding votes:
Non-binding votes are also appreciated as a sign of support! How to voteYou can cast your vote by reacting to
Please note that voting for multiple options is not allowed and those votes won't be counted. The vote will be open for |
Vote statusSo far Summary
Binding votes (2)
|
| User | Vote | Timestamp |
|---|---|---|
| msampathkumar | In favor | 2026-06-02 16:45:35.0 +00:00:00 |
3 similar comments
Vote statusSo far Summary
Binding votes (2)
|
| User | Vote | Timestamp |
|---|---|---|
| msampathkumar | In favor | 2026-06-02 16:45:35.0 +00:00:00 |
Vote statusSo far Summary
Binding votes (2)
|
| User | Vote | Timestamp |
|---|---|---|
| msampathkumar | In favor | 2026-06-02 16:45:35.0 +00:00:00 |
Vote statusSo far Summary
Binding votes (2)
|
| User | Vote | Timestamp |
|---|---|---|
| msampathkumar | In favor | 2026-06-02 16:45:35.0 +00:00:00 |
Vote statusSo far Summary
Binding votes (3)
|
| User | Vote | Timestamp |
|---|---|---|
| msampathkumar | In favor | 2026-06-02 16:45:35.0 +00:00:00 |
2 similar comments
Vote statusSo far Summary
Binding votes (3)
|
| User | Vote | Timestamp |
|---|---|---|
| msampathkumar | In favor | 2026-06-02 16:45:35.0 +00:00:00 |
Vote statusSo far Summary
Binding votes (3)
|
| User | Vote | Timestamp |
|---|---|---|
| msampathkumar | In favor | 2026-06-02 16:45:35.0 +00:00:00 |
Vote statusSo far Summary
Binding votes (5)
|
| User | Vote | Timestamp |
|---|---|---|
| msampathkumar | In favor | 2026-06-02 16:45:35.0 +00:00:00 |
Vote closedThe vote passed! 🎉
Summary
Binding votes (5)
|
| User | Vote | Timestamp |
|---|---|---|
| @msampathkumar | In favor | 2026-06-02 16:45:35.0 +00:00:00 |
Summary
Adds a monotonically increasing
generationfield toTask(and related messages) that supports three new capabilities:1. Event ordering / missed-event detection
TaskStatusUpdateEventandTaskArtifactUpdateEventnow carry the task'sgenerationvalue after the mutation that produced them. A gap in generation values (e.g. jumping from3to5) tells a client it missed at least one event and should re-fetch the full task state.2. Long-polling via
GetTaskRequest.current_generationIf
currentGenerationis supplied on aGetTaskcall the server SHOULD hold the request open until the task's generation advances beyond that value, returning immediately if it already has. This replaces tight polling loops with a single blocking call.3. Optimistic concurrency via
SendMessageConfiguration.if_generation_matchIf
ifGenerationMatchis set the server MUST reject the request with HTTP412 Precondition Failed/ gRPCABORTED(TaskGenerationMismatchError) if the task's generation has changed since the client last read it, preventing lost-update races in multi-client scenarios.Proto changes
Taskint64 generation = 7TaskStatusUpdateEventint64 generation = 5TaskArtifactUpdateEventint64 generation = 7GetTaskRequestoptional int64 current_generation = 4SendMessageConfigurationoptional int64 if_generation_match = 5Spec documentation changes
generationfield, event ordering, long-polling behaviour, optimistic concurrency, and backwards compatibility.currentGenerationlong-polling.ifGenerationMatchoptimistic concurrency behaviour and the new error.TaskGenerationMismatchErrorto the A2A-Specific Errors table.TaskGenerationMismatchError(JSON-RPC-32010/ gRPCABORTED/ HTTP412).currentGenerationto the mapping table and example.412generation mismatch error response example includingifGenerationMatchbody mapping.Backwards compatibility
generationdefaults to0in proto3. Existing clients that do not read or sendgenerationcontinue to interoperate without change. Clients SHOULD treat a server-returnedgenerationof0as "generation unknown" and MUST NOT use it for ordering, long-polling, or precondition checks against a server that does not implement this feature.Fixes #1794