a2aproject / a2aproject/a2a-python

cancel() and producer-failure write a terminal state to the store but not to active subscriber streams

Abierto
#1,175 5 comentarios 0 reacciones 1 asignado Reclamado por @rohityan Ver en GitHub
component: server status:awaiting response
Lenguaje dominante
Python
Estrellas
2.1k
Forks
496
Merge medio
4 d 17 h
PR fusionados (30 d)
12

Descripción

Follow-up from the review of #1172 (raised by @astrogilda).

### What happens

`ActiveTask.cancel()` now closes a task out to a terminal `CANCELED` state when
the executor's `cancel()` is cleanup-only or the task was parked in a
non-terminal state (e.g. `input-required`). That terminal state is written
directly to the task store via `save_task_event`, because by that point both
the agent and subscriber event queues are already closed.

The consequence is that the terminal state reaches the **store** but not an
**active subscriber stream**. A client that was streaming the task
(`SubscribeToTask` / `message/stream`) receives the initial `Task` in `WORKING`
and then the stream ends; it never sees a `CANCELED` event and has to re-read
the task to learn the outcome. The push path does not fire either, since
`_update_task_state` only notifies on `PushNotificationEvent`.

### Why it was left out of #1172

At the point of the direct store write, both queues are already closed, so a
store write is the only option left. The same pattern already exists for the
producer-failure path (`active_task.py`, the `FAILED` write in
`_run_producer`'s `except Exception` arm), under a comment that says so. #1172
kept scope to the authorization fix (#1159) and the "cancel leaves an actionable
terminal state" fix (#1170); this stream-visibility gap is orthogonal.

### Proposed follow-up

Give a streaming or push-subscribed client a way to observe the terminal state
without polling. Options to weigh:

- Emit a synthetic terminal `TaskStatusUpdateEvent` to `_event_queue_subscribers`
before the queues are torn down, so an active subscriber sees the transition.
- Route the terminal store write through the push path so `push_sender` fires.

Both the `cancel()` CANCELED write and the producer-failure `FAILED` write share
this gap and should get the same treatment.

### Repro

Subscribe to a live task whose executor's `cancel()` is cleanup-only, cancel it,
and collect the events the subscriber receives: the stream yields the initial
`WORKING` task and ends, while `task_store.get` afterwards returns `CANCELED`.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.