a2aproject / a2aproject/a2a-dotnet

A2AServer streaming persistence failures are converted to normal EOF

Ouverte
#495 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
Langage dominant
C#
Étoiles
262
Forks
64
Merge moyen
5 j 2 h
PR mergées (30 j)
31

Description

## 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.

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.