Skip to content
Closed
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
28 changes: 25 additions & 3 deletions docs/en/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Entry point for executing a PSOP workflow. Uses the Builder pattern.
| `psop(Workflow)` | required | - | PSOP workflow definition |
| `agentCards(List<AgentCard>)` | optional | `List.of()` | Cards for dispatched agents; required for remote steps unless a configured `engineClient` supplies transport |
| `controlPoint(ControlPoint)` | required | - | User decision implementation |
| `engineClient(WorkflowEngineClient)` | optional | null | Pre-configured client (null = auto-create) |
| `engineClient(WorkflowEngineClient)` | optional | null | Pre-configured client (null = auto-create); cannot be combined with AgentCard, runtime, TLS, or credentials construction settings |
| `runtimeIntent(String)` | optional | `""` | Natural-language intent for context assembly |
| `lang(String)` | optional | `"zh"` | Language hint (`"zh"` or `"en"`) |
| `credentialsConfigPath(String)` | optional | null | Path to credentials JSON file |
Expand Down Expand Up @@ -61,6 +61,8 @@ ExecutionResult result = ExecutePsop.builder()
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content,
NegotiationStrategy negotiationStrategy);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content,
NegotiationStrategy negotiationStrategy, EventCallback invocationEvents);
CompletableFuture<SendMessageResult> getTask(String agentName, String taskId);
CompletableFuture<ListTasksResult> listTasks(String agentName, ListTasksParams params);
CompletableFuture<SendMessageResult> cancelTask(String agentName, String taskId);
Expand All @@ -75,6 +77,9 @@ void close();
AgentCard, authentication and custom runtime as message dispatch. A list result is authorization-scoped:
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 Future returned by `subscribeToTask` represents the subscription result, not an independently
closeable SSE handle. Cancelling it does not guarantee transport termination; close the owning Client/Runtime
to release a non-terminal subscription.

`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
Expand Down Expand Up @@ -118,6 +123,13 @@ Authorization and notification accept host-generated final content. Use separate
their outcomes do not gate the workflow. openNotification registers a handle before I/O, and passes it plus
ReceivedMessage directly to the listener. acknowledgement() is the real ACK; timeout fails. close() requests closure;
completion() observes actual stream termination.
For compatibility, `heartbeat().lastEventAt()` keeps its old name but represents the last transport activity.
A standard SSE `: heartbeat` refreshes it without increasing `eventCount`, which counts decoded A2A business events only.
`lastBusinessEventAt()` returns the most recent business-event time.

`new DefaultExtensionSender(transport)` owns and closes the transport. Use
`DefaultExtensionSender.nonOwning(transport)` only when another component owns the transport, and then close subscriptions
and the transport explicitly.

### WorkflowEngineClientConfig

Expand All @@ -142,6 +154,9 @@ Builder-based configuration for the workflow engine client.
| `credentialsConfig` | `Map` | null | Inline credentials config; must match AgentCard security requirements |
| `maxNegotiationExchanges` | `int` | `3` | Local interaction budget, independent of SDK maxRounds |

`credentialsConfigPath` and `credentialsConfig` are mutually exclusive built-in credential sources;
configuring both fails when the configuration is built.

```java
WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder()
.sslVerify(true)
Expand Down Expand Up @@ -279,7 +294,7 @@ runtime, diagnostic adapter, or explicit lifecycle requires them.
| `A2ATExtension` | Canonical extension names and URIs |
| `A2ATransport` | Low-level transport, authentication, response assembly, and subscription lifecycle |
| `DefaultWorkflowEngineClient` | Default `WorkflowEngineClient` implementation |
| `DefaultExtensionSender` | Default `ExtensionSender` implementation backed by an `A2ATransport` |
| `DefaultExtensionSender` | Default `ExtensionSender`; its public constructor owns the transport, while `nonOwning` does not |
| `DefaultA2AJavaClientRuntime` | Default A2A Java SDK runtime for HTTP/JSON-RPC/gRPC |
| `CredentialCrypto` | AES-GCM credential encryption utility and command-line entry point |
| `EnvFileLoader` | Explicit `.env` parser for host-owned configuration |
Expand Down Expand Up @@ -365,6 +380,8 @@ Load and search PSOP workflows from the orchestration center.
```java
static Workflow load(String baseUrl, String psopId,
String accessToken, boolean sslVerify)
static Workflow load(String baseUrl, String psopId,
String accessToken, boolean sslVerify, LoadPsop.Timeouts timeouts)

static Workflow load(String baseUrl, String psopId)
```
Expand All @@ -377,6 +394,9 @@ GET `/api/v1/orchestrate/psop/{psop_id}`. Returns the full workflow with steps,
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN,
String accessToken, boolean sslVerify)
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN,
String accessToken, boolean sslVerify, LoadPsop.Timeouts timeouts)

