Skip to content

Open container

The escalation boundaryDashboardHTTPSDKMCPScanconditionalAccumulatoraccumulateaccumulateItem

One row is a container that fills over time. Items join one at a time and the workflow wakes once with the ordered collection.

The situation

Bags are scanned into a bin as they come off the line. The bin ships when it holds twelve bags or when four hours pass, whichever comes first. Nobody knows in advance which bags will go in.

The recipe

workflows/bin.ts
import { conditionalAccumulator } from '@hotmeshio/long-tail';
const bin = await conditionalAccumulator<BagV1, { shippedBy?: string }>(signalId, {
role: 'bin',
description: 'Scan each bag into the bin',
metadata: { binKey },
accumulate: { max: 12 },
timeout: '4h',
});
if (bin === null) return cancelled(binKey);
ship(bin.$accumulated.map((item) => item.itemKey), bin.$trigger);
services/scan-bag.ts
await lt.escalations.accumulateByMetadata({ key: 'binKey', value: binKey, itemKey: orderId });
// { outcome: 'accepted', count: 3, remaining: 9, escalationId }

$accumulated lists each entry’s itemKey, payload, at and actor in arrival order. $trigger says what ended the wait: count, timeout or resolve.

The guarantee

Each add is one guarded statement: the entry lands only while the row is pending and the key is not already held, and accumulate_count is recomputed in the same statement. The add that reaches max resolves the row and wakes the wait. Two adds racing for the last slot yield one completed and one refusal. See Batches and accumulators.