automate.ax. Most operators also have a fluent form on Signal, KeyedSignal, or GlobalSignal.
Signals compose synchronously during planning and materialize during durable execution. Transformer, predicate, key, and section callbacks must be synchronous. Put external work in actions.
Choose an operator family
merge and race answer different questions. merge forms one stream from independent roots and emits every occurrence. race selects one alternative within a shared activation boundary. Dependency and structure operators—dependentOn, withPrerequisites, scope, and group—compose with every family.
Property access
Reading a non-function property returns another signal. Chained access works across objects, arrays, and primitives:null or undefined fails with TypeError. Signal methods reserve their names, and function-valued payload properties aren’t projected as callable values. Use transform to call a value method such as names.join(", ").
transform
Creates a derived signal from one or more inputs.
expression_failed and doesn’t consume an action retry. Arbitrary transforms don’t preserve keyBy or globally annotations.
t
Builds a Signal<string> from a tagged template containing at least one signal:
t without a signal interpolation throws TypeError during planning. Use transform for conditional logic, value methods, or non-string output.
isSignal
Narrows an unknown value to Signal:
true only for objects implementing the Automate.ax signal protocol. Action inputs already accept compatible signals; use this guard in helpers that accept both in-memory values and signals.
keyBy
Returns an immutable KeyedSignal<T> whose cross-context operations use the encoded key as their partition:
keyBy replaces an existing keyed or global annotation.
globally
Returns a GlobalSignal<T> whose cross-context operations share one partition:
globally replaces an existing keyed or global annotation. Use keyBy when one entity’s occurrences must not reset another entity’s timer or enter the same batch.
timestamp
Returns the stable platform time at which an occurrence emitted:
Signal<Date>. Trigger timestamps use ingestion time, action timestamps use completion time, and coordinated signals use their persisted decision time. A pure derivation uses the most recent contributing durable boundary. Use a provider timestamp when its domain time is the required value.
correlationId
Returns a stable keyed Signal<string> derived from an occurrence’s durable boundary:
perDeclaration defaults to true, so separate declarations anchored to one occurrence receive independent IDs. Pass false to derive the ID only from the durable dependency boundary. Prefer a provider’s domain ID when it already identifies the entity or conversation.
correlate
Matches one occurrence from every keyed stream by exact encoded key:
Each input defaults to
{ selection: "oldest", consumption: "consume" }. Set selection: "latest" to discard older eligible occurrences for that input. Set consumption: "retain" to reuse the selected occurrence in later matches: oldest retention behaves like a latch, while latest retention behaves like an updating register. Retained occurrences remain operational state until superseded, expired by occurrenceTtl, or the automation is removed.
The child context contains every selected parent history, so downstream work can consume the original keyed signals. The returned Signal<null> exposes the match boundary. Reusing one durable signal origin in several positions throws TypeError; collect distinct occurrences first when one stream must fill several roles.
merge
Combines independent occurrence streams without matching or consuming them together:
correlate when related streams must meet by key, or race when alternatives within one causal occurrence compete to finish first.
collect
Consumes a fixed number of keyed or global occurrences in arrival order:
count accepts a positive integer or Signal<number>. Invalid literal counts throw during planning. Signal-derived counts match only occurrences whose resolved counts are equal. The result is a partition-preserving NonEmptyArray<T>.
once and take
Admit a bounded number of occurrences from each keyed or global partition:
once(signal, options?) is take(signal, 1, options?). Without ttl, the count remains claimed permanently. With ttl, the claim starts when the first occurrence is admitted and the complete count resets when it expires. The result preserves the source partition.
rateLimit
Admit at most a fixed number of occurrences in each rolling interval:
overflow defaults to "wait", which queues excess occurrences FIFO until rolling capacity returns. "drop" closes excess occurrences without emitting. Every key has independent capacity; globally() creates one shared limit.
each
Creates one durable child context per array item:
Signal<TResult[]> in source-array order. A failed item fails the gathered result, a closed item closes it, and an empty source gathers to []. Returning nothing produces void and doesn’t join the item contexts.
gate
Preserves a value only when a separate boolean signal emits true:
false or a closed condition closes the result without materializing the value. A failed condition fails it. The result preserves a keyed or global annotation carried by the value.
filter
Preserves a value only when a synchronous predicate returns true:
expression_failed. The result preserves a keyed or global source annotation.
partition
Splits one signal into complementary matching and remaining paths:
dependentOn
Preserves one signal value while adding prerequisites to its downstream dependency chain:
withPrerequisites
Applies one signal or a nonempty tuple to every durable declaration in a synchronous section:
withPrerequisites or branch. A trigger starts an independent root context.
branch
Declares complementary synchronous sections selected by boolean signals:
branch returns the selected result. With only a true section, its result closes when the condition is false. Sections must be synchronous and can’t declare triggers.
race
Selects the first causally related signal to emit or fail:
merge when either of several triggers should start the same flow.
serialize
Runs one structured work region at a time in each keyed or global partition:
globally() for one queue across all occurrences.
The section creates an isolated structured-concurrency region. Independent declarations inside it can run concurrently; declaration order doesn’t serialize them. The next occurrence in the same partition starts only after every context and unmatched coordination offer created by the current region becomes terminal. Other partitions continue independently.
The callback doesn’t return a completion signal. If it returns a signal, that value remains unavailable downstream until the whole region closes. Returning nothing is valid when only the enclosed effects matter. Sections must be synchronous and can’t declare triggers.
concurrent
Runs up to a fixed number of structured work regions in each partition:
concurrent has the same section and completion semantics as serialize, but admits up to limit active regions per key. Additional occurrences wait FIFO. serialize(stream, section) is the capacity-one form.
delay
Resumes a value or synchronous section after a durable relative delay:
Duration accepts nonnegative milliseconds, a compact duration string, or a compatible signal. A value-carrying delay preserves the source’s keyed or global annotation. The timer-only form declares a hidden delivery trigger; scope it to its initiating occurrence with withPrerequisites when the automation has another possible root.
timeout
Mirrors a signal unless a durable relative deadline wins:
Duration. If it wins, the result fails with name: "TimeoutError" and message "Signal timed out.". Source closure leaves the deadline active. A timeout can span execution jobs but doesn’t interrupt an action handler already running. The result preserves the source’s keyed or global annotation.
funnel
Applies one timing and output policy to each keyed or global partition:
Durations accept
Duration. An end emission requires minQuietPeriod, maxBurstDuration, or until. Start-only mode emits immediately and ignores the rest of the burst, so it doesn’t accept select or buffer. The result preserves the source partition; locally closed paths contribute no occurrence.
Minimum-gap memory belongs to the durable partition and remains effective after older execution values leave retention.
debounce
Emits the latest keyed or global occurrence after its partition remains quiet:
duration accepts Duration. Each occurrence resets only its partition’s timer. This is the selecting funnel preset { minQuietPeriod: duration }, and the result preserves the source partition.
window
Buffers keyed or global occurrences until a relative or absolute deadline:
The relative form accepts
Duration. The first occurrence opens the window. The partition-preserving result is a NonEmptyArray<T> in arrival order.
outcome
Converts every terminal state into a tagged value and always succeeds after the source terminates:
Signal<SignalOutcome<T>>. Unlike value-preserving operators, outcome doesn’t retain a keyed or global annotation.
succeeded
Emits whether a signal terminated successfully with a value:
true for success and false for failure or closure. The predicate itself succeeds after any terminal state.
failed
Emits whether a signal terminated with an automation error:
true for failure and false for success or closure. The predicate itself succeeds after any terminal state.
closed
Emits whether a signal closed without a value:
true for closure and false for success or failure. Failure isn’t closure. The predicate itself succeeds after any terminal state.
onSuccess
Selects a signal’s successful value and closes for failure or closure:
onFailure
Selects a signal’s normalized AutomationError and closes for success or closure:
AutomationError contains author-safe details, not raw errors or stacks.
onClose
Selects closure as Signal<null> and closes for success or failure:
markSignificant
Marks the connected run significant only when the selected signal emits, then returns the original signal:
scope
Gives a synchronous section its own durable hook namespace:
scope adds no prerequisite or presentation metadata. branch, each, and delay create internal scopes for the same isolation.
group
Names a synchronous declaration group and gives it an isolated hook namespace:
Like
scope, group doesn’t add a prerequisite. It returns the section result.
Types and errors
| Type | Contract | | --- | --- | --- | |Signal<T> | Symbolic value that materializes during execution. |
| KeyedSignal<T> | Signal<T> with an encoded partition key from keyBy. |
| GlobalSignal<T> | Signal<T> with one shared cross-context partition from globally. |
| NonEmptyArray<T> | [T, ...T[]]. Complete collections and windows return this type. |
| Duration | Milliseconds, a compact DurationString, or a compatible signal carrying either. |
| Deadline | Date | Signal<Date>. |
| CorrelationOptions | Options accepted by correlate. |
| CorrelationInput | A keyed signal or { signal, selection?, consumption? }. |
| AdmissionOptions | Optional claim ttl accepted by once and take. |
| RateLimitOptions | Rolling limit, interval, and overflow behavior. |
| ConcurrencyOptions | Positive active-region limit accepted by concurrent. |
| ValueInheritanceOptions | Parent-value selection accepted by collect and window. |
| WindowUntilOptions | ValueInheritanceOptions & { until: Deadline }. |
| GroupOptions | Options accepted by group. |
| FunnelOptions | Type-checked timing, edge, selection, and buffering combinations accepted by funnel. |
SignalOutcome<T> is mutually exclusive:
AutomationError contains author-safe failure data:
code is the stable machine-readable classification. message, optional name, JSON details, and a depth-limited cause are safe to expose to automation authors. Raw errors and stacks don’t enter signal values.