Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- Propagate workflow cancellation and close execution-owned resources exactly once.
- Anonymize orchestration access tokens and bound registry response deadlines.
- Preserve multi-value results from arbitrary terminal workflow nodes.
- Map pre-task failures from the standard A2A error envelope while keeping post-creation failures in task state.
- Publish a compiled host integration example and require sample regression tests.
- Keep documentation host-neutral; sample class and AgentCard identifiers remain unchanged.

Expand Down
20 changes: 14 additions & 6 deletions docs/en/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ runtime, diagnostic adapter, or explicit lifecycle requires them.
| `ProtocolResponses` | A2A event/result assembly helpers |
| `ClientEventMapper` | Stable event projection for callbacks and diagnostics |
| `WireLog` | Correlation context and protocol-observation facade |
| `RemoteProblemException` | Structured remote problem response (`status`, `title`, `detail`, `type`, `timestamp`) |
| `RemoteA2AErrorException` | Standard A2A HTTP error projection (`code`, `status`, `message`, typed details and safe headers) |

---

Expand Down Expand Up @@ -589,11 +589,11 @@ not add private negotiation-state keys to the wire message.

## Error Handling

- Top-level remote problem responses retain their fields in `RemoteProblemException`;
`findIn(Throwable)` finds it through wrappers. Workflow failures use `remote.problem.<status>`,
with details in history / TASK_RESPONSE rather than business outputs.
- Standard top-level A2A error envelopes retain their fields in `RemoteA2AErrorException`;
`findIn(Throwable)` also projects wrapped SDK errors. Workflow failures use `a2a.<reason>` or
`a2a.http.<status>`, with details in history / TASK_RESPONSE rather than business outputs.
Executor node events include executionId; task response events and history include the logical taskId.
See [integration guidance](INTEGRATION_GUIDE.md#14-remote-problem-responses) for propagation and logging.
See [integration guidance](INTEGRATION_GUIDE.md#14-a2a-errors-and-task-failures) for propagation and logging.

- Missing/null/exceptional/timed-out callbacks fail explicitly. Host SDK errors may be mapped to BusinessFailure with
safe code/details.
Expand Down Expand Up @@ -655,14 +655,22 @@ Auto-configures the following beans (all `@ConditionalOnMissingBean`, so you can
| `eventBusProcessor` | `MainEventBusProcessor` | Event bus processor |
| `requestHandler` | `RequestHandler` | Default request handler |
| `restHandler` | `RestHandler` | REST protocol handler |
| `a2aController` | `A2AController` | Spring MVC controller (`message:send` + `message:stream`) |
| `a2aController` | `A2AController` | Spring MVC controller for message and task endpoints |

### A2AController

Spring MVC controller that exposes A2A REST endpoints:

- `POST {path-prefix}/message:send` — blocking send
- `POST {path-prefix}/message:stream` — SSE streaming
- `GET {path-prefix}/tasks/{id}` — query a task
- `GET {path-prefix}/tasks` — list tasks with optional filters
- `POST {path-prefix}/tasks/{id}:cancel` — cancel a task
- `POST {path-prefix}/tasks/{id}:subscribe` — subscribe to task updates over SSE

HTTP+JSON responses use `application/a2a+json`; successful streaming responses use `text/event-stream`. When a stream
closes, the controller cancels its publisher subscription and notifies the SDK event consumer so that server-side
polling does not remain active.

### Usage

Expand Down
54 changes: 30 additions & 24 deletions docs/en/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,28 +549,34 @@ Cleanup is enabled and fail-fast by default so stale tasks cannot silently lead
the demo may cancel active tasks created by another installation; use an isolated identity or disable cleanup after
providing an equivalent ownership-aware policy.

## 14. Remote problem responses

A successful HTTP envelope does not guarantee a successful task: SSE data can contain a top-level
problem object such as `{"status":429,"detail":"Active task limit reached","type":""}`.
Top-level numeric status 400–599 with title/detail becomes `RemoteProblemException`, preserving
status, title, detail, type and timestamp. Both synchronous and streaming responses are checked.
Nested business data is not classified as a protocol error.
Detection is limited to bodies actually delivered by the SDK. A non-2xx HTTP response may be rejected
first as an HTTP/authentication error by the SDK or adapter, without exposing its problem body;
such failures retain the generic failure path rather than fabricating errorDetails.

The error fails the call without fabricating a successful task, invoking onNegotiation, or automatically
resubmitting it. For 400, inspect business inputs; for 429, inspect dispatched-agent capacity and active tasks or
subscriptions before deciding whether to retry. Negotiation still requires a valid Negotiation-T Propose.
Use `RemoteProblemException.findIn(error)` to inspect the cause chain (null means no problem found);
workflow execution follows its existing failure path.
Task results and execution history / TASK_RESPONSE events expose `remote.problem.400`,
`remote.problem.429`, etc. as errorCode, the reason as error, the five fields above as errorDetails,
and empty outputs. Known credential fields use the protocol logger's redaction rules;
unrecognized SDK exceptions still expose only the exception type.
Independent authorization/subscription failures do not determine the workflow result.
Pretty logging does not modify the received problem payload.
## 14. A2A errors and task failures

Keep failures before and after task creation separate. A request rejected before a task is created uses a
non-2xx HTTP response with the standard A2A `google.rpc.Status` JSON envelope:

```json
{"error":{"code":400,"status":"INVALID_ARGUMENT","message":"A required parameter is missing","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"INVALID_PARAMS","domain":"a2a-protocol.org","metadata":{"field":"port"}}]}}
```

`RemoteA2AErrorException` preserves the observed HTTP status, envelope code/status/message, typed details,
ErrorInfo reason/domain and safe response headers. `findIn(Throwable)` also projects typed A2A Java SDK
errors. The direct transport checks ordinary responses and defensively detects a top-level error envelope if
an SDK exposes a pre-stream rejection as SSE data. Nested `error` objects inside a task, message or artifact
remain business content.

If an SSE call closes before delivering any A2A event, the transport fails immediately instead of waiting for the
workflow send timeout. The underlying SDK does not expose the HTTP status of an empty streaming response, so this case
is reported as a transport/protocol failure rather than a fabricated HTTP error code.

Workflow history and TASK_RESPONSE expose a stable code such as `a2a.invalid_params`; unknown HTTP errors
fall back to `a2a.http.<status>`. `errorDetails` retains protocol facts and `retryAfter` when observed. Such an
error creates no successful output, does not invoke onNegotiation and is not retried automatically.

After a task has been created, a business execution failure is not an HTTP protocol error. The agent returns
HTTP 200 with a Task or status update in `TASK_STATE_FAILED`; the TaskStatus message, task metadata and
artifacts carry the available failure evidence. The engine marks the workflow task failed, keeps that evidence
in `receivedMessages`, and does not interpret an extension-specific result schema. Independent authorization
and subscription failures remain independent of workflow outcome.

For an `ALL_SUCCESS` step, one failed task does not retroactively cancel a peer call that was already dispatched: the
peer result or timeout is collected before the workflow returns `success=false`. Downstream steps are not invoked;
Expand All @@ -584,9 +590,9 @@ The host agent decides how to expose a failed execution to its caller. It must n
success artifact. A persistent host owns notification subscriptions independently of workflow results.

Logging responsibilities: PROTOCOL captures observed traffic, correlated by requestId rather than
adjacent log entries; REMOTE_PROBLEM reports transport rejection; TASK_FAILED identifies the execution,
adjacent log entries; A2A_ERROR reports a protocol rejection; TASK_FAILED identifies the execution,
step, logical task, agent, code and reason; WORKFLOW_STOPPED lists unexecuted steps.
Sample TASK_RESPONSE logs bridge contextId and executionId. Recognized remote problems use WARN summaries,
Sample TASK_RESPONSE logs bridge contextId and executionId. Recognized A2A errors use WARN summaries,
while unexpected exceptions retain ERROR stack traces. Known credentials and Bearer/Basic values are redacted.
Logging configuration, pretty display and observer callbacks do not determine task outcomes.

Expand Down
17 changes: 12 additions & 5 deletions docs/zh/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ Map<String, Object> normalized = AgentCardNormalizer.normalize(rawMap);
| `ProtocolResponses` | A2A 事件与结果组装辅助方法 |
| `ClientEventMapper` | 面向回调和诊断的稳定事件投影 |
| `WireLog` | 关联上下文与协议观测门面 |
| `RemoteProblemException` | 结构化远程问题响应(`status`、`title`、`detail`、`type`、`timestamp`) |
| `RemoteA2AErrorException` | 标准 A2A HTTP 错误投影(`code`、`status`、`message`、类型化详情及安全响应头) |

---

Expand Down Expand Up @@ -560,10 +560,10 @@ getReceivedMessages() 是保留层级的响应来源,getOutputs() 为便利投

## 错误处理

- 顶层远端 problem 错误通过 `RemoteProblemException` 保留;可用 `findIn(Throwable)` 查找包装异常中的原因。
工作流任务失败信息为 `remote.problem.<status>`,详情进入 history / TASK_RESPONSE,不作为业务 outputs。
- 顶层标准 A2A 错误信封通过 `RemoteA2AErrorException` 保留;`findIn(Throwable)` 也会投影包装的 SDK 错误。
工作流任务失败信息为 `a2a.<reason>` 或 `a2a.http.<status>`,详情进入 history / TASK_RESPONSE,不作为业务 outputs。
引擎节点事件携带 executionId,任务结果事件和 history 携带逻辑 taskId。
失败传播、并行步骤失败规则和日志职责见 [集成指南](INTEGRATION_GUIDE.md#14-远端错误响应)。
失败传播、并行步骤失败规则和日志职责见 [集成指南](INTEGRATION_GUIDE.md#14-a2a-错误与任务失败)。

- 回调异常/空值/超时明确失败;SDK 内容错误由宿主转换为 BusinessFailure,必要时保留安全的 code/details。
- maxNegotiationExchanges 耗尽或本地 Stop 只结束本地交互,不自动发送 Abort。业务 Send(Abort) 不算任务成功。
Expand Down Expand Up @@ -621,14 +621,21 @@ a2at:
| `eventBusProcessor` | `MainEventBusProcessor` | 事件总线处理器 |
| `requestHandler` | `RequestHandler` | 默认请求处理器 |
| `restHandler` | `RestHandler` | REST 协议处理器 |
| `a2aController` | `A2AController` | Spring MVC 控制器(`message:send` + `message:stream`) |
| `a2aController` | `A2AController` | 消息和任务端点的 Spring MVC 控制器 |

### A2AController

暴露 A2A REST 端点的 Spring MVC 控制器:

- `POST {path-prefix}/message:send` — 阻塞式发送
- `POST {path-prefix}/message:stream` — SSE 流式
- `GET {path-prefix}/tasks/{id}` — 查询任务
- `GET {path-prefix}/tasks` — 按可选条件分页查询任务
- `POST {path-prefix}/tasks/{id}:cancel` — 取消任务
- `POST {path-prefix}/tasks/{id}:subscribe` — 通过 SSE 订阅任务更新

HTTP+JSON 响应使用 `application/a2a+json`,成功建立的流式响应使用 `text/event-stream`。连接结束时,控制器会取消
Publisher 订阅并通知 SDK 停止事件消费,避免服务端轮询继续占用资源。

### 用法

Expand Down
43 changes: 22 additions & 21 deletions docs/zh/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,30 +514,31 @@ Demo 在打开独立协议通道和启动工作流之前,先查询每个被调
`A2A_TASK_CLEANUP_MAX_TASKS` 配置。任务查询受认证身份权限约束;若多个实例共用同一身份,Demo 可能取消其他实例创建的活跃任务。
应使用隔离身份,或在提供等价的任务归属清理策略后关闭该功能。

## 14. 远端错误响应
## 14. A2A 错误与任务失败

响应头成功不代表任务成功。HTTP 200 的 SSE data 也可能返回顶层错误对象,例如:
任务创建前和创建后的失败必须分开处理。请求在任务创建前被拒绝时,返回非 2xx HTTP 状态和标准
A2A `google.rpc.Status` JSON 错误信封:

```json
{"status":429,"detail":"Current active tasks have reached the maximum limit 10.","type":""}
{"error":{"code":400,"status":"INVALID_ARGUMENT","message":"缺少必填参数","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"INVALID_PARAMS","domain":"a2a-protocol.org","metadata":{"field":"port"}}]}}
```

通信层识别顶层数字 status 为 400–599 且携带 title/detail 的错误对象,
以 `RemoteProblemException` 保留 status、title、detail、type、timestamp。
同步响应和流式响应均检查;普通任务内容或 artifact 内嵌的同名字段不被当成协议错误。
该识别以 SDK 实际交付的正文为限:真实 HTTP 非 2xx 可能先被 SDK/适配器作为 HTTP 或认证错误拒绝,
不保证可取得 problem 正文;没有该正文时仍走原有通用失败路径,不伪造 errorDetails。
错误不生成成功结果、不触发 onNegotiation,也不自动重新提交请求。400 应核对业务参数;
429 应检查被调度智能体的容量、活跃任务和订阅,再由业务决定是否重试。
只有合法 Negotiation-T Propose 才进入协商回调。

直接调用客户端时可用 `RemoteProblemException.findIn(error)` 检查 cause 链(没有则返回 null);
工作流执行时沿现有失败路径报告错误:
任务结果及执行 history / TASK_RESPONSE 事件中的 errorCode 为 `remote.problem.400`、
`remote.problem.429` 等,error 包含原因,errorDetails 包含上述五个字段,outputs 为空。
错误内容中的已知凭据字段按协议日志的规则脱敏;未识别的 SDK 异常仍只暴露异常类型。
预置授权、通知订阅的错误仍由其独立生命周期处理,不决定工作流执行结果。
协议 pretty 日志仅用于展示,不改写原始收到的错误对象。
`RemoteA2AErrorException` 保留实际观察到的 HTTP 状态、信封 code/status/message、类型化 details、
ErrorInfo reason/domain 及安全响应头;`findIn(Throwable)` 也会投影 A2A Java SDK 的类型化错误。
直连传输检查普通响应,并防御性识别被 SDK 作为 SSE data 暴露的建流前顶层错误信封。
Task、Message 或 Artifact 内嵌的 `error` 对象仍属于业务内容,不会被误判为协议错误。

若 SSE 调用未产生任何 A2A 事件便结束,传输会立即失败,不再等待工作流发送超时。底层 SDK 不暴露空流响应的 HTTP
状态,因此该场景报告为传输/协议失败,不会虚构 HTTP 错误码。

工作流 history 和 TASK_RESPONSE 使用 `a2a.invalid_params` 等稳定错误码;未知 HTTP 错误回退为
`a2a.http.<status>`。`errorDetails` 保留协议事实及实际取得的 `retryAfter`。这类错误不生成成功输出、
不触发 onNegotiation,也不自动重试。

任务创建成功后的业务执行失败不是 HTTP 协议错误。被调度智能体返回 HTTP 200,并通过 Task 或
StatusUpdate 的 `TASK_STATE_FAILED` 表示失败;TaskStatus 消息、任务 metadata 和 artifact 携带失败证据。
引擎将工作流任务标记为失败,把证据保留在 `receivedMessages`,但不解释扩展协议的业务结果 schema。
独立授权和通知订阅的失败仍按各自生命周期处理,不决定工作流结果。

对于 `ALL_SUCCESS` 步骤,一个任务失败不会追溯取消已下发的并行任务;引擎在另一任务返回或超时后报告 `success=false`。后续步骤不执行,成功的并行结果保留在 history,不伪造整体成功结果。引擎不自动重试、排队或部分成功汇总;`ANY_SUCCESS` 节点仍遵循声明的任一成功语义。

Expand All @@ -547,9 +548,9 @@ Demo 在打开独立协议通道和启动工作流之前,先查询每个被调
宿主智能体决定如何向调用方暴露失败,但不得把失败执行转换为成功 artifact。常驻宿主以独立生命周期管理通知订阅。

**日志职责**:PROTOCOL 记录已观察到的请求/响应,按 requestId 关联并发报文;
REMOTE_PROBLEM 记录通信层识别的远端拒绝,TASK_FAILED 记录执行 ID、节点、逻辑任务 ID、
A2A_ERROR 记录协议拒绝,TASK_FAILED 记录执行 ID、节点、逻辑任务 ID、
智能体、错误码和原因,WORKFLOW_STOPPED 列出未执行节点。示例 TASK_RESPONSE 日志同时包含
contextId 与 executionId,供跨层定位。已识别的远端问题使用 WARN 摘要,
contextId 与 executionId,供跨层定位。已识别的 A2A 错误使用 WARN 摘要,
未知异常保留 ERROR 堆栈;错误详情中的已知凭据及 Bearer/Basic 值脱敏。
日志开关、pretty 展示和观察回调不会改变任务结果。

Expand Down
Loading
Loading