External callers and brokers commonly retry requests. Continuum can bind those retries to one durable workflow start and one signal mailbox entry.

Unique starts

Use a stable key from the upstream request:

{:ok, run_id, status} =
  Continuum.start_unique(OrderWorkflow, order,
    namespace: "orders",
    idempotency_key: "checkout:#{order.id}"
  )

status is :started for the caller that inserted the run and :existing for all retries. Keys are scoped by namespace and workflow and remain attached to the root run for its full lifetime. Continuum.start/3 also accepts :idempotency_key, returning {:error, {:already_started, run_id}} on conflict.

Atomic application transactions

Use Continuum.Multi.enqueue/5 when the business record and workflow must commit together. The selected Continuum instance must use the transaction's repo and the PostgreSQL journal.

Ecto.Multi.new()
|> Ecto.Multi.insert(:order, Order.changeset(%Order{}, params))
|> Continuum.Multi.enqueue(:workflow, OrderWorkflow,
  fn %{order: order} -> %{order_id: order.id} end,
  fn %{order: order} ->
    [namespace: "orders", idempotency_key: "order:#{order.id}"]
  end)
|> Repo.transaction()

The :workflow change is %{run_id: id, status: :enqueued | :existing}. Input and options can be computed from earlier Multi changes. Namespace, attributes, trace context, and the selected workflow version are preserved. A duplicate returns the original root ID without changing the original input; other Multi steps still execute and need their own business uniqueness constraints.

No engine starts inside the transaction. A committed run is unleased until a dispatcher claims it, so keep a dispatcher enabled for the repo. PostgreSQL visibility prevents other connections from claiming an uncommitted run. Rollback removes both the new run and its ingress reservation. Once committed, dispatch can recover the run even if the process that submitted the Multi dies. Do not call the dispatcher yourself inside the transaction.

Unique signals

Pass the message or request ID supplied by the transport:

{:ok, status} =
  Continuum.signal_unique(run_id, :payment_received, payment,
    "payments:#{payment.event_id}"
  )

status is :delivered or :duplicate. Delivery IDs are scoped to the logical workflow chain and signal name, so the same signal remains idempotent when the workflow has continued as new.