发布边界:Maven Central 最新发布为
0.1.1(标签 v0.1.1,952cde2)。本文按当前源码说明;中心独立 HTTP 认证/TLS、PSOP 账号登录及通知结果状态无关处理是在该标签之后加入,尚未发布。使用这些能力需构建精确检视源码的独立版本,不能覆盖已发布坐标。源码 revision=0.1.0-SNAPSHOT 是开发坐标,不是更新的正式版本。
A2A-T 工作流执行引擎是一个 Java SDK,用于基于 A2A 协议和 A2A-T 电信扩展编排多智能体工作流。
引擎处理 A2A 信封、消息收发、流式响应、协商关联、认证和 TLS;宿主负责最终内容生成、语义校验和业务决策。
| 要求 | 版本 |
|---|---|
| JDK | 17+ |
| Maven | 3.6+ |
<dependency>
<groupId>net.openan.workflow.sdk</groupId>
<artifactId>workflow-engine</artifactId>
<version>0.1.1</version>
</dependency>0.1.1 已发布到 Maven Central;当前仓库构建版本为 0.1.0-SNAPSHOT。
整个集成过程分四步:定义工作流 -> 加载 AgentCard -> 实现 ControlPoint -> 执行。
完整可运行源码:HostQuickStart.java。 该源码参与编译,远端任务→本地汇总流程由 HostQuickStartTest 验证。 可复制到集成方工程,或在 IDEA 的 samples 模块运行,传入注册中心 URL、目标 AgentCard 名称、凭证路径。 下列片段解释同一套 API;AgentCard 加载方式二选一,Task-T 内容生成需要由业务实现。 每条非空业务条件都需要 onRoute 独立判断;这里使用无条件边,不依赖默认选路。
Workflow workflow = Workflow.builder()
.name("服务分析")
.steps(List.of(
WorkflowStep.builder()
.name("analyze")
.subtasks(List.of(
Task.builder()
.agent("Dispatched Agent")
.skill("analysis")
.description("分析当前请求")
.build()))
.next(List.of(
JumpCondition.builder()
.step("merge")
.condition("")
.build()))
.layer(0)
.build(),
WorkflowStep.builder()
.name("merge")
.stepType(StepType.SELF_LOOP) // 宿主智能体本地步骤,不发送 A2A-T
.subtasks(List.of(
Task.builder()
.agent("Host Agent")
.skill("aggregate")
.description("汇总结果")
.build()))
.next(List.of(
JumpCondition.builder()
.step("end")
.condition("")
.build()))
.layer(1)
.contextFrom(List.of("*"))
.build()
))
.build();// 方式一:从 JSON 文件加载
ObjectMapper mapper = new ObjectMapper()
.registerModule(new AgentCardJacksonModule());
AgentCard card = mapper.readValue(
new File("agentcard/my_agent.json"), AgentCard.class);
// 方式二:从注册中心拉取
HttpAuthProvider registryAuth = HttpAuthProvider.bearer(() -> System.getenv("REGISTRY_API_TOKEN"));
RegistryClient registry = new RegistryClient("https://127.0.0.1:5000", registryAuth, TlsPolicy.strict());
ObjectMapper cardMapper = new ObjectMapper().registerModule(new AgentCardJacksonModule());
List<AgentCard> cards = registry.fetchAgentCards().stream()
.map(raw -> cardMapper.convertValue(raw, AgentCard.class)).toList();RegistryClient 默认设置覆盖正文读取的 30 秒截止时间;可使用
new RegistryClient(url, registryAuth, TlsPolicy.strict(), Duration.ofSeconds(15)) 设置正值预算。
列表、单卡查询和注册均在发送前应用认证;凭据只放请求头,不放 URL。缺失或非法 Bearer Token 在本地拒绝。
interface ControlPoint {
CompletableFuture<MessageContent> onTask(TaskRequest request);
CompletableFuture<TaskResult> onSelfTask(TaskRequest request);
CompletableFuture<RouteDecision> onRoute(RouteRequest request);
CompletableFuture<NegotiationReply> onNegotiation(NegotiationRequest request);
}onTask 返回最终 parts/metadata/extensions,引擎封装发送,不再生成或改写内容。onSelfTask 返回本地 TaskResult;onRoute 对一条条件边独立返回放行或拒绝;onNegotiation 返回 Send 或 Stop。无条件边不调用 onRoute 并始终放行。未实现的回调明确失败, 不回显成功、不默认放行条件边、不自动同意。 字段与完整示例见 业务回调集成契约。
ControlPoint callbacks = ControlPoint.builder()
.onTask(request -> CompletableFuture.completedFuture(
MessageContent.text(request.getInstruction())))
.onSelfTask(request -> CompletableFuture.completedFuture(
TaskResult.success(List.of(Map.of(
"sourceResults", request.getWorkflowInput().upstreamResults())))))
.onRoute(request -> CompletableFuture.completedFuture(
RouteDecision.deny("Replace with host condition evaluation")))
.onNegotiation(request -> CompletableFuture.completedFuture(
new NegotiationReply.Stop("manual.required", "Manual confirmation required")))
.build();CompletableFuture<ExecutionResult> execution = ExecutePsop.builder()
.psop(workflow)
.agentCards(cards)
.controlPoint(callbacks)
.runtimeIntent("分析服务请求")
.lang("zh")
.credentialsConfigPath("credentials.json")
.sslVerify(true)
.onFinish((r, history) -> {
System.out.println("执行结果: " + r.isSuccess());
})
.execute();
try {
ExecutionResult result = execution.get(10, TimeUnit.MINUTES);
System.out.println(result.getStepOutputs());
} finally {
if (!execution.isDone()) execution.cancel(true);
}必填项:psop、controlPoint。其余配置项都有默认值。
若宿主传入已配置好的 engineClient,AgentCard、runtime、TLS 和 credentials 均由该 client 负责,不要再设置对应的
ExecutePsop.Builder 构建参数;混用会在启动执行前直接报错,避免配置被静默忽略。
引擎不读取 A2A-T .env,也不创建 LLM client。宿主智能体的业务回调需要 A2A-T 时,由宿主使用自有环境文件初始化 A2ATClient/A2ATServer,配置 provider/model/key/base URL 和 A2AT_LANGUAGE。样例中的 a2atEnvPath 不是引擎 builder 参数。被调度智能体凭据解密不与 LLM 配置耦合:内置凭据模式通过 WorkflowEngineClientConfig.builder().credentialEncryptionKey (key) 显式提供密钥, 再将配置好的 engineClient 传给 ExecutePsop。自定义 AuthProvider 自行管理 token 和配置。 测试使用当前 SDK SPI 的离线 provider,不覆盖模板,也不是生产失败兜底。
需要认证的智能体,提供 JSON 凭证文件:
{
"Dispatched Agent": {
"bearerAuth": {
"login_url": "https://agent.example.com/oauth/token",
"method": "POST",
"request_fields": {
"username": "service-account",
"password": "enc:<base64-iv>:<base64-ciphertext>"
},
"token_field": "accessSession",
"token_ttl": 3600
}
}
}- 加密密码使用
enc:<iv>:<ciphertext>格式,密钥来自A2AT_CRED_KEY - 也接受明文密码(不加
enc:前缀) - Token 自动缓存和刷新
凭证文件中的密码支持加密存储,避免明文泄露。
生成密钥
openssl rand -hex 32输出示例:
4f8a2b1c3d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b
生成密钥后由宿主安全保存,并显式传给 credentialEncryptionKey;下列环境变量也可由宿主读取:
A2AT_CRED_KEY=4f8a2b1c3d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b
实例隔离加密可调用 CredentialCrypto.encrypt(plaintext, keyHex);显式 key 优先且不会修改 JVM 全局属性。不要记录明文或密钥。
加密密码
先执行 mvn -pl workflow-engine -am "-Drevision=0.1.0" package,以下命令在仓库根目录运行,仅需 SDK jar 和 JDK。
set 是 Windows cmd 语法,PowerShell 应使用 $env:A2AT_CRED_KEY='...'。
以下参数仅用于演示;命令行口令/密钥可能进入终端历史和进程参数。生产应由集成方安全读取密钥并调用 Java 加密 API。
# 方式一:先设置环境变量
set A2AT_CRED_KEY=4f8a2b1c3d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b
java -cp workflow-engine/target/workflow-engine-0.1.0.jar dev.openan.workflow.engine.client.CredentialCrypto "Admin@123"
# 方式二:密钥作为第二个参数
java -cp workflow-engine/target/workflow-engine-0.1.0.jar dev.openan.workflow.engine.client.CredentialCrypto "Admin@123" 4f8a2b1c3d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b输出:
enc:uHQcTeKZMVNRM9Ga:o5vm4weRozBXBs04phrLq7j7+/yRVyDsrw==
将输出结果填入凭证 JSON 的 value 字段。
更换密钥
- 生成新密钥:
openssl rand -hex 32 - 更新集成方密钥存储及显式
credentialEncryptionKey,或 OS/JVM 的A2AT_CRED_KEY;引擎不自动加载.env - 用新密钥重新加密所有密码:
java -cp workflow-engine/target/workflow-engine-0.1.0.jar dev.openan.workflow.engine.client.CredentialCrypto "明文密码" 新密钥 - 将新的
enc:...结果更新到凭证 JSON 文件
.env文件不应提交到版本库,建议加入.gitignore。
当令牌由集成方或外部认证服务获取,或使用非标准认证方式时,实现 AuthProvider 接口。接口只有一个方法:
public interface AuthProvider {
void applyAuth(String agentName, AgentCard agentCard, Map<String, String> headers);
}每次发消息前都会调用 applyAuth,实现方在 headers 中设置所需认证头。
场景 1:企业 SSO / 外部 Token 服务
public class SsoAuthProvider implements AuthProvider {
private final SsoClient ssoClient;
public SsoAuthProvider(SsoClient ssoClient) {
this.ssoClient = ssoClient;
}
@Override
public void applyAuth(String agentName, AgentCard agentCard, Map<String, String> headers) {
String token = ssoClient.getToken(agentName);
headers.put("Authorization", "Bearer " + token);
}
}
// 注册
WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder()
.authProvider(new SsoAuthProvider(mySsoClient))
.sslVerify(true)
.build();场景 2:AgentCard 没声明 securitySchemes,但服务端要求认证
WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder()
.authProvider((agentName, agentCard, headers) -> {
headers.put("X-API-Key", "static-api-key-value");
})
.build();场景 3:自定义 Header 名称(非标准 Authorization)
WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder()
.authProvider((agentName, agentCard, headers) -> {
String token = refreshTokenIfNeeded(agentName);
headers.put("X-Auth-Token", token);
headers.put("X-Tenant-Id", "tenant-001");
})
.build();注意事项:
applyAuth每次发消息都会调用,内部可自行实现 token 缓存和刷新逻辑securitySchemes表示智能体支持的认证方式;securityRequirements表示当前对接强制要求的认证方式。securityRequirements为空时不启用内置凭证认证,但AuthProvider仍会被调用- 只配置
AuthProvider时,它可以作为唯一认证来源,即使securityRequirements非空 - 如果同时配了凭证文件和
AuthProvider,两者分别生成 Header 后合并;同名不同值会 fail-fast - 认证失败时(如 token 获取异常),抛出的异常会传播到
send()方法,请求会被拦截,不会发出
AgentCard 通过 capabilities.extensions 声明扩展点:
{
"name": "Dispatched Agent",
"capabilities": {
"streaming": true,
"extensions": [
{
"uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Task-T/v1",
"description": "结构化任务提示",
"required": false
},
{
"uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Negotiation-T/v1",
"description": "协商文本交换",
"required": false
},
{
"uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Authorization-T/v1",
"description": "授权操作",
"required": false
},
{
"uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Notification-T/v1",
"description": "通知订阅",
"required": false
}
]
},
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer"
}
},
"securityRequirements": [
{
"schemes": {
"bearerAuth": []
}
}
],
"supportedInterfaces": [
{
"protocolBinding": "HTTP+JSON",
"protocolVersion": "1.0",
"url": "https://127.0.0.1:26335/a2a/json"
}
]
}扩展 URI 必须与 A2A-T 定义完全一致。
securitySchemes 与 securityRequirements 都是可选字段。前者表示智能体支持的认证方式,后者表示当前对接强制要求的认证方式;
securityRequirements: [] 表示不启用内置凭证认证。
AgentCard 声明 Negotiation-T 只表示目标支持协商,不代表本次任务已经启用。是否允许协商由宿主业务在首轮最终内容中选择:
MessageContent outgoing = A2atMessages.from(generatedTask, parts)
.withExtension(A2ATExtension.NEGOTIATION_T.uri());引擎将 extensions 同时写入 A2A Message 和 A2A-Extensions 请求头。首轮 metadata 仍然只有 Task-T 正文;不要在收到
Propose 前伪造 Negotiation-T metadata。仅配置协商回调不会自动激活扩展,不允许协商的任务不要调用 withExtension。
只有远端 INPUT_REQUIRED 或无任务裸 message 携带有效 Negotiation-T Propose 才进入 onNegotiation(后者对应
A2A-T 先协商后建任务模式:按 contextId 与协商 id 关联,续发请求不带 taskId)。 终态不会重启协商,普通 INPUT_REQUIRED
明确报告不支持的交互;无任务裸 message 上的无效协商元数据同样显式失败,不会静默当作普通结果。 宿主自行校验、理解 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 内容,
引擎随后通过取消已知的非终态 A2A 任务完成生命周期清理。同一任务/会话/轮次的重复等待事件不会重复回调、重复提交;未变化状态通过
getTask 观察。
maxNegotiationExchanges 默认 3,是独立于 SDK context.maxRounds 的本地交互资源预算。 超时、预算耗尽、回调缺失均明确失败,不默认
Accept,也不自动生成 Abort。 Accept/Reject 的 SUBMITTED/WORKING ACK 仍需等待任务结果,不重发原命令。 业务发送 Abort 后,即使远端用
COMPLETED 确认,也不能判为任务成功。
对于 DAG 外的独立任务,直接调用任务客户端。每次调用使用新的 context,但等待和协商续发始终保持同一个远端 taskId。 可选的单次调用策略不会修改客户端级 ControlPoint。普通 A2A 内容可以直接发送;激活 Task-T 时必须携带 Task-T metadata, 且目标 AgentCard 必须声明该扩展。
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content);
CompletableFuture<SendMessageResult> sendTask(String agentName, MessageContent content,
NegotiationStrategy negotiationStrategy);本地交互无法继续且已知远端任务仍为非终态时,客户端先尽力取消远端任务,再以异常完成。标准 A2A 请求错误仍按异常传递, 不会伪装成业务任务失败。
CompletableFuture<SendMessageResult> sendAuthorization(String agentName, MessageContent content);
NotificationSubscription openNotification(String agentName, MessageContent content,
BiConsumer<NotificationSubscription, ReceivedMessage> listener);宿主智能体生成最终 Authorization-T/Notification-T 内容。授权与通知分别使用独立于任务客户端且彼此隔离的 transport/runtime/context。订阅监听器收到 handle 与完整 ReceivedMessage。任务或通知结果终态不关闭订阅;宿主取消订阅或退出时显式关闭 handle,流退出或异常、配置的空闲超时也会结束订阅。 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
空闲时间。
// 仅用于受控的本地诊断:跳过证书链校验,但仍校验主机名
.sslVerify(false)
// 生产环境:启用验证 + 自定义 CA 证书
.sslVerify(true)
.caCertsPath("/path/to/ca-certs.pem")
// 可选:mTLS 与 CRL。私钥支持 PKCS#8 PEM/DER;加密私钥需提供密码
.clientCertPath("/path/to/client-cert.pem")
.clientKeyPath("/path/to/client-key.pem")
.clientKeyPassword("change-me")
.crlPath("/path/to/revocations.crl")HTTP/JSON-RPC 的 TLS 策略只作用于当前客户端,不修改 JVM 全局主机名校验设置;关闭证书链校验时仍会加载 mTLS 客户端身份。生产环境应保持
sslVerify(true);自签证书通过 caCertsPath 建立信任。默认 gRPC runtime 在
sslVerify(false) 时使用 plaintext,因此不能同时配置 mTLS 或 crlPath,这些组合会 fail-fast。
将 PROTOCOL logger 设为 DEBUG,可查看实际传输边界的观测记录。 HTTP/JSON-RPC 记录 A2A SDK 处理后的实际序列化正文和应用头;
A2A-Version 只在真实请求有该头时出现,不为日志展示补造 Header。 gRPC 记录实际 metadata 和 protobuf 的 JSON 展示,不伪装成
HTTP JSON 报文。 JDK 自动网络头、HTTP/2 帧、TLS 密文及服务端字节不在此观测范围。
MODEL_PREVIEW 只是高层预览,默认关闭,不是协议证据。
logger.protocol.name=PROTOCOL
logger.protocol.level=DEBUG
logger.protocol.additivity=true
WORKFLOW_ENGINE_PROTOCOL_INCLUDE_BODY=true
WORKFLOW_ENGINE_PROTOCOL_MAX_BODY_CHARS=100000DEBUG 开启时正文观测默认开启;敏感部署可显式禁用。JVM 同名属性优先于环境变量。 认证头、Cookie、Token 及识别出的口令字段强制脱敏,不提供关闭脱敏的开关。 这是字段级脱敏,不能自动识别所有个人信息和业务敏感内容,生产应另定日志策略。 缓冲有界:原始收集器按该数值限制字节,输出文本按字符限制; 超限 SSE 帧整帧丢弃至下个分隔符并标记 dropped-capacity,禁用、截断、中断也有明确标记。 UTF-8 分片先组装再解码,日志观察失败不改变报文投递;文件引用不会为打印日志而下载。 requestId 关联单次调用;工作流调用还带 executionId/logicalTaskId/attempt、 agent/contextId/channel,已知远端任务时附 remoteTaskId,均为本地日志字段,不污染协议 metadata。
使用一个被调度智能体返回“INPUT_REQUIRED + 有效 Negotiation-T Propose”的受控测试。预期序列为:请求 → Propose → onNegotiation → 最终 Send/Stop → 任务终态。PROTOCOL 日志应显示实际观测到的请求/响应边界与关联字段,且不泄露凭据。AgentCard 声明 Negotiation-T 能力本身不会强制发起协商。
订阅执行事件实现实时监控:
EventCallback callback = new EventCallback() {
@Override
public void onEvent(String eventType, Map<String, Object> data) {
switch (eventType) {
case EventType.STEP_START -> System.out.println("步骤开始: " + data.get("step"));
case EventType.TASK_STATUS_CHANGED -> System.out.println(
data.get("agent") + " 状态: " + data.get("state"));
case EventType.NEGOTIATION_REQUEST -> System.out.println(
"协商请求来自 " + data.get("agent"));
case EventType.COMPLETE -> System.out.println("工作流执行完成");
}
}
};
ExecutePsop.builder()
.eventCallback(callback)
// ...常用事件类型:START、STEP_START、TASK_REQUEST、TASK_RESPONSE、
TASK_STATUS_CHANGED、STEP_COMPLETE、NEGOTIATION_REQUEST、
NEGOTIATION_RESOLVED、NEGOTIATION_FAILED、ROUTE_DECISION、
WORKFLOW_COMPLETE、COMPLETE、ERROR、CLOSE。
// 中心凭据与 A2A AuthProvider、智能体凭据独立。
TlsPolicy orchTls = TlsPolicy.strict(); // 私有 CA 可用 trustedCa("/external/certs/ca.pem")。
HttpAuthProvider orchAuth = new OrchestrationAccountAuth(
"https://orchestration.example.com", System.getenv("ORCH_USERNAME"),
System.getenv("ORCH_PASSWORD"), orchTls);
// 按意图搜索
List<WorkflowSearchResult> results = LoadPsop.search(
"https://orchestration.example.com", "分析服务请求", 5, orchAuth, orchTls);
// 按 ID 加载完整工作流
Workflow workflow = LoadPsop.load(
"https://orchestration.example.com", results.get(0).getWorkflowId(), orchAuth, orchTls);直接构造最终 MessageContent(parts, metadata, extensions),由宿主管理扩展内容的生成/校验。无需注册引擎 handler 或 SDK 实例。A2atMessages.from 是 A2A-T metadata 复制辅助;非 A2A-T 扩展也可直接提供 metadata 和激活 URI。引擎不会仅因 AgentCard 声明而自动生成内容。
TlsPolicy.strict() 默认校验证书链和主机名;私有或自签 CA 使用 TlsPolicy.trustedCa(caPemPath)。
TlsPolicy.insecure() 显式对当前管理面客户端关闭两项校验,等同 Python 的 verify=False,
打印 INSECURE_TLS 警告,不修改 JVM 全局默认值,也不关闭 Bearer 鉴权。
“免配本地证书”不表示 HTTPS 服务端不需要证书,也不能绕过要求客户端证书的 mTLS。
该非验证模式不能确认服务端身份,存在中间人风险,仅用于受控联调。
生产保持 true,给服务端配置匹配实际 URL 的 SAN,并让运行 JVM 信任其 CA;
mTLS 使用 TlsPolicy.mutualTls(ca, clientCert, pkcs8Key, password);
TlsPolicy.insecure(clientCert, pkcs8Key, password) 关闭对端校验但仍携带客户端身份。
被调度智能体的 caCertsPath 不会自动传给 LoadPsop。
LoadPsop 不修改 JVM 全局 SSLContext、默认 SocketFactory 或 HostnameVerifier;
被调度智能体 HTTP/JSON-RPC 的 false 仍仅跳过证书链校验。
原 LoadPsop 方法签名保留,但 accessToken 改用 Bearer 请求头,不再进入 URL。 原 RegistryClient boolean 构造仍为匿名调用,false 仍只关闭证书链校验; 新接入应使用显式的认证/TLS 构造,不要混淆两种语义。
HttpAuthProvider.bearer(tokenSupplier) 每次请求读取一次当前 Token。OAuth2 获取、缓存、刷新
由应用接入的 IAM 客户端负责,SDK 不会因 401 自动重放业务写请求。业务自定义认证实现
headers(URI),返回自定义请求头;大小写重复及保留的传输请求头会被拒绝。
HttpAuthProvider.none() 只用于服务明确允许匿名访问或使用 mTLS 身份的场景。
SpringSpnDemo 使用 ORCH_USERNAME、ORCH_PASSWORD(与登录页面相同的账号)、
ORCH_VERIFY_SERVER(默认 true)及可选 ORCH_CA_CERTS_PATH,不复用
A2A_SSL_VERIFY 或智能体/Order 凭据。密码通过外部 Secret 注入,不写入提交的
YAML 或命令行参数。共享账号提供器首次调用时向 POST /api/v1/auth/token
发送 {username, password} JSON,按 expires_in 缓存 access_token;到期后
重新登录。该第一方接口不是 OAuth2 密码授权或 refresh-token 服务。
只有 PSOP 加载/搜索在 401 后允许刷新凭据并最多重试一次;403、429、服务错误、
TLS/网络失败和登录拒绝均不重试。凭据限定在配置的编排中心 PSOP API,不进入
查询参数或 Cookie,不发往其他来源。登录和 PSOP 使用相同的中心 TLS 策略;
关闭服务端证书校验不关闭账号鉴权。HTTP 用于隔离联调,但会明文传输密码,
部署应优先使用校验对端的 HTTPS。多个执行共享同一 OrchestrationAccountAuth
实例,复用线程安全的会话缓存;中心账号不影响 Task-T 或 Notification-T 流。
外部管理的 Bearer 可单独设置 ORCH_API_TOKEN,不能与账号密码混用;
静态 Token/supplier 提供器默认不参与 401 自动重试。
动态或自定义认证可提供名为 orchestrationHttpAuthProvider 的 Spring bean;
mTLS 或应用自管 TLS 策略可提供名为 orchestrationTlsPolicy 的 TlsPolicy bean。
HostQuickStart 使用 REGISTRY_API_TOKEN 完成注册中心发现认证。
中心的 401/403、TLS/网络错误、无候选结果均不会改为执行另一条工作流。
--a2a.local-workflow-enabled=true 明确选择离线示例工作流,不访问中心,默认关闭,不作为生产兜底。
引擎只消费 AgentCard,不启动被调度智能体服务。宿主智能体负责 AgentCard 发现、端点可达性校验与开发测试资源。使用外部管理的被调度智能体时,不得在相同端点上启动本地测试服务。生产 AgentCard 和凭据应保存在仓库外部。
Demo 在打开独立协议通道和启动工作流之前,先查询每个被调度智能体中处于 SUBMITTED、WORKING、
INPUT_REQUIRED、AUTH_REQUIRED 状态的任务,再通过标准 A2A 任务接口取消当前认证身份可见的全部结果。
查询会跟随 nextPageToken,对扫描过程中发生状态变化的任务去重,并正确处理“查询后、取消前任务已进入终态”的竞态。
查询和取消使用独立的短生命周期认证传输,不复用工作流、授权或通知的生命周期。
清理默认开启并采用 fail-fast,避免遗留任务静默累积后触发容量错误。可通过
A2A_TASK_CLEANUP_ENABLED、A2A_TASK_CLEANUP_FAIL_FAST、A2A_TASK_CLEANUP_PAGE_SIZE(1–100)和
A2A_TASK_CLEANUP_MAX_TASKS 配置。任务查询受认证身份权限约束;若多个实例共用同一身份,Demo 可能取消其他实例创建的活跃任务。
应使用隔离身份,或在提供等价的任务归属清理策略后关闭该功能。
A2A HTTP+JSON 定义了 message:send、message:stream、tasks/{id}:cancel 和
tasks/{id}:subscribe 等动作端点,标准端点应始终保留。首选由 API 网关发布斜杠形式的公开路径,
并重写到后端标准路径,例如:
/gateway/service/a2a/json/message/send -> /a2a/json/message:send
/gateway/service/a2a/json/message/stream -> /a2a/json/message:stream
如果网关不能重写后端路径,可开启 starter 的兼容别名:
a2at:
server:
enabled: true
path-prefix: /a2a/json
slash-action-aliases-enabled: true此时网关可以原样转发 /message/send 和 /message/stream;同一配置还会开放
/tasks/{id}/cancel 和 /tasks/{id}/subscribe。如果网关转发前会去掉它管理的公开前缀,
不要把该前缀写进 path-prefix;只有应用实际收到该前缀时才需要配置。A2A-T SDK 不发送 HTTP 请求,
A2A Java REST transport 会在 AgentCard 基础 URL 后追加标准动作后缀。因此,必须发出斜杠路径的调用方
需要使用网关重写或传输适配器,仅修改 AgentCard 基础 URL 不够。
任务创建前和创建后的失败必须分开处理。请求在任务创建前被拒绝时,返回非 2xx HTTP 状态和标准
A2A google.rpc.Status JSON 错误信封:
{"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"}}]}}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 节点仍遵循声明的任一成功语义。
宿主智能体通过 TASK_RESPONSE 事件及时观察任务失败,或在 onFinish(result, events)
检查 result.history;onTask 是发送前准备消息的回调,不是失败重试回调。
引擎节点事件带 executionId;TASK_RESPONSE 和 history 还带逻辑 taskId。
宿主智能体决定如何向调用方暴露失败,但不得把失败执行转换为成功 artifact。常驻宿主以独立生命周期管理通知订阅。
日志职责:PROTOCOL 记录已观察到的请求/响应,按 requestId 关联并发报文; A2A_ERROR 记录协议拒绝,TASK_FAILED 记录执行 ID、节点、逻辑任务 ID、 智能体、错误码和原因,WORKFLOW_STOPPED 列出未执行节点。示例 TASK_RESPONSE 日志同时包含 contextId 与 executionId,供跨层定位。已识别的 A2A 错误使用 WARN 摘要, 未知异常保留 ERROR 堆栈;错误详情中的已知凭据及 Bearer/Basic 值脱敏。 日志开关、pretty 展示和观察回调不会改变任务结果。
| 接口/类 | 用途 |
|---|---|
ExecutePsop.Builder |
工作流执行入口 |
ControlPoint / DefaultControlPoint |
业务决策实现(onTask、onSelfTask、onRoute、onNegotiation 等) |
WorkflowEngineClient / DefaultWorkflowEngineClient |
工作流与独立任务交互 |
ExtensionSender / DefaultExtensionSender |
独立 Authorization-T 操作与 Notification-T 长连接订阅 |
A2ATransport |
共享通信层(A2A Java 客户端 runtime、认证、SSE 消费) |
WorkflowEngineClientConfig |
配置(SSL、认证、A2A-T、协商轮数、自定义 Handler) |
AuthProvider |
自定义认证 |
EventCallback / EventType |
事件回调 |
LoadPsop / RegistryClient |
工作流加载 / AgentCard 获取 |
Workflow / WorkflowStep / Task / JumpCondition |
工作流定义 |
ExecutionResult |
执行结果 |
SendMessageResult / TaskResult |
消息/任务响应 |