Skip to content

Batch: N contributions, one wake

The escalation boundaryDashboardHTTPSDKMCPconditionalbatchresolveBatchItemresolveBatchItemByMetadata

One row collects a named set of submissions and wakes the workflow once, when the last one lands, with the whole collection.

The situation

An order needs three stations to sign off: cut, weld and paint. Each station submits on its own schedule. The workflow should continue once, with all three results, and never wake for each one.

The recipe

workflows/assemble-order.ts
const parts = await conditional<Record<'cut' | 'weld' | 'paint', StationResultV1>>(signalId, {
role: 'assembly',
description: 'Each station submits its result',
metadata: { orderId },
batch: ['cut', 'weld', 'paint'],
timeout: '24h',
});
if (parts === false) return expired(orderId);
if (parts === null) return cancelled(orderId);
parts.weld;
services/stations.ts
await lt.escalations.resolveBatchItemByMetadata({
key: 'orderId', value: orderId, itemKey: 'weld', resolverPayload: { ok: true },
});
// interim: { outcome: 'accepted', remaining: 1, escalationId }
// last: { outcome: 'completed', remaining: 0, signaled: true, workflowId }

Progress lives on the row as queryable facets: batch_pending, batch_count and batch_keys. Each item validates against the role’s versioned form.

The guarantee

Each fill is one guarded statement. A payload lands only while its key is pending, so a duplicate answers 409 duplicate-item and changes nothing. The last fill resolves the row, stores the collection as resolver_payload and wakes the wait, atomically. See Batches and accumulators.