From 5a0ae2f46d5dcb2f4ed9320b55f48ef4b37f0836 Mon Sep 17 00:00:00 2001 From: Vernon Stinebaker Date: Mon, 28 Sep 2026 21:48:36 +0800 Subject: [PATCH 1/2] docs(multi-bot): document A2A worker URL base convention and transition trigger naming Both conventions were discovered while validating #48 end-to-end against nullclaw v0.3.0 and ZeroClaw: the A2A dispatcher appends /a2a to the configured worker URL (a full-path URL yields POST /a2a/a2a and a bare HTTP 404), and on_success/on_failure transition_to is applied as the pipeline FSM trigger name, so the trigger must exist on the task's current stage. --- docs/multi-bot-integration.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/multi-bot-integration.md b/docs/multi-bot-integration.md index 34e5a76..5fb290f 100644 --- a/docs/multi-bot-integration.md +++ b/docs/multi-bot-integration.md @@ -66,3 +66,24 @@ Bridge response is strict webhook-compatible JSON: Then register that bridge endpoint in NullBoiler with protocol `webhook`. Native pull-mode with `NullTickets` is documented in `docs/nulltickets-nullboiler-nullclaw.md`. + +## A2A worker conventions + +When a workflow uses `execution: "dispatch"` with `dispatch.protocol = "a2a"`: + +- **Worker URL is the base URL.** The dispatcher appends `/a2a` to the configured + worker URL itself. Configure `http://host:3000`, not `http://host:3000/a2a` — + the latter produces `POST /a2a/a2a` and fails with `HTTP 404`. +- The dispatcher sends A2A v0.3.0 JSON-RPC (`message/send`, `kind` discriminators, + `messageId`), requires the worker to accept it at `POST {base}/a2a`, and parses + the reply from the result's `parts[].text` (falling back to the legacy + `artifacts[].parts[].text` shape). + +## Workflow transition conventions + +`on_success.transition_to` and `on_failure.transition_to` are applied as the +pipeline FSM **trigger** name. Define the pipeline transition's `trigger` to +match the value used in the workflow (naming the trigger after its target state, +e.g. `trigger: "done"` for `to: "done"`, is the simplest convention). A trigger +name that does not exist on the task's current stage is rejected by the tracker +and the task re-enters its retry cycle. From 0f17be04e7d86b50acf452348a561a43db5c5de7 Mon Sep 17 00:00:00 2001 From: Vernon Stinebaker Date: Sun, 4 Oct 2026 13:01:16 +0800 Subject: [PATCH 2/2] docs(multi-bot): exact A2A response paths; label failure transition as implementation gap (DonPrus review) --- docs/multi-bot-integration.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/multi-bot-integration.md b/docs/multi-bot-integration.md index 5fb290f..1b71661 100644 --- a/docs/multi-bot-integration.md +++ b/docs/multi-bot-integration.md @@ -76,14 +76,21 @@ When a workflow uses `execution: "dispatch"` with `dispatch.protocol = "a2a"`: the latter produces `POST /a2a/a2a` and fails with `HTTP 404`. - The dispatcher sends A2A v0.3.0 JSON-RPC (`message/send`, `kind` discriminators, `messageId`), requires the worker to accept it at `POST {base}/a2a`, and parses - the reply from the result's `parts[].text` (falling back to the legacy - `artifacts[].parts[].text` shape). + the reply from `result.parts[0].text` (falling back to the legacy + `result.artifacts[0].parts[0].text`). Only the first part is read — multi-part + replies are not combined. ## Workflow transition conventions -`on_success.transition_to` and `on_failure.transition_to` are applied as the -pipeline FSM **trigger** name. Define the pipeline transition's `trigger` to -match the value used in the workflow (naming the trigger after its target state, -e.g. `trigger: "done"` for `to: "done"`, is the simplest convention). A trigger -name that does not exist on the task's current stage is rejected by the tracker -and the task re-enters its retry cycle. +`on_success.transition_to` is passed to the pipeline FSM as the **trigger** +name. Define the pipeline transition's `trigger` to match the value used in the +workflow (naming the trigger after its target state, e.g. `trigger: "done"` for +`to: "done"`, is the simplest convention). A trigger name that does not exist on +the task's current stage is rejected by the tracker and the task re-enters its +retry cycle. + +`on_failure.transition_to` follows the same naming convention, but is currently +an **existing implementation gap**: it is parsed and never consumed. After retry +handling, `Tracker.driveFailed` calls `failRun` directly, so no failure +transition fires (#52, closed without merge). Treat failure transitions as the +intended contract, not current behavior.