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
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ and full A2A-T extension support. Full A2A-T extension support and multi-protoco
extraction
- `ExtensionSender` / `DefaultExtensionSender`: one-shot pre-positioning facade for Authorization-T and Notification-T
(long-lived SSE subscription)
- `ControlPoint` / `ExtensionCallback` split: flow decisions (`onTask` / `onSelfTask` / `onRoute` / `onNegotiation`) and
reactive hooks (`onAuthorization` / `onNotification`) on separate interfaces
- `ControlPoint` / `ExtensionSender` split: flow decisions (`onTask` / `onSelfTask` / `onRoute` / `onNegotiation`) on `ControlPoint`; Authorization-T / Notification-T pre-positioning on `ExtensionSender`
- `NegotiationStrategy`: pluggable clarification strategy injected into
`DefaultControlPoint`
- `SELF_LOOP` step type for local task handling without an A2A-T message
Expand Down
65 changes: 39 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,43 +92,54 @@ System.out.println("Success: " + result.isSuccess());

## Architecture

```
Layer 2 - Orchestration ExecutePsop
| lifecycle, event stream, onFinish hook
Layer 1 - Traversal WorkflowExecutor
| DAG walk, parallel dispatch, context assembly, routing
Layer 0 - Transport WorkflowEngineClient / A2ATransport
A2A send, auth, extensions, SSL, SSE
```mermaid
graph TD
L2["Layer 2 — Orchestration<br/>ExecutePsop<br/>lifecycle, event stream, onFinish hook"]
L1["Layer 1 — Traversal<br/>WorkflowExecutor<br/>DAG walk, parallel dispatch, context assembly, routing"]
L0["Layer 0 — Transport<br/>WorkflowEngineClient / A2ATransport<br/>A2A send, auth, extensions, SSL, SSE"]
F["Foundation — Decision<br/>ControlPoint<br/>user-implemented business decisions"]

L2 --> L1 --> L0
L0 -.-> F
```

| Layer | Entry Point | Responsibility |
|-------|-------------|----------------|
| High | `ExecutePsop.Builder` | Event stream, lifecycle, `onFinish` persistence |
| Mid | `WorkflowExecutor` | DAG traversal, context assembly, ControlPoint dispatch |
| Low | `WorkflowEngineClient` / `A2ATransport` | A2A send, auth, extensions, SSL, SSE normalization |
| Foundation | `ControlPoint` | User-implemented business decisions (onTask, onSelfTask, onRoute, onNegotiation) |

## Package Structure

```mermaid
graph TD
root["dev.openan.workflow.engine"]
client["client<br/>A2A transport, auth, extensions"]
control["control<br/>User decision interfaces"]
core["core<br/>DAG traversal, context assembly"]
model["model<br/>Data models"]
registry["registry<br/>LoadPsop, RegistryClient"]
runner["runner<br/>ExecutePsop entry point"]

root --> client
root --> control
root --> core
root --> model
root --> registry
root --> runner
```
dev.openan.workflow.engine
├── client # A2A transport, auth, extensions (package-private internals)
│ ├── WorkflowEngineClient # Send facade interface
│ ├── DefaultWorkflowEngineClient # Send + Negotiation-T auto-loop
│ ├── ExtensionSender # One-shot pre-positioning (Auth-T, Notification-T)
│ ├── A2ATransport # Shared wire layer (runtime, auth, SSE)
│ ├── AuthProvider # Custom auth provider interface
│ ├── WorkflowEngineClientConfig # Builder config: SSL, auth, A2A-T
│ ├── LlmHelper # LLM utility (wraps a2a-t-sdk LLMClient)
│ └── CredentialCrypto # AES-256-GCM credential encryption
├── control # User-facing decision interfaces
│ ├── ControlPoint # onTask, onSelfTask, onRoute, onNegotiation
│ ├── DefaultControlPoint # Default routing implementation
│ ├── EventCallback # Event stream callback
│ └── NegotiationStrategy # Pluggable negotiation clarification
├── model # Data models (Workflow, Task, StepType, etc.)
├── registry # LoadPsop, RegistryClient
└── runner # ExecutePsop (entry point)
```

| Package | Key Classes | Description |
|---------|-------------|-------------|
| `client` | `WorkflowEngineClient`, `DefaultWorkflowEngineClient`, `ExtensionSender`, `A2ATransport`, `AuthProvider`, `AgentAuthManager`, `WorkflowEngineClientConfig`, `CredentialCrypto`, `AgentCardJacksonModule` | A2A transport, auth, extensions (package-private internals) |
| `control` | `ControlPoint`, `DefaultControlPoint`, `EventCallback`, `EventType`, `NegotiationStrategy` | User-facing decision interfaces |
| `core` | `WorkflowExecutor`, `ContextBuilder` | DAG traversal, context assembly |
| `model` | `Workflow`, `WorkflowStep`, `Task`, `TaskRequest`, `TaskResponse`, `StepType`, `JumpCondition`, `RouteDecision`, `ExecutionResult`, `SendMessageResult`, `TaskStatus`, `WorkflowSearchResult` | Data models |
| `registry` | `LoadPsop`, `RegistryClient` | PSOP loading and AgentCard registry |
| `runner` | `ExecutePsop` | Entry point for workflow execution |

> **Note:** `LlmHelper` lives in the **samples** module (`dev.openan.workflow.engine.examples`), not in the `client` package. The workflow engine itself does not call an LLM directly.

## Documentation

Expand All @@ -149,6 +160,8 @@ dev.openan.workflow.engine
- [Notification-T 设计分析](docs/zh/notification-t-design-analysis.md) - 长连接 SSE 设计选型
- [业务流](docs/zh/业务流.md) - SPN 跨城诊断业务流程
- [调用过程](docs/zh/调用过程.md) - 端到端报文交互示例
- [工作台集成指南](docs/zh/工作台集成指南.md) - 工作台 Spring Boot 集成完整指南
- [工作台 AgentScope 结合方案](docs/zh/工作台AgentScope结合方案.md) - AgentScope 框架结合方案

## Modules

Expand Down
150 changes: 150 additions & 0 deletions docs/en/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
|-----------------------------------|-------------------------------------------------|
| `dev.openan.workflow.engine.client` | A2A message transport, auth, extensions, config |
| `dev.openan.workflow.engine.control` | User decision points and event system |
| `dev.openan.workflow.engine.core` | DAG traversal engine and context assembly |
| `dev.openan.workflow.engine.model` | Data models (Workflow, Task, results) |
| `dev.openan.workflow.engine.registry` | PSOP loading and AgentCard registry |
| `dev.openan.workflow.engine.runner` | Entry point for workflow execution |
Expand Down Expand Up @@ -234,6 +235,55 @@ public interface A2AJavaClientRuntime {

A default implementation is provided. Implement this interface only if you need custom HTTP transport.

### ConversationScopedA2AJavaClientRuntime

Optional lifecycle callback for runtime implementations whose transport session spans multiple A2A requests (e.g. a gateway login that must stay alive across all negotiation rounds).

```java
public interface ConversationScopedA2AJavaClientRuntime {
void closeConversation(AgentCard agentCard, String contextId);
}
```

When the runtime also implements this interface, the engine calls `closeConversation` after the full send + negotiation cycle completes — not after each individual HTTP request. This allows gateway sessions to be released only after the logical conversation is done.

Implement alongside `A2AJavaClientRuntime`:

```java
public class MyGatewayRuntime
implements A2AJavaClientRuntime, ConversationScopedA2AJavaClientRuntime {
@Override
public Iterable<ClientEvent> sendMessage(...) { ... }

@Override
public void closeConversation(AgentCard agentCard, String contextId) {
// release gateway session
}

@Override
public void close() { ... }
}
```

### AgentCardJacksonModule

Jackson module for deserializing AgentCard JSON with security scheme normalization. Handles the OpenAPI-format `securitySchemes` / `securityRequirements` fields that the A2A SDK's strongly-typed `AgentCard` record expects.

```java
ObjectMapper mapper = new ObjectMapper()
.registerModule(new AgentCardJacksonModule());
AgentCard card = mapper.readValue(
new File("agentcard/my_agent.json"), AgentCard.class);
```

### AgentCardNormalizer

Utility that normalizes a raw `Map<String, Object>` (as returned by the Registry Center API) into the `AgentCard`-compatible format. Used internally by `RegistryClient.fetchAgentCards()`. Also available as a public static method for custom normalization:

```java
Map<String, Object> normalized = AgentCardNormalizer.normalize(rawMap);
```

---

## dev.openan.workflow.engine.control
Expand Down Expand Up @@ -300,6 +350,7 @@ Override to receive real-time execution events. Event types are defined in `Even
| `STEP_COMPLETE` | A workflow step completed |
| `TASK_REQUEST` | A task was dispatched to an agent |
| `TASK_RESPONSE` | A task response was received |
| `TASK_STATUS_CHANGED` | A task's status changed (pending → running → success/failed) |
| `AGENT_REQUEST` | A message was sent to an agent |
| `AGENT_RESPONSE` | A response was received from an agent |
| `AGENT_STATUS_UPDATE` | Agent SSE status update (SUBMITTED, WORKING, etc.) |
Expand All @@ -312,6 +363,7 @@ Override to receive real-time execution events. Event types are defined in `Even
| `AUTHORIZATION_RESOLVED` | Authorization decision was made |
| `NOTIFICATION` | Notification received from agent |
| `ROUTE_DECISION` | Route decision was made |
| `WORKFLOW_COMPLETE` | The workflow completed (all steps finished) |
| `START` | Workflow execution started |
| `COMPLETE` | Workflow execution completed successfully |
| `ERROR` | Workflow execution failed |
Expand Down Expand Up @@ -371,6 +423,27 @@ Map<String, Object> registerAgentCard(Map<String, Object> agentCard)

---

## dev.openan.workflow.engine.core

### WorkflowExecutor

Mid-layer DAG traversal engine. Walks the workflow steps, assembles upstream context via `ContextBuilder`, dispatches subtasks in parallel, applies step success policies (`ALL_SUCCESS` / `ANY_SUCCESS` / `SELF_LOOP`), and routes to the next step.

Not typically instantiated directly by SDK users — `ExecutePsop` wraps it internally. Exposed for advanced integrations that need to run the traversal layer without the runner's lifecycle management.

```java
WorkflowExecutor executor = new WorkflowExecutor(
workflow, controlPoint, engineClient,
eventCallback, runtimeIntent, lang);
ExecutionResult result = executor.run().join();
```

### ContextBuilder

Package-private helper that assembles the context message for each step by folding upstream step outputs according to `contextFrom` rules (`"*"` = all ancestors, specific names = selective inheritance). Not part of the public API surface.

---

## dev.openan.workflow.engine.model

### Workflow
Expand Down Expand Up @@ -403,6 +476,17 @@ Static factory: `Workflow.fromMap(Map<String, Object>)` parses from orchestratio
| `ANY_SUCCESS` | Any subtask success is sufficient |
| `SELF_LOOP` | The workflow agent handles the task locally via `onSelfTask`; no A2A-T message is sent. Success follows `ALL_SUCCESS`. |

### TaskStatus

Task lifecycle status, used in `TASK_STATUS_CHANGED` events for cross-SDK consistency with the Python SDK.

| Value | String | Description |
|-----------|------------|--------------------------------------|
| `PENDING` | `"pending"` | Task created, not yet started |
| `RUNNING` | `"running"` | Task in progress |
| `SUCCESS` | `"success"` | Task completed successfully |
| `FAILED` | `"failed"` | Task failed |

### Task

| Field | Type | Description |
Expand Down Expand Up @@ -511,3 +595,69 @@ Static factory: `Workflow.fromMap(Map<String, Object>)` parses from orchestratio
- Negotiation failures fall through after `maxNegotiationRounds`.
- Auth failures (401) are logged as `ERROR` and auth headers are not set; the request proceeds without auth.
- SSE stream errors after terminal events are logged at `DEBUG` level (expected behavior).

---

## spring-boot-starter Module

The `spring-boot-starter` module provides Spring Boot auto-configuration for the A2A **server** side (not the client/workflow side). When on the classpath of a Spring Boot web application, it auto-registers all A2A SDK server components as Spring beans.

### A2AProperties

Configuration properties prefixed with `a2at.server`:

| Property | Default | Description |
|----------|---------|-------------|
| `a2at.server.agent-card` | `classpath:agentcard.json` | Path to the AgentCard JSON file (classpath: or file: prefix supported) |
| `a2at.server.path-prefix` | `/a2a/json` | URL path prefix for A2A endpoints |

```yaml
a2at:
server:
agent-card: classpath:agentcard/my_agent.json
path-prefix: /a2a/json
```

### A2AAutoConfiguration

Auto-configures the following beans (all `@ConditionalOnMissingBean`, so you can override any):

| Bean | Type | Purpose |
|------|------|---------|
| `agentCard` | `AgentCard` | Loaded from `a2at.server.agent-card` path via Jackson |
| `a2aConfigProvider` | `A2AConfigProvider` | SDK configuration values |
| `taskStore` | `InMemoryTaskStore` | In-memory task storage |
| `eventBus` | `MainEventBus` | Event bus for SSE streaming |
| `queueManager` | `InMemoryQueueManager` | Event queue manager |
| `pushStore` | `PushNotificationConfigStore` | Push notification config storage |
| `agentExecutorPool` | `ExecutorService` | Thread pool for agent execution (8 threads, daemon) |
| `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

Spring MVC controller that exposes A2A REST endpoints:

- `POST {path-prefix}/message:send` — blocking send
- `POST {path-prefix}/message:stream` — SSE streaming

### Usage

The partner only needs to provide an `AgentExecutor` implementation:

```java
@Component
public class MyAgentExecutor implements AgentExecutor {
@Override
public ExecuteResult execute(ExecuteRequest request) {
// business logic
return ExecuteResult.builder()
.addTextPart("result text")
.build();
}
}
```

All other beans (AgentCard, RequestHandler, RestHandler, A2AController, etc.) are auto-configured.
12 changes: 3 additions & 9 deletions docs/en/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ concurrently, applies the step's success policy, and determines the next step (s

Step dispatch rules:

- Steps whose predecessors are all satisfied are collected and dispatched in parallel (`asyncio.gather` /
`CompletableFuture`), so steps at the same layer run concurrently.
- Steps whose predecessors are all satisfied are collected and dispatched in parallel (`CompletableFuture`), so steps at the same layer run concurrently.
- Subtasks within a step also run in parallel.
- `ALL_SUCCESS` - all subtasks must succeed.
- `ANY_SUCCESS` - the first successful subtask wins; the rest are cancelled.
Expand Down Expand Up @@ -115,11 +114,6 @@ Authorization-T and Notification-T are pre-positioning concerns handled via `Ext

---

## 4. A2A-T Extension Model
means a host that only cares about routing does not have to implement (or stub) authorization hooks, and vice versa.

---

## 4. A2A-T Extension Model

Four A2A-T extensions are supported. They divide into two groups by lifecycle.
Expand Down Expand Up @@ -269,7 +263,7 @@ sequenceDiagram

---

## 9. Dependencies
## 8. Dependencies


**Java SDK:** `org.a2aproject.sdk:a2a-java-sdk-client` (A2A protocol),
Expand All @@ -279,7 +273,7 @@ The SDK is standalone: it does not depend on the orchestration center.

---

## 10. Design Decisions Summary
## 9. Design Decisions Summary

1. **Shared transport, two facades** - wire machinery written once on
`A2ATransport`; `WorkflowEngineClient` and `ExtensionSender` each own one orchestration concern and delegate wire
Expand Down
21 changes: 7 additions & 14 deletions docs/en/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ Events come from three layers: the runner (lifecycle bracket), the executor (ste
| `step_start` | executor | Step begins | `step` |
| `task_request` | executor | A subtask is dispatched to `onTask`/`onSelfTask` | `step`, `agent`, `task` |
| `task_response` | executor | `onTask`/`onSelfTask` returned a `TaskResponse` | `step`, `agent`, `task`, `output` |
| `task_status_changed` | executor | Task status changed (pending → running → success/failed) | `step`, `agent`, `task`, `status` |
| `route_decision` | executor | Branch chosen | `step`, `next`, `reason` |
| `step_complete` | executor | Step finished | `step`, `results` |
| `workflow_complete` | executor | All steps finished | `history`, `step_outputs` |
| `agent_request` | engine client | Message sent to agent | `agent`, `request`, `metadata` |
| `agent_response` | engine client | Response from agent | `agent`, `response` |
| `agent_status_update` | engine client | Agent SSE status update | `agent`, `state`, `is_final` |
Expand Down Expand Up @@ -205,22 +207,13 @@ Passwords can be AES-GCM encrypted with `enc:<iv>:<ciphertext>` prefix. The decr
### 7.2 Custom AuthProvider

For non-standard auth (SSO, API keys, custom headers):

```java
WorkflowEngineClientConfig.builder()
.

authProvider((agentName, agentCard, headers) ->{
headers.

put("Authorization","Bearer "+mySsoToken);
headers.

put("X-Custom","value");
.authProvider((agentName, agentCard, headers) -> {
headers.put("Authorization", "Bearer " + mySsoToken);
headers.put("X-Custom", "value");
})
.

build();
.build();
```

### 7.3 Credential File Fields
Expand Down Expand Up @@ -300,7 +293,7 @@ subscriber and let the future complete.
## 11. Checklist

1. Add Maven dependencies
2. Implement `ControlPoint` (onTask + onSelfTask + onRoute)
2. Implement `ControlPoint` (at minimum `onTask` + `onRoute`; `onSelfTask` and `onNegotiation` have defaults)
3. Get AgentCards (from registry or JSON files)
4. Load Workflow (via `LoadPsop` or build your own)
5. Configure `.env` and credentials file
Expand Down
Loading
Loading