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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [Unreleased]

- Add `WorkflowEngineClient.sendTask(...)` for task execution outside a DAG. It reuses the normal
task state machine, keeps one task/context through Negotiation-T, and supports a per-call
`NegotiationStrategy`. Local interaction failures cancel a known non-final remote task before
releasing the conversation. The internal workflow dispatch operation and the former
`sendMessage(...)` alias are no longer part of the public `WorkflowEngineClient` contract.
- Increase the default Notification-T acknowledgement timeout from 5 seconds to 5 minutes and expose the sample Spring
setting as `a2a.notification-ack-timeout-seconds` /
`A2A_NOTIFICATION_ACK_TIMEOUT_SECONDS`.
Expand Down
21 changes: 17 additions & 4 deletions docs/en/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ ExecutionResult result = ExecutePsop.builder()
### WorkflowEngineClient

```java
CompletableFuture<SendMessageResult> dispatch(TaskRequest request, MessageContent content, ControlPoint callbacks);
CompletableFuture<SendMessageResult> sendMessage(String agentName, MessageContent content);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content,
NegotiationStrategy negotiationStrategy);
CompletableFuture<SendMessageResult> getTask(String agentName, String taskId);
CompletableFuture<ListTasksResult> listTasks(String agentName, ListTasksParams params);
CompletableFuture<SendMessageResult> cancelTask(String agentName, String taskId);
Expand All @@ -75,7 +76,18 @@ AgentCard, authentication and custom runtime as message dispatch. A list result
it contains only tasks visible to the authenticated identity. Callers must paginate with
`nextPageToken`; cancellation is not implied by closing a local client or notification stream.

The executor calls dispatch; onTask does not send. Content is final parts/metadata/extensions. The engine manages
`sendTask` executes final host-provided content outside a DAG. Every call has a fresh context and
uses the same send, wait, task-query and Negotiation-T loop as workflow execution. The overload takes
a per-call strategy without changing the client-wide ControlPoint. If a known remote task remains
non-final after a timeout, missing handler or other local interaction failure, the client attempts
to cancel it before completing exceptionally. `sendTask` is the only public task-submission name;
protocol-runtime implementations may still use the A2A operation name `sendMessage` internally.

Plain A2A content does not require Task-T. When Task-T is activated, its metadata must be present and
the target AgentCard must declare Task-T support.

After `onTask` returns, the executor calls `sendTask` internally; `onTask` does not send. Content is final
parts/metadata/extensions. The engine manages
envelopes and interactions, never instantiates A2ATClient or generates content from AgentCard declarations. Template
queries and generation belong to the host SDK.

Expand All @@ -86,7 +98,8 @@ obtain the received context; reply with the same id, round and maxRounds. The la
Do not call nextRound for an ending reply or return a new Propose.

Return `new NegotiationReply.Send(content)` to send that exact content. Return `new NegotiationReply.Stop(code, reason)`
to stop locally without a generated Abort. Repeated task/session/round events do not repeat the callback or submission.
to stop without generating Abort content; the engine then cancels the known non-final A2A task as lifecycle cleanup.
Repeated task/session/round events do not repeat the callback or submission.
Unchanged waiting state is observed with getTask.
`maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout,
exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in
Expand Down
12 changes: 10 additions & 2 deletions docs/en/BUSINESS_CALLBACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ obtain the received context; reply with the same id, round and maxRounds. The la
Do not call nextRound for an ending reply or return a new Propose.

