Skip to content

Commit 9384703

Browse files
committed
adding idempotency for multi-turn message continuation. better behavior in case of collisions
1 parent 697e334 commit 9384703

File tree

1 file changed

+272
-7
lines changed

1 file changed

+272
-7
lines changed

docs/specification.md

Lines changed: 272 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ Specifies optional A2A protocol features supported by the agent.
322322
--8<-- "types/src/types.ts:AgentCapabilities"
323323
```
324324

325-
- **`idempotencyEnforced`**: When set to `true`, indicates that the agent enforces messageId-based idempotency for new task creation. This enables the agent to detect and handle duplicate `messageId` values to prevent unintended duplicate task creation due to network failures or client retries. See [Section 7.1.2](#712-idempotency) for detailed behavior.
325+
- **`idempotencyEnforced`**: When set to `true`, indicates that the agent enforces messageId-based idempotency for task creation and follow-up. This enables the agent to detect and handle duplicate `messageId` values to prevent unintended duplicate task creation or wrong behavior in multi turn messages due to network failures or client retries. See [Section 7.1.2](#712-idempotency) for detailed behavior.
326326

327327
#### 5.5.2.1. `AgentExtension` Object
328328

@@ -727,28 +727,33 @@ The `error` response for all transports in case of failure is a [`JSONRPCError`]
727727

728728
A2A supports optional messageId-based idempotency to enable idempotent task creation, addressing scenarios where network failures or client crashes could result in duplicate tasks with unintended side effects.
729729

730-
**Scope**: Idempotency **ONLY** applies to new task creation (when `message.taskId` is not provided). Messages sent to continue existing tasks follow normal message uniqueness rules without task-level deduplication.
730+
**Scope**: Idempotency applies to new task creation (when `message.taskId` is not provided) and on messages sent to continue existing tasks in a multi turn workflow.
731731

732732
**Agent Requirements:**
733733

734734
- Agents **MAY** enforce idempotency by declaring `idempotencyEnforced: true` in their `AgentCapabilities`.
735-
- When `idempotencyEnforced` is `true`, agents **MUST** track `messageId` values for new task creation within the authenticated user/session scope.
735+
- When `idempotencyEnforced` is `true`, agents **MUST** track `messageId` values for new task creation and multi-turn message continuation within the authenticated user/session scope.
736736
- When `idempotencyEnforced` is `false` or absent, agents **MAY** not enforce idempotency and do not track `messageId` values.
737737

738738
**Server Behavior:**
739739

740740
- **MessageId scope**: MessageId tracking is scoped to the authenticated user/session to prevent cross-user conflicts.
741-
- **Active task collision**: If a `messageId` (for new task creation) matches an existing task in a non-terminal state (`submitted`, `working`, `input-required`), the server **MUST** return a [`MessageIdAlreadyExistsError`](#82-a2a-specific-errors) (-32008).
741+
- **MessageId collision handling**: If a `messageId` matches a value previously seen, the server **MUST**:
742+
- **Content hash verification**: If the SHA256 hash of the request message content matches the hash of the previous message AND the matched message is the most recent message for the Task/Message, then:
743+
- If the request is non-blocking, OR the task is in a terminal state, OR the response was a Message: immediately return the current state of the Task/Message.
744+
- If the request is blocking and the task is in a non-terminal state: block until the Task transitions to a terminal state, and return the Task.
745+
- **Content mismatch**: Otherwise, return a [`MessageIdAlreadyExistsError`](#82-a2a-specific-errors) (-32008) indicating the `messageId` is already associated with different content.
742746
- **Terminal task reuse**: If a `messageId` matches a task in a terminal state (`completed`, `failed`, `canceled`, `rejected`), the server **MAY** allow creating a new task with the same messageId.
743747
- **Time-based expiry**: Servers **MAY** implement time-based expiry for messageId tracking associated with terminal tasks.
744748

745749
**Client Responsibilities:**
746750

747751
- **Unique messageIds**: Clients **MUST** generate unique `messageId` values for each intended new task within their authenticated session (this is already required by the base A2A specification).
752+
- **Simple retry loops**: Clients can implement simple retry loops that keep sending the same request until it succeeds, without worrying about handling collision errors for genuine retries.
748753
- **Error handling**: When receiving `MessageIdAlreadyExistsError`, clients **SHOULD**:
749-
1. Use the `existingTaskId` from the error data to call `tasks/get` to retrieve the existing task. This is the correct action when retrying a request that may have already succeeded (e.g., after a network error).
750-
2. Alternatively, if the `messageId` was reused unintentionally and a new, distinct task is desired, generate a new `messageId` and retry the request.
751-
- **Retry safety**: Clients can safely retry `message/send` requests with the same `messageId` after network failures when creating new tasks.
754+
1. Use the `existingTaskId` from the error data to call `tasks/get` to retrieve the existing task. This indicates the client has sent a different message with the same `messageId`.
755+
2. If a new, distinct task is desired, generate a new `messageId` and retry the request.
756+
- **Retry safety**: Clients can safely retry `message/send` requests with identical content and the same `messageId` after network failures, as the server will return the appropriate response without creating duplicate tasks.
752757

753758
### 7.2. `message/stream`
754759

@@ -2003,6 +2008,266 @@ _If the task were longer-running, the server might initially respond with `statu
20032008

20042009
This pattern ensures that duplicate operations are prevented while allowing clients to safely recover from network failures.
20052010

2011+
### 9.9. Idempotent Multi-Turn Interaction
2012+
2013+
**Scenario:** Client wants to shop for items, and network failures cause message retransmission during the multi-turn workflow. The agent enforces idempotency to prevent duplicate orders.
2014+
2015+
1. **Client discovers agent supports idempotency from Agent Card:**
2016+
2017+
```json
2018+
{
2019+
"capabilities": {
2020+
"idempotencyEnforced": true
2021+
}
2022+
}
2023+
```
2024+
2025+
2. **Client sends initial shopping request (new task creation):**
2026+
2027+
```json
2028+
{
2029+
"jsonrpc": "2.0",
2030+
"id": "req-001",
2031+
"method": "message/send",
2032+
"params": {
2033+
"message": {
2034+
"role": "user",
2035+
"parts": [
2036+
{
2037+
"kind": "text",
2038+
"text": "I want to go shopping"
2039+
}
2040+
],
2041+
"messageId": "msg1-shopping-start"
2042+
}
2043+
}
2044+
}
2045+
```
2046+
2047+
3. **Server creates task and requests input:**
2048+
2049+
```json
2050+
{
2051+
"jsonrpc": "2.0",
2052+
"id": "req-001",
2053+
"result": {
2054+
"id": "task1-shopping-session",
2055+
"contextId": "ctx-shopping-001",
2056+
"status": {
2057+
"state": "input-required",
2058+
"message": {
2059+
"role": "agent",
2060+
"parts": [
2061+
{
2062+
"kind": "text",
2063+
"text": "Welcome to our store! What would you like to add to your cart?"
2064+
}
2065+
],
2066+
"messageId": "agent-msg1",
2067+
"taskId": "task1-shopping-session",
2068+
"contextId": "ctx-shopping-001"
2069+
}
2070+
},
2071+
"history": [
2072+
{
2073+
"role": "user",
2074+
"parts": [{"kind": "text", "text": "I want to go shopping"}],
2075+
"messageId": "msg1-shopping-start",
2076+
"taskId": "task1-shopping-session",
2077+
"contextId": "ctx-shopping-001"
2078+
}
2079+
],
2080+
"kind": "task"
2081+
}
2082+
}
2083+
```
2084+
2085+
4. **Client adds first item to cart:**
2086+
2087+
```json
2088+
{
2089+
"jsonrpc": "2.0",
2090+
"id": "req-002",
2091+
"method": "message/send",
2092+
"params": {
2093+
"message": {
2094+
"role": "user",
2095+
"parts": [
2096+
{
2097+
"kind": "text",
2098+
"text": "Add a SuperX4 Laptop to my cart"
2099+
}
2100+
],
2101+
"taskId": "task1-shopping-session",
2102+
"contextId": "ctx-shopping-001",
2103+
"messageId": "msg2-add-laptop"
2104+
}
2105+
}
2106+
}
2107+
```
2108+
2109+
5. **Server processes request and responds:**
2110+
2111+
```json
2112+
{
2113+
"jsonrpc": "2.0",
2114+
"id": "req-002",
2115+
"result": {
2116+
"id": "task1-shopping-session",
2117+
"contextId": "ctx-shopping-001",
2118+
"status": {
2119+
"state": "input-required",
2120+
"message": {
2121+
"role": "agent",
2122+
"parts": [
2123+
{
2124+
"kind": "text",
2125+
"text": "Added SuperX4 Laptop ($1,299) to your cart. Anything else?"
2126+
}
2127+
]
2128+
}
2129+
},
2130+
"kind": "task"
2131+
}
2132+
}
2133+
```
2134+
2135+
6. **Network failure occurs - client retries the same laptop addition:**
2136+
2137+
```json
2138+
{
2139+
"jsonrpc": "2.0",
2140+
"id": "req-003",
2141+
"method": "message/send",
2142+
"params": {
2143+
"message": {
2144+
"role": "user",
2145+
"parts": [
2146+
{
2147+
"kind": "text",
2148+
"text": "Add a SuperX4 Laptop to my cart"
2149+
}
2150+
],
2151+
"taskId": "task1-shopping-session",
2152+
"contextId": "ctx-shopping-001",
2153+
"messageId": "msg2-add-laptop"
2154+
}
2155+
}
2156+
}
2157+
```
2158+
2159+
7. **Server detects identical messageId and content hash, returns current state without duplicate action:**
2160+
2161+
```json
2162+
{
2163+
"jsonrpc": "2.0",
2164+
"id": "req-003",
2165+
"result": {
2166+
"id": "task1-shopping-session",
2167+
"contextId": "ctx-shopping-001",
2168+
"status": {
2169+
"state": "input-required",
2170+
"message": {
2171+
"role": "agent",
2172+
"parts": [
2173+
{
2174+
"kind": "text",
2175+
"text": "Added SuperX4 Laptop ($1,299) to your cart. Anything else?"
2176+
}
2177+
]
2178+
}
2179+
},
2180+
"kind": "task"
2181+
}
2182+
}
2183+
```
2184+
2185+
8. **Client adds second item (different messageId):**
2186+
2187+
```json
2188+
{
2189+
"jsonrpc": "2.0",
2190+
"id": "req-004",
2191+
"method": "message/send",
2192+
"params": {
2193+
"message": {
2194+
"role": "user",
2195+
"parts": [
2196+
{
2197+
"kind": "text",
2198+
"text": "Add a XtraScreen to my cart"
2199+
}
2200+
],
2201+
"taskId": "task1-shopping-session",
2202+
"contextId": "ctx-shopping-001",
2203+
"messageId": "msg3-add-screen"
2204+
}
2205+
}
2206+
}
2207+
```
2208+
2209+
9. **Client finalizes order:**
2210+
2211+
```json
2212+
{
2213+
"jsonrpc": "2.0",
2214+
"id": "req-005",
2215+
"method": "message/send",
2216+
"params": {
2217+
"message": {
2218+
"role": "user",
2219+
"parts": [
2220+
{
2221+
"kind": "text",
2222+
"text": "I'm done let's pay"
2223+
}
2224+
],
2225+
"taskId": "task1-shopping-session",
2226+
"contextId": "ctx-shopping-001",
2227+
"messageId": "msg4-checkout"
2228+
}
2229+
}
2230+
}
2231+
```
2232+
2233+
10. **Server completes transaction with correct single laptop:**
2234+
2235+
```json
2236+
{
2237+
"jsonrpc": "2.0",
2238+
"id": "req-005",
2239+
"result": {
2240+
"id": "task1-shopping-session",
2241+
"contextId": "ctx-shopping-001",
2242+
"status": {
2243+
"state": "completed"
2244+
},
2245+
"artifacts": [
2246+
{
2247+
"artifactId": "order-confirmation",
2248+
"name": "Order Receipt",
2249+
"parts": [
2250+
{
2251+
"kind": "data",
2252+
"data": {
2253+
"orderId": "ORD-12345",
2254+
"items": [
2255+
{"name": "SuperX4 Laptop", "price": 1299, "quantity": 1},
2256+
{"name": "XtraScreen", "price": 299, "quantity": 1}
2257+
],
2258+
"total": 1598
2259+
}
2260+
}
2261+
]
2262+
}
2263+
],
2264+
"kind": "task"
2265+
}
2266+
}
2267+
```
2268+
2269+
This example demonstrates how idempotency in multi-turn workflows prevents duplicate operations (only one laptop was ordered despite the retry) while allowing the conversation to continue naturally.
2270+
20062271
These examples illustrate the flexibility of A2A in handling various interaction patterns and data types. Implementers should refer to the detailed object definitions for all fields and constraints.
20072272

20082273
## 10. Appendices

0 commit comments

Comments
 (0)