Dispatch — planner to executor¶
Dispatch carries work-release events the moment a document in the planner transitions into an executable state. The executor receives them, expands them into floor tasks, and assigns workers, robots, or equipment.
Event kinds¶
Kind |
Trigger in the planner |
|---|---|
|
A shipper has moved to a picking-ready state. |
|
A receiver has been created from a PO or ASN. |
|
A work order is released for execution. |
|
A transfer order is confirmed at the source warehouse. |
|
A transfer order has entered in-transit toward this warehouse. |
|
A value-added-services order is released. |
|
A refurbish work order is released. |
|
A previously released document is cancelled, amended, or reprioritized. |
The dispatch payload carries the full Routing recipe inline — ordered Operations with expected time/cost, BOM deltas per Op, and skill / resource requirements. The executor does not call back to the planner at runtime to fetch the recipe. See WMS ADR-0022 amendment 2026-05-24 and ADR-0028 for the rationale.
Payload size is well under 10 KB for typical v1 Routings (3–8 Operations). Complex Routings with 50+ Operations approach 100 KB; both transports below tolerate that.
Choosing a transport¶
The planner-executor pair picks one transport per warehouse. Mixing transports for the same warehouse is not supported.
Transport |
When to choose |
Cost on the implementer |
|---|---|---|
Webhook POST |
Default. The planner POSTs each event to a URL the executor registers. |
Low. Outbound HTTPS + HMAC over body. |
Kafka subscription |
Both sides reach the same Kafka cluster and want ordered, high-throughput streaming. |
Medium. Shared Kafka access + topic ownership decision. |
Polling |
The executor cannot accept inbound, or the planner cannot publish to a shared Kafka. The executor pulls from a planner-hosted queue. |
Medium. Planner exposes |
For most third-party integrations, webhook is the recommended default. Kafka becomes attractive when both sides co-locate in shared cloud infrastructure. Polling exists for restricted networks that only permit outbound HTTPS from one side.
Ordering¶
Order is preserved per warehouse, not globally.
Kafka — the partition key is
warehouse_frn. Events for the same warehouse land in the same partition and are consumed in publish order.Webhook — delivery is serialized per
(planner_id, warehouse_id)pair. The next event for a warehouse is not sent until the previous one is acknowledged or finally fails to the DLQ.Polling — the planner returns events ordered by an internal cursor. The executor must process pages strictly in order and acknowledge only after durable processing.
Cross-warehouse ordering is not guaranteed and should not be relied on. If a planner releases two shippers in the same wall-clock millisecond into different warehouses, executors may see them in either order.
Delivery semantics¶
At-least-once. A planner must assume any single delivery attempt may fail and be retried. An executor must therefore be idempotent on (planner_id, correlation_id) — see Idempotency and ordering.
Webhook retry policy (typical):
0s → 5s → 30s → 2m → 10m → 1h → ... → 24h cap → DLQ
The planner DLQs an event after 24h of failed retries; supervisors are expected to be alerted and to triage from there.
Kafka and polling rely on the consumer’s own commit / acknowledgement semantics — the planner re-delivers everything past the last committed cursor.
Cancellation, amendment, reprioritization¶
These three operations all surface as a CANCELLED event referencing the original correlation_id. The semantics are deliberately collapsed into one kind to keep the executor’s reaction simple: stop the document if active, drop pending tasks if not yet started, and wait for a fresh release if applicable.
Amendments and reprioritizations land as a CANCELLED followed by a fresh release event (SHIPPER_RELEASED, WORKORDER_RELEASED, …) with a new correlation_id. The executor must not attempt to mutate in-flight work — only fresh releases get new tasks.
Webhook payload shape¶
{
"kind": "SHIPPER_RELEASED",
"correlation_id": "01J7Y6K1NQ3W2C0X4V0R5T6E7N",
"planner_id": "fgai-wms",
"warehouse_id": "WH-Tokyo-01",
"document_ref": { "type": "SHIPPER", "id": "SH-2026-000183" },
"routing": {
"ops": [
{ "op_id": "op-1", "kind": "PICK", "sku": "SKU-WIDGET-RED-LG", "qty": 12, "from_location": "A.12.3.1" },
{ "op_id": "op-2", "kind": "PACK", "carton": "CTN-S" },
{ "op_id": "op-3", "kind": "SHIP", "dock": "DOCK-3" }
],
"expected_duration_seconds": 420
},
"meta": {
"released_at": "2026-05-24T03:14:01Z",
"priority": "NORMAL"
}
}
correlation_id is the partner-generated idempotency key — UUID v4 or v7. The full payload schema lives in the OpenAPI reference; per-event-kind schemas vary in the routing and meta blocks but the envelope is constant.
Webhook recipients must verify the X-FGAI-Signature: sha256=<hex> header over the raw request body before parsing — see Authentication.
Poll-mode shape¶
GET /wes/v1/dispatch/pending?since={cursor}&limit=100
POST /wes/v1/dispatch/ack { "cursor": "..." }
The planner returns an ordered page of dispatch events and a next_cursor. The executor processes them durably, then POSTs the cursor to /dispatch/ack. The next GET /dispatch/pending?since={cursor} advances past the acked events.
Without an ack, the planner re-delivers the same page on the next pull. Pages are bounded in size; pull again immediately if next_cursor is non-empty.