Skip to main content

Runs

View Markdown

A run is one execution of a task or a flow. You choose its id, and the id is the idempotency key: the same id twice is the same work.

val options = RunOptions(
id = "otp-${otpRow.id}", // stable, unique per unit of work
queue = "lab",
attemptTimeout = 10.seconds,
overallTimeout = 2.minutes,
retry = RetryPolicy(maxAttempts = 5, initialInterval = 500.milliseconds),
)
val handle = orchestrator.start(SendOtp.type, input, options)

States

handle.status() returns the state and the attempt number. handle.result() suspends until a terminal state and throws RunFailed for anything but Completed; the exception carries the state and the handler's own message.

Id policies

What happens when a run with the same id already exists depends on whether it is still running.

Still running (ConflictPolicy):

PolicyEffect
FAIL (default)the second start throws RunAlreadyExists
USE_EXISTINGthe second caller gets a handle to the run in progress; server-side de-duplication

Already finished (ReusePolicy):

PolicyEffect
ALLOW_DUPLICATE_FAILED_ONLY (default)never redo a success, always allow another go at a failure
ALLOW_DUPLICATEa new run under the same id
REJECT_DUPLICATEnever again, whatever happened

An OTP send wants REJECT_DUPLICATE: a failed send must not be re-run under an old id after the code has expired. A nightly recompute wants the default.

Re-attaching

A handle is not the run. orchestrator.handle(type, RunId("...")) re-attaches from any process, and result() on it returns the recorded result, however long ago it finished. The result lives in the orchestrator, not in the client that started it.

Remember: choose the id like a database key, because that is what it is.