Continuum activity idempotency is scoped by activity module and key.
If any run completes MyActivity with idempotency key "k1", a later task for
MyActivity and "k1" receives the same committed result. The later task does
not run the activity body. This is intentionally cross-run: the key represents
the external operation, not the workflow run.
defmodule MyApp.Activities.ChargeCard do
use Continuum.Activity
@impl true
def run(%{order_id: order_id, amount: amount}) do
MyApp.Payments.charge(order_id, amount, idempotency_key: "charge:#{order_id}")
end
@impl true
def idempotency_key([%{order_id: order_id}]) do
"charge:#{order_id}"
end
endReturning nil opts out for that activity call.
Continuum records committed activity results in continuum_activity_results.
Replay still reads from continuum_events; the side table only suppresses
future duplicate execution.
There is one remaining crash window: if the activity performs the external side effect and the worker dies before Continuum commits the result, a retry can run the activity body again. Pass the same idempotency key to the external system for operations such as payments, emails, and third-party mutations.