What happened?
Summary
SendMessage to a task in a terminal state (e.g., completed) succeeds instead of returning UnsupportedOperationError. Fails on all three transports (JSON-RPC, gRPC, HTTP+JSON).
Requirement
Specification
UnsupportedOperationError: Messages sent to Tasks that are in a terminal state (e.g., completed, canceled, rejected) cannot accept further messages.
Expected behavior
When a client sends a SendMessage request with a taskId referencing a task that has already reached a terminal state (completed, canceled, rejected), the SDK framework MUST intercept the request and return an UnsupportedOperationError before the message reaches the AgentExecutor.
Actual behavior
The SDK accepts the message and forwards it to the AgentExecutor, which processes it as if the task were still active. No error is returned.
Reproducer
The TCK test creates a task that immediately completes, then sends a follow-up message referencing that task's ID.
The SUT executor that produces the completed task looks like this (from the generated TckAgentExecutorProducer.java):
// Any message with this prefix completes the task immediately,
// putting it in a terminal state (TASK_STATE_COMPLETED).
if (messageId.startsWith("tck-task-helper")) {
emitter.complete(A2A.toAgentMessage("Task helper response"));
return;
}
After this task completes, the TCK sends another SendMessage with taskId set to the completed task's ID. The SDK should reject this with UnsupportedOperationError at the framework level, but instead it forwards the message to the executor.
JSON-RPC reproducer:
# Step 1: Create a task that completes immediately
curl -s -X POST http://localhost:9999/ \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"1","method":"SendMessage","params":{"message":{"messageId":"tck-task-helper-abcd1234","role":"ROLE_USER","parts":[{"text":"Create task"}]}}}'
# → Returns task with status.state = "TASK_STATE_COMPLETED"
# Extract the task ID from result.task.id
# Step 2: Send a message to the terminal task (replace <TASK_ID> with the ID from step 1)
curl -s -X POST http://localhost:9999/ \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"2","method":"SendMessage","params":{"message":{"messageId":"tck-follow-up-1234","role":"ROLE_USER","parts":[{"text":"Follow-up"}],"taskId":"<TASK_ID>"}}}'
# Expected: JSON-RPC error with code -32004 (UnsupportedOperationError)
# Actual: Succeeds with a new task result
TCK test
tests/compatibility/core_operations/test_task_lifecycle.py::TestMultiTurn::test_send_message_to_terminal_task[grpc]
tests/compatibility/core_operations/test_task_lifecycle.py::TestMultiTurn::test_send_message_to_terminal_task[http_json]
tests/compatibility/core_operations/test_task_lifecycle.py::TestMultiTurn::test_send_message_to_terminal_task[jsonrpc]
Relevant log output
Code of Conduct
What happened?
Summary
SendMessageto a task in a terminal state (e.g.,completed) succeeds instead of returningUnsupportedOperationError. Fails on all three transports (JSON-RPC, gRPC, HTTP+JSON).Requirement
Specification
Expected behavior
When a client sends a
SendMessagerequest with ataskIdreferencing a task that has already reached a terminal state (completed,canceled,rejected), the SDK framework MUST intercept the request and return anUnsupportedOperationErrorbefore the message reaches theAgentExecutor.Actual behavior
The SDK accepts the message and forwards it to the
AgentExecutor, which processes it as if the task were still active. No error is returned.Reproducer
The TCK test creates a task that immediately completes, then sends a follow-up message referencing that task's ID.
The SUT executor that produces the completed task looks like this (from the generated
TckAgentExecutorProducer.java):After this task completes, the TCK sends another
SendMessagewithtaskIdset to the completed task's ID. The SDK should reject this withUnsupportedOperationErrorat the framework level, but instead it forwards the message to the executor.JSON-RPC reproducer:
TCK test
Relevant log output
Code of Conduct