Return `new NegotiationReply.Send(content)` to send that exact content. Return `new NegotiationReply.Stop(code, reason)`
to stop locally without a generated Abort. Repeated task/session/round events do not repeat the callback or submission.
to stop without generating Abort content; the engine then cancels the known non-final A2A task as lifecycle cleanup.
Repeated task/session/round events do not repeat the callback or submission.
Unchanged waiting state is observed with getTask.
`maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout,
exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in
Expand Down Expand Up @@ -204,11 +205,18 @@ output comes as JSON in outputs; natural-language output calls for text matching
Callbacks for different conditional edges may run concurrently; do not hold shared mutable current-task state. Each
workflow task activation
(preparation and dispatch combined) is bounded by the client timeout (default sendTimeoutSeconds=600). Routing has a
callback timeout; dispatch/negotiation additionally has its own total wait deadline. Cancellation/timeout prevents late
callback timeout; task interaction/negotiation additionally has its own total wait deadline. Cancellation/timeout prevents late
sends; it does not automatically stop external business/LLM work. Hosts own cancellation of their resources. Synchronous
callback entry points must return promptly; place blocking operations in an asynchronous executor.
Missing/null/exceptional callbacks fail; uncertain sends are not blindly retried.

### 6.1 Standalone task

Use `WorkflowEngineClient.sendTask(agentName, finalContent)` for a task outside the DAG. The overload
with `NegotiationStrategy` supplies only that call's negotiation decisions. The host still owns
content generation and semantic validation; the engine preserves the remote task/context, waits for
a final state and cleans up a known non-final task when local interaction cannot continue.

## 7. Independent extensions

ExtensionSender.sendAuthorization(agentName, finalContent) returns CompletableFuture<SendMessageResult>.
Expand Down
8 changes: 5 additions & 3 deletions docs/en/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ entry point.
graph TD
L2["Layer 2 - Orchestration<br/>execute_psop / ExecutePsop<br/>lifecycle, event stream, cancellation, onFinish persistence"]
L1["Layer 1 - Traversal<br/>WorkflowExecutor<br/>DAG walk, parallel dispatch, context assembly, routing"]
L0["Layer 0 - Communication<br/>A2ATransport + two facades<br/>WorkflowEngineClient (workflow send) | ExtensionSender (independent operations)"]
L0["Layer 0 - Communication<br/>A2ATransport + two facades<br/>WorkflowEngineClient (workflow / standalone tasks) | ExtensionSender (independent operations)"]
F["Foundation - Decision<br/>ControlPoint<br/>user-implemented business decisions"]

L2 --> L1 --> L0
Expand Down Expand Up @@ -204,7 +204,8 @@ callback budget expires or the run is cancelled is ignored and never sent to the
### 3.1 Layer 0 - Communication

A2ATransport uses A2A SDK REST, JSON-RPC and gRPC bindings for authentication, delivery and complete response assembly.
WorkflowEngineClient accepts MessageContent and manages task association and negotiation continuation. ExtensionSender
WorkflowEngineClient accepts MessageContent for workflow or standalone tasks and manages task association, waiting,
negotiation continuation and non-final task cleanup. ExtensionSender
provides independent sendAuthorization and openNotification operations. The implementations are reusable; task,
authorization and notification do not share transport/runtime/context instances. ProtocolResponses assembles artifact
deltas by identity and ReceivedMessage preserves metadata at each level.
Expand Down Expand Up @@ -267,7 +268,8 @@ obtain the received context; reply with the same id, round and maxRounds. The la
Do not call nextRound for an ending reply or return a new Propose.

Return `new NegotiationReply.Send(content)` to send that exact content. Return `new NegotiationReply.Stop(code, reason)`
to stop locally without a generated Abort. Repeated task/session/round events do not repeat the callback or submission.
to stop without generating Abort content; the engine then cancels the known non-final A2A task as lifecycle cleanup.
Repeated task/session/round events do not repeat the callback or submission.
Unchanged waiting state is observed with getTask.
`maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout,
exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in
Expand Down
3 changes: 2 additions & 1 deletion docs/en/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ obtain the received context; reply with the same id, round and maxRounds. The la
Do not call nextRound for an ending reply or return a new Propose.

Return `new NegotiationReply.Send(content)` to send that exact content. Return `new NegotiationReply.Stop(code, reason)`
to stop locally without a generated Abort. Repeated task/session/round events do not repeat the callback or submission.
to stop without generating Abort content; the engine then cancels the known non-final A2A task as lifecycle cleanup.
Repeated task/session/round events do not repeat the callback or submission.
Unchanged waiting state is observed with getTask.
`maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout,
exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in
Expand Down
28 changes: 22 additions & 6 deletions docs/en/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,23 +393,39 @@ obtain the received context; reply with the same id, round and maxRounds. The la
Do not call nextRound for an ending reply or return a new Propose.

Return `new NegotiationReply.Send(content)` to send that exact content. Return `new NegotiationReply.Stop(code, reason)`
to stop locally without a generated Abort. Repeated task/session/round events do not repeat the callback or submission.
to stop without generating Abort content; the engine then cancels the known non-final A2A task as lifecycle cleanup.
Repeated task/session/round events do not repeat the callback or submission.
Unchanged waiting state is observed with getTask.
`maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout,
exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in
SUBMITTED/WORKING remain pending and are observed without resending the command. A business-sent Abort is never
task success, even if the dispatched agent acknowledges it with COMPLETED.

For a task outside a workflow DAG, call the task client directly. It uses a fresh context but keeps
the same remote task identity through waiting and negotiation. The optional per-call strategy does
not change the client-wide ControlPoint. Plain A2A content is allowed; activated Task-T content must
include Task-T metadata and the target AgentCard must declare the extension.

```java
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content,
NegotiationStrategy negotiationStrategy);
```

If a known remote task is still non-final when local interaction cannot continue, the client makes
a best-effort cancellation before completing exceptionally. Standard A2A request errors remain
exceptional results and are not converted into business task failures.

```java
CompletableFuture<SendMessageResult> sendAuthorization(String agentName, MessageContent content);
NotificationSubscription openNotification(String agentName, MessageContent content,
BiConsumer<NotificationSubscription, ReceivedMessage> listener);
```

The host agent generates final Authorization-T/Notification-T content and calls these methods on separate
transport/runtime/context instances. The listener receives the handle and complete ReceivedMessage, and closes on the
host-defined terminal event. acknowledgement() and completion() separately represent ACK and actual stream exit;
neither is a workflow prerequisite.
The host agent generates final Authorization-T/Notification-T content. Authorization and notification each use a
transport/runtime/context independent of the task client and of one another. The listener receives the handle and
complete ReceivedMessage, and closes on the host-defined terminal event. acknowledgement() and completion() separately
represent ACK and actual stream exit; neither is a workflow prerequisite.

