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

SHIPPER_RELEASED

A shipper has moved to a picking-ready state.

RECEIVER_EXPECTED

A receiver has been created from a PO or ASN.

WORKORDER_RELEASED

A work order is released for execution.

TRANSFER_OUT_RELEASED

A transfer order is confirmed at the source warehouse.

TRANSFER_IN_EXPECTED

A transfer order has entered in-transit toward this warehouse.

VASORDER_RELEASED

A value-added-services order is released.

REFURBISH_ORDER_RELEASED

A refurbish work order is released.

CANCELLED

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 /dispatch/pending + /dispatch/ack.

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.