Defines a recurring action entity with its configuration schema.
every is not a timeout: nothing is timing out. It is a recurring action
that runs on an interval for as long as a record sits in its step. Firing
never leaves the step — there is no transition_to — so every always
requires an action.
Storage
AshWorkflow.Transformers.AddAttributes adds one nilable
:utc_datetime_usec attribute per every, holding the instant it last
fired. Its interval is measured against that column, not against
state_entered_at, so two every entities on the same step — and any
timeout sharing the step — no longer share one anchor that one firing
resets out from under the others.
Named <step>_<every>_last_fired_at by default, or explicitly with
last_fired_field:
every :reminder do
interval {1, :hours}
action :send_reminder
last_fired_field :reminder_last_fired_at
endNot field — AshWorkflow.Entities.Timeout's field names an anchor
AshWorkflow reads and never writes. last_fired_field names a column
AshWorkflow owns and writes on every fire. Reusing the name would give it
two opposite meanings.
A record whose column is still nil has never fired this every, and the
interval is then measured from state_entered_at, so the first firing lands
one whole interval after the record entered the step rather than on entry.
Both the generated Oban trigger and
AshWorkflow.Scheduler.Precise.Timeline apply that fallback.
Bounding an every with until
every alone fires forever. until stops it after a fixed amount of
wall-clock time since the record entered the step:
every :reminder do
interval {2, :days}
action :send_review_reminder
until {8, :days}
enduntil is measured against state_entered_at directly. An every's own
firing writes its last_fired_field, not state_entered_at, so
state_entered_at stays put for as long as the record occupies the step —
nothing moves the anchor until measures against. See
AshWorkflow.Verifiers.ValidateEvery for why until must be strictly
longer than interval.
Reaching the bound only stops the firing. It does not transition state —
compose a second, ordinary timeout with a fire_after equal to the bound
for "give up and move on".
Summary
Functions
The attribute this every writes its last-fired instant to, and measures
interval against: last_fired_field if given, otherwise
<step_name>_<every_name>_last_fired_at.
Types
@type duration_unit() :: AshWorkflow.Duration.unit()
@type t() :: %AshWorkflow.Entities.Every{ __spark_metadata__: term(), action: atom(), check_interval: String.t() | nil, interval: {pos_integer(), duration_unit()}, last_fired_field: atom() | nil, name: atom(), retry: AshWorkflow.Entities.Retry.t() | nil, self_scheduled?: boolean(), until: AshWorkflow.Duration.t() | nil }