`WorkflowEngineClientConfig.notificationAckTimeoutSeconds` controls the initial subscription ACK wait and defaults to
300 seconds. The Spring sample exposes it as `a2a.notification-ack-timeout-seconds` and
Expand Down Expand Up @@ -635,7 +651,7 @@ Logging configuration, pretty display and observer callbacks do not determine ta
|--------------------------------------------------------|-------------------------------------------------------------------------|
| `ExecutePsop.Builder` | Workflow execution entry point |
| `ControlPoint` / `DefaultControlPoint` | Business decisions (onTask, onSelfTask, onRoute, onNegotiation, etc.) |
| `WorkflowEngineClient` / `DefaultWorkflowEngineClient` | Workflow send (sendMessage, auth, extensions) |
| `WorkflowEngineClient` / `DefaultWorkflowEngineClient` | Workflow and standalone task interaction |
| `ExtensionSender` / `DefaultExtensionSender` | Independent Authorization-T operations and Notification-T subscriptions |
| `A2ATransport` | Shared wire layer (A2A Java client runtime, auth, SSE consumer) |
| `WorkflowEngineClientConfig` | Configuration (TLS, auth, deadlines, executor limits, negotiation exchange budget) |
Expand Down
18 changes: 13 additions & 5 deletions docs/zh/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ ExecutionResult result = ExecutePsop.builder()
### WorkflowEngineClient

```java
CompletableFuture<SendMessageResult> dispatch(TaskRequest request, MessageContent content, ControlPoint callbacks);
CompletableFuture<SendMessageResult> sendMessage(String agentName, MessageContent content);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content,
NegotiationStrategy negotiationStrategy);
CompletableFuture<SendMessageResult> getTask(String agentName, String taskId);
CompletableFuture<ListTasksResult> listTasks(String agentName, ListTasksParams params);
CompletableFuture<SendMessageResult> cancelTask(String agentName, String taskId);
Expand All @@ -74,16 +75,23 @@ void close();
任务管理操作。查询结果受认证身份权限约束,只包含当前身份可见的任务;调用方必须使用
`nextPageToken` 完成分页。关闭本地客户端或通知流不等同于取消远端任务。

执行器内部调用 dispatch;onTask 不自行发送。内容是最终 parts/metadata/extensions,引擎只管理信封和交互,不创建 A2ATClient,不按
`sendTask` 在 DAG 外执行宿主提供的最终任务内容。每次调用使用新的 context,并复用工作流任务的发送、等待、查询和
Negotiation-T 闭环。重载方法接受本次调用独有的协商策略,不修改客户端级 ControlPoint。超时、缺少处理器或其他本地交互失败后,
如果已知远端任务仍处于非终态,客户端会先尝试取消,再以异常完成。`sendTask` 是公开任务提交的唯一名称;传输运行时内部仍可使用
A2A 标准动作名称 `sendMessage`。

普通 A2A 内容不要求 Task-T;一旦激活 Task-T,就必须同时携带对应 metadata,且目标 AgentCard 必须声明支持 Task-T。

`onTask` 返回后由执行器内部调用 `sendTask`;`onTask` 不自行发送。内容是最终 parts/metadata/extensions,引擎只管理信封和交互,不创建 A2ATClient,不按
AgentCard 声明生成内容。模板查询和生成接口请直接使用宿主 SDK。

只有远端 `INPUT_REQUIRED` 携带有效 Negotiation-T Propose 才进入 `onNegotiation`。 终态不会重启协商,普通 INPUT_REQUIRED
明确报告不支持的交互。 宿主自行校验、理解 Propose,并用自己的 A2A-T client 生成最终 Accept/Reject/Abort。 通过
`A2atMessages.contextOf(request.received())` 取得收到的上下文; 结束回复保持相同 id、round、maxRounds,最后允许的一轮仍可回答,不自行
nextRound 或返回新 Propose。

返回 `new NegotiationReply.Send(content)` 发送最终内容; 返回 `new NegotiationReply.Stop(code, reason)` 只在本地停止,不生成
Abort。 同一任务/会话/轮次的重复等待事件不会重复回调、重复提交;未变化状态通过 getTask 观察。
返回 `new NegotiationReply.Send(content)` 发送最终内容;返回 `new NegotiationReply.Stop(code, reason)` 时不生成 Abort 内容,
引擎随后通过取消已知的非终态 A2A 任务完成生命周期清理。同一任务/会话/轮次的重复等待事件不会重复回调、重复提交;未变化状态通过 getTask 观察。
`maxNegotiationExchanges` 默认 3,是独立于 SDK context.maxRounds 的本地交互资源预算。 超时、预算耗尽、回调缺失均明确失败,不默认
Accept,也不自动生成 Abort。 Accept/Reject 的 SUBMITTED/WORKING ACK 仍需等待任务结果,不重发原命令。 业务发送 Abort 后,即使远端用
COMPLETED 确认,也不能判为任务成功。
Expand Down
Loading