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
end

Returning 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.