static List<WorkflowSearchResult> search(
String baseUrl, String intent)
Expand All @@ -389,6 +409,8 @@ Explicit `false` skips certificate-chain and hostname verification on that orche
allowing development without a local CA file. The server still needs a TLS certificate; this does not bypass
mTLS, modify JVM-wide defaults, or change other clients' TLS policies. Production must verify trust and matching SANs.
This is distinct from the engine's southbound HTTP/JSON-RPC verification policy.
Connection and read timeouts both default to 30 seconds. Supply
`new LoadPsop.Timeouts(connectTimeout, readTimeout)` to set positive per-call values.

### RegistryClient

Expand Down Expand Up @@ -629,7 +651,7 @@ not add private negotiation-state keys to the wire message.

## Thread Safety

- The engine client is thread-safe. Concurrent collections are used internally.
- The default engine client supports concurrent tasks. Each workflow invocation binds its own negotiation strategy and event callback without mutating client defaults.
- `ControlPoint` implementations must be thread-safe if used from multiple workflow executions concurrently.
- `EventCallback.onEvent` is called from multiple threads (main + SSE worker threads). Use synchronization if needed.

Expand Down
2 changes: 1 addition & 1 deletion docs/en/BUSINESS_CALLBACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Each `next` entry is a `JumpCondition(step, condition)`; an empty condition make
```java
WorkflowStep diagnosis = WorkflowStep.builder()
.name("diagnosis")
.subtasks(List.of(Task.builder().agent("SPN Domain Agent City1").description("diagnose").build()))
.subtasks(List.of(Task.builder().agent("Scheduled Agent A").description("diagnose").build()))
.next(List.of(
new JumpCondition("notify", ""), // unconditional: always active, never reaches onRoute
new JumpCondition("hardware_recovery", "fault.hardware"),
Expand Down
10 changes: 10 additions & 0 deletions docs/en/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ try {

Required: `psop`, `controlPoint`. All other config items have defaults.

When the host supplies a configured `engineClient`, that client owns AgentCard, runtime, TLS, and credentials
construction. Do not also set the corresponding `ExecutePsop.Builder` options; conflicting settings fail before
execution instead of being silently ignored.

## 5. Configuration

### 5.1 .env File
Expand Down Expand Up @@ -429,6 +433,12 @@ transport/runtime/context independent of the task client and of one another. The
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.

`heartbeat().lastEventAt()` is the last transport activity. A standard SSE comment heartbeat such as `: heartbeat`
refreshes it without increasing `eventCount`, which counts decoded A2A business events only. Use
`lastBusinessEventAt()` for the last business-message time. Cancelling the Future returned by `subscribeToTask`
only stops the local wait; close `NotificationSubscription` for an independent subscription, or close its owning
client/runtime for a client-managed task subscription.

`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
`A2A_NOTIFICATION_ACK_TIMEOUT_SECONDS`. It does not control SSE idle time after acknowledgement.
Expand Down
23 changes: 20 additions & 3 deletions docs/zh/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
|------------------------------------------|------|-------------|---------------------------------|
| `psop(Workflow)` | 必填 | - | PSOP 工作流定义 |
| `agentCards(List<AgentCard>)` | 可选 | `List.of()` | 被调度智能体的 AgentCard;存在远程步骤且未传入已配置 `engineClient` 时必须提供 |
| `engineClient(WorkflowEngineClient)` | 可选 | null | 预配置客户端(null=自动创建) |
| `engineClient(WorkflowEngineClient)` | 可选 | null | 预配置客户端(null=自动创建);传入后不可再设置 AgentCard、runtime、TLS 或 credentials 构造参数 |
| `controlPoint(ControlPoint)` | 必填 | - | 用户决策实现 |
| `runtimeIntent(String)` | 可选 | `""` | 自然语言意图,用于上下文组装 |
| `lang(String)` | 可选 | `"zh"` | 语言提示(`"zh"` 或 `"en"`) |
Expand Down Expand Up @@ -61,6 +61,8 @@ ExecutionResult result = ExecutePsop.builder()
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content,
NegotiationStrategy negotiationStrategy);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content,
NegotiationStrategy negotiationStrategy, EventCallback invocationEvents);
CompletableFuture<SendMessageResult> getTask(String agentName, String taskId);
CompletableFuture<ListTasksResult> listTasks(String agentName, ListTasksParams params);
CompletableFuture<SendMessageResult> cancelTask(String agentName, String taskId);
Expand All @@ -74,6 +76,8 @@ void close();
`listTasks` 与 `cancelTask` 通过消息下发使用的同一 AgentCard、认证配置和自定义运行时暴露标准 A2A
任务管理操作。查询结果受认证身份权限约束,只包含当前身份可见的任务;调用方必须使用
`nextPageToken` 完成分页。关闭本地客户端或通知流不等同于取消远端任务。
当前 `subscribeToTask` 返回的 Future 表示订阅结果,不是底层 SSE 的独立关闭句柄;取消 Future
不保证终止传输。非终态订阅需要通过 owning Client/Runtime 的 `close()` 释放。

`sendTask` 在 DAG 外执行宿主提供的最终任务内容。每次调用使用新的 context,并复用工作流任务的发送、等待、查询和
Negotiation-T 闭环。重载方法接受本次调用独有的协商策略,不修改客户端级 ControlPoint。超时、缺少处理器或其他本地交互失败后,
Expand Down Expand Up @@ -107,6 +111,11 @@ NotificationSubscription openNotification(String agentName, MessageContent conte
授权和订阅接收宿主生成的最终内容。三类操作使用独立 transport/runtime/context,成功与否不阻断工作流。openNotification 先注册
handle 再开始 I/O,监听器直接收到 handle 和完整 ReceivedMessage。acknowledgement() 是实际 ACK;超时失败。close()
请求关闭,completion() 在流真正退出后完成。
`heartbeat().lastEventAt()` 为兼容保留旧名称,实际表示最后一次传输活动;标准 SSE `: heartbeat`
会刷新它,但不会增加只统计已解码 A2A 业务事件的 `eventCount`。`lastBusinessEventAt()` 返回最近业务事件时间。

`new DefaultExtensionSender(transport)` 默认拥有并在 `close()` 时关闭 transport;仅当 transport
由其他组件统一管理时使用 `DefaultExtensionSender.nonOwning(transport)`,并由调用方关闭订阅和 transport。

### WorkflowEngineClientConfig

Expand All @@ -131,6 +140,8 @@ handle 再开始 I/O,监听器直接收到 handle 和完整 ReceivedMessage。
| `credentialsConfig` | `Map` | null | 内联凭证配置;AgentCard 声明安全要求时必须匹配 |
| `maxNegotiationExchanges` | `int` | `3` | 本地交互预算,独立于 SDK maxRounds;耗尽仅本地失败 |

`credentialsConfigPath` 与 `credentialsConfig` 是两种互斥的内置凭据来源,同时设置会在构建配置时失败。

```java
WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder()
.sslVerify(true)
Expand Down Expand Up @@ -259,7 +270,7 @@ Map<String, Object> normalized = AgentCardNormalizer.normalize(rawMap);
| `A2ATExtension` | 规范扩展名称与 URI |
| `A2ATransport` | 底层传输、认证、响应组装与订阅生命周期 |
| `DefaultWorkflowEngineClient` | `WorkflowEngineClient` 默认实现 |
| `DefaultExtensionSender` | 基于 `A2ATransport` 的 `ExtensionSender` 默认实现 |
| `DefaultExtensionSender` | 基于 `A2ATransport` 的 `ExtensionSender` 默认实现;公开构造器拥有 transport,`nonOwning` 工厂不拥有 |
| `DefaultA2AJavaClientRuntime` | HTTP/JSON-RPC/gRPC 的默认 A2A Java SDK runtime |
| `CredentialCrypto` | AES-GCM 凭据加密工具与命令行入口 |
| `EnvFileLoader` | 显式加载宿主自有 `.env` 配置 |
Expand Down Expand Up @@ -343,6 +354,8 @@ public class EventCallback {
```java
static Workflow load(String baseUrl, String psopId,
String accessToken, boolean sslVerify)
static Workflow load(String baseUrl, String psopId,
String accessToken, boolean sslVerify, LoadPsop.Timeouts timeouts)

static Workflow load(String baseUrl, String psopId)
```
Expand All @@ -355,6 +368,9 @@ GET `/api/v1/orchestrate/psop/{psop_id}`。返回完整工作流(含步骤、
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN,
String accessToken, boolean sslVerify)
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN,
String accessToken, boolean sslVerify, LoadPsop.Timeouts timeouts)

static List<WorkflowSearchResult> search(
String baseUrl, String intent)
Expand All @@ -366,6 +382,7 @@ LoadPsop 的简便重载默认 `sslVerify=true`,使用 JVM 信任库并校验
显式传 `false` 时,仅该编排中心 HTTPS 连接跳过证书链和主机名校验,可免配本地 CA 文件联调。
服务端仍须提供 HTTPS 证书;此开关不绕过 mTLS,也不修改其他客户端或 JVM 的全局 TLS 策略。
生产环境必须保持验证并配置正确的服务端 SAN 与信任库;本设置不是引擎南向 HTTP/JSON-RPC 的 TLS 策略变更。
连接与读取超时默认均为 30 秒;可通过 `new LoadPsop.Timeouts(connectTimeout, readTimeout)` 为单次调用设置正值。

### RegistryClient

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

## 线程安全

- 引擎客户端线程安全,内部使用并发集合。
- 默认引擎客户端可并发执行任务;工作流为每次调用绑定独立协商策略和事件回调,不改写客户端默认回调。
- `ControlPoint` 实现若在多工作流并发执行中使用,需自行保证线程安全。
- `EventCallback.onEvent` 从多个线程调用(主线程 + SSE 工作线程),需要时使用同步。

Expand Down
2 changes: 1 addition & 1 deletion docs/zh/BUSINESS_CALLBACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ RouteDecision.allow (reason) 激活该边,或返回 RouteDecision.deny (reason
```java
WorkflowStep diagnosis = WorkflowStep.builder()
.name("diagnosis")
.subtasks(List.of(Task.builder().agent("SPN Domain Agent City1").description("diagnose").build()))
.subtasks(List.of(Task.builder().agent("被调度智能体 A").description("diagnose").build()))
.next(List.of(
new JumpCondition("notify", ""), // 无条件:始终激活,不经 onRoute
new JumpCondition("hardware_recovery", "fault.hardware"),
Expand Down
8 changes: 8 additions & 0 deletions docs/zh/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ try {

必填项:`psop`、`controlPoint`。其余配置项都有默认值。

若宿主传入已配置好的 `engineClient`,AgentCard、runtime、TLS 和 credentials 均由该 client
负责,不要再设置对应的 `ExecutePsop.Builder` 构建参数;混用会在启动执行前直接报错,避免配置被静默忽略。

## 5. 配置

### 5.1 .env 文件
Expand Down Expand Up @@ -407,6 +410,11 @@ NotificationSubscription openNotification(String agentName, MessageContent conte
transport/runtime/context。订阅监听器收到 handle 与完整 ReceivedMessage,在宿主定义的终态事件上关闭。
handle.acknowledgement() 和 completion() 分别表示 ACK 和真实流退出,两者都不是工作流前提。

`heartbeat().lastEventAt()` 表示最后一次传输活动;标准 SSE comment 心跳(如 `: heartbeat`)会刷新该时间,
但不会增加只统计已解码 A2A 业务事件的 `eventCount`。需要判断最后一条业务消息时间时使用
`lastBusinessEventAt()`。取消 `subscribeToTask` 返回的 Future 只停止本地等待;结束独立订阅应关闭
`NotificationSubscription`,结束客户端管理的任务订阅则关闭其 client/runtime。

`WorkflowEngineClientConfig.notificationAckTimeoutSeconds` 控制首次订阅 ACK 的等待时间,默认 300 秒;Spring 示例可通过
`a2a.notification-ack-timeout-seconds` 或环境变量 `A2A_NOTIFICATION_ACK_TIMEOUT_SECONDS` 覆盖。该参数不控制 ACK 后的 SSE
空闲时间。
Expand Down
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<jackson.version>2.22.0</jackson.version>
<slf4j.version>2.0.18</slf4j.version>
<junit.version>5.10.2</junit.version>
<grpc.version>1.81.0</grpc.version>
<log4j2.version>2.24.3</log4j2.version>
<spring-boot.version>3.3.10</spring-boot.version>
<central.plugin.version>0.11.0</central.plugin.version>
Expand All @@ -71,6 +72,14 @@

<dependencyManagement>
<dependencies>
<!-- Declare test-library management first so protocol SDK BOMs cannot override it. -->
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-bom</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ public void onEvent(String type, Map<String, Object> data) {
}
});
notificationControlClient = new DefaultWorkflowEngineClient(notificationTransport);
authorizationSender = new DefaultExtensionSender(authorizationTransport);
notificationSender = new DefaultExtensionSender(notificationTransport);
authorizationSender = DefaultExtensionSender.nonOwning(authorizationTransport);
notificationSender = DefaultExtensionSender.nonOwning(notificationTransport);
}

private void teardown() {
Expand Down
Loading