Skip to content

[Bug]: A2ARESTFastAPIApplication omits state field from task status when task is rejected #625

@benedict-khoo-sap

Description

@benedict-khoo-sap

What happened?

Steps to reproduce

  1. Install a2a-sdk[http-server] (tested with v0.3.22) and uvicorn (tested with v0.40.0) using Python 3.12:
uv init -p 3.12
uv add "a2a-sdk[http-server]" uvicorn
  1. Paste the following code in main.py
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.apps import A2ARESTFastAPIApplication, A2AStarletteApplication
from a2a.server.events import EventQueue
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore, TaskUpdater
from a2a.types import (
    AgentCapabilities,
    AgentCard,
    AgentInterface,
    AgentSkill,
    TransportProtocol,
    UnsupportedOperationError,
)
from a2a.utils import new_agent_text_message, new_task
from a2a.utils.errors import ServerError
import uvicorn

SUPPORTED_CONTENT_TYPES = ["text", "text/plain"]


class TheAgentExecutor(AgentExecutor):
    async def execute(
        self,
        context: RequestContext,
        event_queue: EventQueue,
    ) -> None:
        task = context.current_task
        if not task:
            task = new_task(context.message)  # type: ignore
            await event_queue.enqueue_event(task)

        context_id = context.context_id or task.context_id
        updater = TaskUpdater(event_queue, task.id, context_id)

        await updater.reject(message=new_agent_text_message("I don't want to work"))

    async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
        raise ServerError(error=UnsupportedOperationError())


def create_server(transport_protocol: TransportProtocol):
    capabilities = AgentCapabilities(streaming=True, push_notifications=False)
    skill = AgentSkill(
        id="convert_currency",
        name="Currency Exchange Rates Tool",
        description="Helps with exchange values between various currencies",
        tags=["currency conversion", "currency exchange"],
        examples=["What is exchange rate between USD and GBP?"],
    )
    url = "http://localhost:10000"
    agent_card = AgentCard(
        name="Currency Agent",
        description="Helps with exchange rates for currencies",
        url=url,
        version="1.0.0",
        default_input_modes=SUPPORTED_CONTENT_TYPES,
        default_output_modes=SUPPORTED_CONTENT_TYPES,
        capabilities=capabilities,
        skills=[skill],
        preferred_transport=transport_protocol,
        additional_interfaces=[AgentInterface(transport=transport_protocol, url=url)],
    )

    request_handler = DefaultRequestHandler(
        agent_executor=TheAgentExecutor(),
        task_store=InMemoryTaskStore(),
    )
    match transport_protocol:
        case TransportProtocol.http_json:
            return A2ARESTFastAPIApplication(
                agent_card=agent_card, http_handler=request_handler
            )
        case TransportProtocol.jsonrpc:
            return A2AStarletteApplication(
                agent_card=agent_card, http_handler=request_handler
            )
        case _:
            raise NotImplementedError(
                f"Unsupported server transport protocol: {transport_protocol}"
            )

server = create_server(transport_protocol=TransportProtocol.http_json)

if __name__ == "__main__":
    uvicorn.run(server.build(), host="0.0.0.0", port=10000)
  1. Run it: uv run main.py
  2. Open a terminal and send a message to the server:
curl --request POST \
  --url http://127.0.0.1:10000/v1/message:send \
  --header 'content-type: application/json' \
  --data '{
  "message": {
    "messageId": "c52d542b-06cb-4e9a-b36e-15ee6cb7eee4",
    "role": "ROLE_USER",
    "content": [
      {
        "text": "Exchange USD"
      }
    ]
  }
}'
  1. Wait a few seconds then get the task, replacing <TASK_ID> with the task ID you got inresponse to the send message request:
curl --request GET \
  --url http://127.0.0.1:10000/v1/tasks/<TASK_ID>

Actual Output

The Task response is not valid. It contains a status field with no state sub-field.

Example (IDs might be different for you, but the shape should be the same):

{
  "id": "dd8b8e5b-1831-422c-a8a0-46560ef59e59",
  "contextId": "d076a8f4-e1a2-4a25-8516-3e853433e8bb",
  "status": {
    "message": {
      "messageId": "c7e0d4aa-8a8d-4e31-bafe-e57e19585803",
      "role": "ROLE_AGENT",
      "content": [
        {
          "text": "I don't want to work"
        }
      ]
    }
  },
  "history": [
    {
      "messageId": "c52d542b-06cb-4e9a-b36e-15ee6cb7eee4",
      "contextId": "d076a8f4-e1a2-4a25-8516-3e853433e8bb",
      "taskId": "dd8b8e5b-1831-422c-a8a0-46560ef59e59",
      "role": "ROLE_USER",
      "content": [
        {
          "text": "Exchange USD"
        }
      ],
      "metadata": {}
    }
  ]
}

Expected Output

The response should be a task object that contains a status field. Within status, there should be a state field with a valid TaskState value. According to the A2A specs, state is a required field: https://a2a-protocol.org/dev/specification/#412-taskstatus

Other notes

  • If you change the server code to use TransportProtocol.jsonrpc, the state field appears as expected.
  • If you change TheAgentExecutor to complete the task instead of rejecting it, the state field appears as expected: i.e. replace
await updater.reject(message=new_agent_text_message("I don't want to work"))

with

await updater.complete(message=new_agent_text_message("I don't want to work"))

Relevant log output

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Labels

component: serverIssues related to frameworks for agent execution, HTTP/event handling, database persistence logic.status:awaiting response

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions