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
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# Changelog

## [Unreleased]
## [0.0.9] — 2026-09-14

- Make Negotiation-T activation host-owned. The business activates Negotiation-T per send via the new
`MessageContent.withExtension(...)`; the engine mirrors that selection into `message.extensions` and the
`A2A-Extensions` request header and validates that the target AgentCard declares the extension before dispatch.
An unsolicited negotiation (host did not activate Negotiation-T) fails fast instead of silently entering the
auto-loop.
- Accept Negotiation-T Propose carried on a taskless bare message (A2A-T pre-task negotiation, where the remote
proposes before creating any task): `onNegotiation` now also fires for a valid Propose without an
`INPUT_REQUIRED` task status. The exchange is correlated by contextId and negotiation id, and the follow-up send
carries no taskId. Invalid negotiation metadata on a bare message now fails explicitly instead of being silently
treated as a normal successful response.
- Converge non-functional formatting and javadoc drift between `main` and `dev` so the branches differ only in the
Eastcom platform integration.

## [0.0.8] — 2026-09-13

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ return final message content and own any A2A-T generation, schema, validation or

## Features

- **A2A-T Extension Support**: Task-T (structured task prompts), Negotiation-T (stateless auto negotiation loop),
- **A2A-T Extension Support**: Task-T (structured task prompts), Negotiation-T (host-activated auto negotiation loop),
Authorization-T (independent authorization operation), Notification-T (independent long-lived SSE subscription)
- **Content-neutral callbacks**: final MessageContent, complete ReceivedMessage, local multi-output TaskResult and
explicit NegotiationReply.Send/Stop
Expand All @@ -38,7 +38,7 @@ subscription remains open until the host-defined terminal event, explicit cancel

### 1. Add Maven dependency

The current engine release is `0.0.8` on [Maven Central](https://repo.maven.apache.org/maven2/net/openan/workflow/sdk/).
The current engine release is `0.0.9` on [Maven Central](https://repo.maven.apache.org/maven2/net/openan/workflow/sdk/).

A2A-T SDK `1.1.0` is published to Maven Central. Maven resolves it automatically; no SDK source checkout or local SDK
build is required. The engine depends only on
Expand All @@ -51,7 +51,7 @@ upgrade guidance.
<dependency>
<groupId>net.openan.workflow.sdk</groupId>
<artifactId>workflow-engine</artifactId>
<version>0.0.8</version>
<version>0.0.9</version>
</dependency>
```

Expand All @@ -61,7 +61,7 @@ For Spring Boot server-side integration:
<dependency>
<groupId>net.openan.workflow.sdk</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>0.0.8</version>
<version>0.0.9</version>
</dependency>
```

Expand Down
106 changes: 56 additions & 50 deletions docs/en/API_REFERENCE.md

Large diffs are not rendered by default.

28 changes: 19 additions & 9 deletions docs/en/BUSINESS_CALLBACKS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Business callback integration contract

Release `0.0.8` API; A2A-T SDK `1.1.0`, A2A Java `1.2.0.Final`. The engine owns DAG scheduling, standard A2A envelopes,
Release `0.0.9` API; A2A-T SDK `1.1.0`, A2A Java `1.2.0.Final`. The engine owns DAG scheduling, standard A2A envelopes,
auth, transport and task waiting. The host owns content, schemas, templates, semantic validation and any LLM calls.
The business contracts are independent of the transport runtime.

Expand Down Expand Up @@ -120,13 +120,22 @@ one. Sample initialization helpers are host-side examples, not engine dependenci

## 5. Negotiation

The host business explicitly decides whether the current task allows negotiation. An AgentCard declaration only says
that the target supports Negotiation-T. To allow it, call
`withExtension(A2ATExtension.NEGOTIATION_T.uri())` on the initial Task-T content. The engine then writes both
`message.extensions` and the `A2A-Extensions` request header without adding premature Negotiation-T metadata. Merely
implementing `onNegotiation` or supplying a `NegotiationStrategy` does not activate negotiation; the target must not
start Negotiation-T when it was not activated.

NegotiationRequest(task, originalSubmission, received, previousExchanges, remainingWait):
task is the original TaskRequest; originalSubmission is the exact initial content; received is the complete current
response; previousExchanges contains only this session's completed Exchange (received, reply); remainingWait is the
remaining local interaction deadline. There are no engine-defined business proposal schemas.

Only a remote `INPUT_REQUIRED` carrying valid Negotiation-T Propose enters `onNegotiation`. Terminal responses never
restart negotiation; ordinary `INPUT_REQUIRED` fails explicitly. The host validates/interprets the proposal and
Only a remote `INPUT_REQUIRED` or a taskless bare message carrying a valid Negotiation-T Propose enters
`onNegotiation` (the latter is the A2A-T pre-task mode: correlated by contextId and negotiation id, with taskId-less
follow-up sends). Terminal responses never restart negotiation; ordinary `INPUT_REQUIRED` fails explicitly. Invalid
negotiation metadata on a bare message also fails explicitly instead of being treated as a normal response. The host validates/interprets the proposal and
generates the final Accept/Reject/Abort with its own A2A-T client. Use `A2atMessages.contextOf(request.received())` to
obtain the received context; reply with the same id, round and maxRounds. The last allowed round can still be answered.
Do not call nextRound for an ending reply or return a new Propose.
Expand Down Expand Up @@ -162,7 +171,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("Scheduled Agent A").description("diagnose").build()))
.subtasks(List.of(Task.builder().agent("SPN Domain Agent City1").description("diagnose").build()))
.next(List.of(
new JumpCondition("notify", ""), // unconditional: always active, never reaches onRoute
new JumpCondition("hardware_recovery", "fault.hardware"),
Expand Down Expand Up @@ -205,17 +214,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; task interaction/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.
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

Expand Down
10 changes: 8 additions & 2 deletions docs/en/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,14 @@ generation.

Negotiation-T:

Only a remote `INPUT_REQUIRED` carrying valid Negotiation-T Propose enters `onNegotiation`. Terminal responses never
restart negotiation; ordinary `INPUT_REQUIRED` fails explicitly. The host validates/interprets the proposal and
The host business decides whether a task allows negotiation through the initial `MessageContent.extensions`. Neither
the AgentCard declaration nor the callback itself activates it. The engine only maps that selection to the message and
the `A2A-Extensions` request header, then manages subsequent negotiation correlation.

Only a remote `INPUT_REQUIRED` or a taskless bare message carrying a valid Negotiation-T Propose enters
`onNegotiation` (the latter is the A2A-T pre-task mode: correlated by contextId and negotiation id, with taskId-less
follow-up sends). Terminal responses never restart negotiation; ordinary `INPUT_REQUIRED` fails explicitly. Invalid
negotiation metadata on a bare message also fails explicitly instead of being treated as a normal response. The host validates/interprets the proposal and
generates the final Accept/Reject/Abort with its own A2A-T client. Use `A2atMessages.contextOf(request.received())` to
obtain the received context; reply with the same id, round and maxRounds. The last allowed round can still be answered.
Do not call nextRound for an ending reply or return a new Propose.
Expand Down
15 changes: 11 additions & 4 deletions docs/en/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ Add to your `pom.xml`:
<dependency>
<groupId>net.openan.workflow.sdk</groupId>
<artifactId>workflow-engine</artifactId>
<version>0.0.8</version>
<version>0.0.9</version>
</dependency>
```

Version `0.0.8` is published to Maven Central and contains the APIs documented in this guide.
Version `0.0.9` is published to Maven Central and contains the APIs documented in this guide.

The engine pulls in A2A protocol transports and a2a-t-core only. Host agents generating A2A-T content explicitly add
a2a-t-client; dispatched-agent services that validate received content also add a2a-t-server.
Expand Down Expand Up @@ -131,8 +131,15 @@ ExecutionResult result = executor.run().join();

### 6.1 Negotiation Auto-Loop

Only a remote `INPUT_REQUIRED` carrying valid Negotiation-T Propose enters `onNegotiation`. Terminal responses never
restart negotiation; ordinary `INPUT_REQUIRED` fails explicitly. The host validates/interprets the proposal and
When the host business allows negotiation for a task, call
`withExtension(A2ATExtension.NEGOTIATION_T.uri())` on the initial Task-T `MessageContent`. An AgentCard declaration or a
negotiation callback does not add the request header automatically; the engine only maps the business selection to
`message.extensions` and `A2A-Extensions`.

Only a remote `INPUT_REQUIRED` or a taskless bare message carrying a valid Negotiation-T Propose enters
`onNegotiation` (the latter is the A2A-T pre-task mode: correlated by contextId and negotiation id, with taskId-less
follow-up sends). Terminal responses never restart negotiation; ordinary `INPUT_REQUIRED` fails explicitly. Invalid
negotiation metadata on a bare message also fails explicitly instead of being treated as a normal response. The host validates/interprets the proposal and
generates the final Accept/Reject/Abort with its own A2A-T client. Use `A2atMessages.contextOf(request.received())` to
obtain the received context; reply with the same id, round and maxRounds. The last allowed round can still be answered.
Do not call nextRound for an ending reply or return a new Propose.
Expand Down
Loading