a2aproject / a2aproject/a2a-dotnet

A2AServer streaming persistence failures are converted to normal EOF

Abierto
#495 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
C#
Estrellas
262
Forks
64
Merge medio
5 d 2 h
PR fusionados (30 d)
31

Descripción

## Summary

`A2AServer.SendStreamingMessageAsync` converts failures from `ApplyEventAsync` into normal end-of-stream. A task-store read or write failure can therefore appear to the caller as successful stream completion.

## Current behavior

The streaming loop catches every exception raised while applying an event and exits the iterator:

```csharp
try
{
await ApplyEventAsync(response, context!, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
A2ADiagnostics.ErrorCount.Add(1);
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
RecordException(activity, ex);
yield break;
}
```

`ApplyEventAsync` calls both `ITaskStore.GetTaskAsync` and `ITaskStore.SaveTaskAsync`. If either operation fails, consumers only observe EOF. This is indistinguishable from normal stream completion unless they perform a separate authoritative read, which may itself fail.

The behavior also applies when a persistence failure happens after one or more responses have already been emitted: the next iterator move returns `false` instead of throwing the persistence exception.

## Expected behavior

A failure to read or persist authoritative task state must not become normal EOF. The SDK should either:

1. rethrow the event-application exception from the async iterator; or
2. expose a host-configurable failure policy that allows authoritative persistence failures to propagate.

Caller-requested cancellation should continue to propagate according to the caller cancellation token rather than being classified only by exception type.

## Reproduction

1. Configure an `ITaskStore` whose `SaveTaskAsync` throws after receiving a streaming task event.
2. Call `A2AServer.SendStreamingMessageAsync` and enumerate the response stream.
3. Observe that enumeration completes normally instead of throwing the store exception.
4. Repeat with a failure after an earlier response has been emitted; the subsequent `MoveNextAsync` returns `false`.

The same result can be reproduced with a failing `GetTaskAsync` inside `ApplyEventAsync`.

## Impact

- Clients can interpret a failed authoritative write as successful streaming completion.
- The persisted task may remain stale or non-terminal.
- Hosts must add SDK-specific exception-capture wrappers to preserve failure semantics.

## Suggested validation

Add tests covering:

- first-event `GetTaskAsync` and `SaveTaskAsync` failure;
- persistence failure after at least one emitted response;
- internal `OperationCanceledException` while the caller token is not canceled;
- caller-requested cancellation with a canceled caller token;
- unchanged successful and empty-stream behavior.

## Downstream context

WorkIQ currently carries a temporary wrapper around the SDK iterator to recapture these failures for its legacy SDK-managed persistence path. Its newer protocol-neutral task-management path does not use the SDK complete-snapshot persistence loop, so this is being tracked as an SDK defect rather than a WorkIQ feature requirement.

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.