Skip to content

Questions on to_a2a wrapper event behavior #3794

@bparees

Description

@bparees

I have been experimenting with the to_a2a utility and some of the behavior doesn't seem quite right with respect to the A2A protocol so I wanted to discuss it here:

The core behavior of this wrapper seems to produce the following sequences of A2A events when the wrapped agent is invoked for a streaming response (user submits a Message object containing their query):

  1. A TaskStatusUpdateEvent is generated setting the status to Submitted and containing a Message for which the text is the user's original query. Example

  2. A TaskStatusUpdateEvent is generated setting the status to Working with no Message content Example

  3. Another TaskStatusUpdateEvent is generated with the status still Working but containing a Message with the text response from the agent/llm Example

  4. A TaskArtifactUpdateEvent is generated containing an Artifact which contains the text response from the agent/llm Example

  5. A final TaskStatusUpdateEvent is generated with the status Completed and no Message content. Example

My questions are as follows:

A) Why is the original query used as the "reason" (Message text) for the status update in (1)? This seems like it should be a reason for the status change (e.g. "the user query has been received"), not literally the user's query (though yes, technically the query is the reason for the status change)

B) Why is the response text included in (3) ? Again I'd expect the message content here to be an explanation of why the Status has changed, e.g. "agent is generating a response". (Also not entirely clear why there should be two Status update events when the status isn't actually changing, but i could see an argument for doing it if the reason/Message was changing....which in this case it sort of is, it goes from nothing to an actual value)

C) Ultimately the response text is also sent as a TaskArtifactUpdate, which is what I'd expect, but it's a single Artifact update, the code in question has no way to send multiple TaskArtifactUpdates such as you might expect if it were streaming chunks of response text back as the agent/llm generated them. Instead the implementation seems to rely on using multiple TaskStatusUpdateEvents(each containing a Message with a chunk of the response) to stream back chunks of response text, if it's going to do so at all, which doesn't seem like the right use of the protocol objects.

D) Somewhat related, the TaskResultAggregrator, from which the message content for the Artifact is retrieved, is not actually aggregating text, it only retains the last chunk of text it saw. This seems like it won't work if the response text was returned as chunks in a series of TaskStatusUpdateEvents because in that case the resulting Artifact will only contain that last chunk. Of course this code is also predicated on the underlying agent sending TaskStatusUpdateEvents to return the actual response text and not TaskArtifactUpdate events, which seems incorrect to me.

As evidence that at least some of this behavior is incorrect, I explored how this behaves in the non-streaming case and among other things I can clearly see that this behavior results in duplicated History text in the Task object that is returned to the client because of the response text being included in the TaskStatusUpdateEvent. The A2A library itself, when producing a non-streaming response, aggregates the various events that are being generated, which in this case means it includes in the history:

  • the incoming Message (user's original query)
  • the outgoing TaskStatusUpdateEvent from (1) which contains the identical Message object
  • the outgoing TaskStatusUpdateEvent from (5) containing the final response

in addition to the actual Artifact content which is derived from the TaskArtifactUpdate event.

This means that both the original query, and the final response, are duplicated in the resulting Task response. (The query is duplicated in the History, the final response appears in both History and the actual Artifact)

What I would expect the behavior to be:

  1. User submits a Message containing a query
  2. ADK responds with a TaskStatusUpdateEvent setting status to submitted with, optionally, some sort of message indicating "your request has been accepted"
  3. ADK begins seeing response content from the agent, sends a TaskStatusUpdateEvent setting the task to Working, optionally with some sort of message indicating "agent is generating your response"
  4. ADK begins responding with TaskArtifactUpdateEvents as it starts receiving chunks of text from the agent/llm, using the append field as needed.
  5. upon receiving the final chunk, it sends a final TaskArtifactUpdateEvent updating the artifact one last time with the lastChunk field set to true.
  6. ADK sends a final TaskStatusUpdateEvent setting the task status to complete with, optionally, a message indicating "response is complete"

In the non-streaming case, this would result in A2A emitting a single Task containing:

  • History: the original user query and all the TaskStatusUpdateEvent messages (none of which include the original user query)
  • Artifact: the response text, aggregated from the TaskArtifactUpdateEvent(s)

TLDR: I think that response content generated by the agent/llm should be emitted in one or more TaskArtifactUpdateEvents, not included as Message text on TaskStatusUpdateEvents and the behavior of the A2A server library in the non-streaming case(which still consumes A2A streaming events from the agent before turning them into a single A2A Task event) seems to confirm this expectation. You can follow the A2A request handler logic and how it aggregates events into a Single task for the non-streaming case here: https://github.com/a2aproject/a2a-python/blob/3bfbea9ec8d7982fa73eb12d8352a581307355dc/src/a2a/server/request_handlers/default_request_handler.py#L289-L400

My example includes no tool or sub-agent calls, but it would make sense to me that if those were present, they would result in additional TaskStatusUpdateEvents where the message content included the tool call request/responses, which I believe does already happen with the current implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    a2a[Component] This issue is related a2a support inside ADK.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions