a2aproject / a2aproject/a2a-dotnet

A2AServer streaming persistence failures are converted to normal EOF

未关闭
#495 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
C#
星标
262
派生
64
平均合并
5 天 2 小时
30 天内合并 PR
31

描述

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

贡献指南

打开贡献指南

调研方向

The issue is in A2AServer.SendStreamingMessageAsync, specifically the try-catch block that yields break on any exception from ApplyEventAsync. ApplyEventAsync calls ITaskStore.GetTaskAsync and ITaskStore.SaveTaskAsync. Start by examining the streaming loop in the A2AServer class. Look for tests related to streaming persistence failures to understand the expected behavior. The fix involves modifying the exception handling to rethrow or propagate persistence failures instead of converting them to EOF.

由索引模型根据 Issue 内容生成。

评估

技术栈
csharp
领域
api, backend
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
描述清楚
新手友好度
45/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。