retry. A workflow with no flow config runs unshaped - every control is
opt-in and off by default.
At a glance
Keys
Most controls take an optionalkey: a dotted path into the event data that scopes the control to a
value. key: "accountId" gives each account its own independent limit; key: "user.id" reads a nested
field. Keys are field paths, not expressions. An omitted key - or a missing / non-scalar field - scopes
the control to the whole workflow.
Concurrency
Caps how many runs execute at once in a scope. A slot is held only while a run is actively executing, so a run that is sleeping or awaiting an event releases its slot and does not consume one. The count is taken across the shared database, so the limit is global, not per-process. Over-limit runs are not dropped - they wait and retry as slots free, preserving order.Project-wide ceiling
Above the per-workflowconcurrency you set in code, each project has a concurrency ceiling that
caps how many runs execute at once across the whole project, regardless of workflow or key. It comes
from your plan, not from a workflow field (0 = unlimited). A run must clear both its per-workflow
limit and the project ceiling to start; whichever is tighter applies, and an over-ceiling run waits and
retries exactly like a per-workflow over-limit run.
Throttle
Bounds how often runs start, smoothing bursts by spreading overflow into the future - one start everyperMs / limit. No run is lost; excess runs begin later.
Rate limit
Same window as throttle, opposite action: instead of delaying overflow it drops it. Up tolimit
runs start per perMs; the rest are shed and the event response reports dropped: true. Use it for
abuse protection where shedding beats queueing.
Throttle and rate limit share one rate primitive (GCRA). Throttle delays the overflow; rate limit
drops it.
Debounce
Coalesces a burst of events into a single run that fires afterperiodMs of quiet. Each new event
slides the deadline forward and replaces the payload, so only the last event in a quiet-bounded
burst runs.
Batch
Collects events into one run, flushing when the buffer hitsmaxSize or timeoutMs elapses,
whichever comes first. The run receives the events as ctx.events; ctx.event is the first of them.
Priority
Shifts a workflow’s runs earlier in the shared queue byshiftMs, so they dequeue ahead of other
workflows competing for the same slots.
Singleton
Allows at most one non-terminal run per key.Idempotency
Suppresses a second run of this workflow for the same derived key within a time window. The first matching event starts a run; a later event whose key resolves to the same value inside the window is dropped for this workflow (the response reportsdeduped: true with no runId). The event is still
recorded and still wakes waitForEvent waiters - only the duplicate run is suppressed.
The
key is a dotted field path into the event data, resolved the same way as every other
control. A structurally malformed path - empty segments, or a leading or trailing dot - is
rejected when the workflow is registered.
To verify a per-key path resolves, send two events with distinct payloads and confirm two runs
start. Sending the same payload twice is deduped whether or not the path resolves, so it proves nothing.
This is run-level dedupe keyed off the event payload. To dedupe a whole event regardless of which
workflows it matches - the usual safety net for an at-least-once caller retrying POST /events - send a
dedupeId on the event instead (see Events); a repeat of
that id within 24h is dropped before any fan-out.
AI spend controls
Two more controls are declared the same way but meter AI spend rather than run starts. They are documented with the rest of the spend tooling in Cost controls; the table is the short version.Observing flow control
The console renders the same three reads in the Workflows list and on Overview.