Activity-side use macro. Activities are plain Elixir modules —
no determinism constraint. Each activity module implements
execute(input) and is dispatched by the runner based on its
module name (the activity_type). The runner calls execute/1 with
the deserialized input.
Activity context
Inside execute/1, an activity body can call info/0 to read the
per-dispatch context — workflow_id, run_id, activity_id,
attempt — populated by Hourglass.ActivityRunner from the
inbound Coresdk.ActivityTask.Start. attempt/0 is a thin
delegate that returns just the attempt count. Both raise when
called outside an active dispatch.
try_info/0 is the non-raising sibling: returns the same struct
inside an activity dispatch, nil outside.
See info/0 for field details and the (run_id, activity_id)
retry-stability guarantee.
Options
use Hourglass.Activity, input: X, output: Y, retry: [...]
declares input and output schemas and the module-default Temporal
RetryPolicy applied by Hourglass.ActivityRunner when an activity
returns {:error, _} or raises and the
Hourglass.Activity.RetryClassifier classifies the failure
as :retryable.
:input— aHourglass.Schemamodule or scalar atom (e.g.:map,:string). Default::map.:output— aHourglass.Schemamodule or scalar atom. Default::map.:retry— keyword list of retry policy options (optional).
Allowed retry keys (mirror Temporal's RetryPolicy proto fields):
:max_attempts— non-negative integer.0means "unlimited" per Temporal's spec; positive values cap retries. Default:1(no retry).:initial_interval— milliseconds before the first retry.:backoff_coefficient— float>= 1.0controlling exponential backoff growth.:max_interval— millisecond cap on the backoff interval.
Disallowed keys: :retryable_error_types and
:non_retryable_error_types. The classifier owns eligibility (which
error shapes retry); the policy only controls quantity (how many
times + how fast). Mixing both controls would let an activity
silently widen retry eligibility around the classifier's audited
taxonomy.
Compile-time validation raises CompileError for unknown keys,
for :max_attempts < 0, and for :backoff_coefficient < 1.0.
Default policy
Activities that omit :retry get
default_retry_policy/0 — [max_attempts: 1] (no retry). Retry is
opt-in: the policy plus a :retryable classification are both
required for the runner to retry.
Example
defmodule MyApp.Activities.Download do
use Hourglass.Activity,
input: MyApp.Download.Args,
output: MyApp.Download.Result,
retry: [
max_attempts: 5,
initial_interval: 1_000,
backoff_coefficient: 2.0,
max_interval: 30_000
]
@impl true
def execute(%MyApp.Download.Args{url: url}), do: do_fetch(url)
end
Summary
Functions
Returns the current attempt count for the running activity (1-based).
Default retry policy for activities that don't specify one.
Returns the per-dispatch context as a Hourglass.Activity.Info
struct. Fields: workflow_id, run_id, activity_id, attempt.
Non-raising variant of info/0: returns the per-dispatch
Hourglass.Activity.Info struct inside an activity dispatch,
nil outside.
Functions
@spec attempt() :: pos_integer()
Returns the current attempt count for the running activity (1-based).
attempt is 1 on the first dispatch, 2 on the first retry, and
so on — Temporal Server increments the count per retry. Delegates
to info/0; raises if called outside an active activity dispatch.
@spec default_retry_policy() :: keyword()
Default retry policy for activities that don't specify one.
The default is "no retry" (max_attempts: 1). Activities that
genuinely need retries opt in via use Hourglass.Activity, retry: [...].
The classifier still owns eligibility — even if max_attempts is
set, the runner only retries when the classifier returns
:retryable.
@spec info() :: Hourglass.Activity.Info.t()
Returns the per-dispatch context as a Hourglass.Activity.Info
struct. Fields: workflow_id, run_id, activity_id, attempt.
Read from a process-dictionary key ({Hourglass.Activity, :info})
that Hourglass.ActivityRunner sets immediately before each dispatch
and clears (via try ... after) immediately after. Calling outside
an active activity dispatch raises — there is no meaningful context
outside that scope.
The (run_id, activity_id) pair is stable across retries of the
same activity invocation.
@spec try_info() :: Hourglass.Activity.Info.t() | nil
Non-raising variant of info/0: returns the per-dispatch
Hourglass.Activity.Info struct inside an activity dispatch,
nil outside.