Restate uses an execution log to replay operations after failures and suspensions. Non-deterministic operations (database calls, HTTP requests, UUID generation) must be wrapped to ensure deterministic replay.

Run

Use ctx.run to safely wrap any non-deterministic operation, like HTTP calls or database responses, and have Restate store its result in the execution log.
async def call_llm(prompt: str) -> str:
    # ... implement ...
    return "llm response"

# specify the (async) function to call and its arguments
result = await ctx.run_typed("LLM call", call_llm, prompt="What is the weather?")

# or use a lambda to capture a single value
my_number = await ctx.run_typed("generate number", lambda: random.randint(0, 10))
Note that inside ctx.run, you cannot use the Restate context (e.g., ctx.get, ctx.sleep, or nested ctx.run).

Deterministic randoms

When you do non-deterministic operations, like generating UUIDs or random numbers, you must ensure that the results are deterministic on replay. For example, to generate stable UUIDs for things like idempotency keys:
my_uuid = ctx.uuid()
The SDK provides deterministic helpers for random values — seeded by the invocation ID — so they return the same result on retries.

UUIDs

To generate stable UUIDs for things like idempotency keys:
my_uuid = ctx.uuid()
Do not use this in cryptographic contexts.

Random numbers

To generate a deterministic float between 0 and 1:
ctx.random().random()
This behaves like Math.random() but is deterministically replayable.

Deterministic time

To get the current millis since midnight, January 1, 1970, that is consistent across retries:
current_time = await ctx.time()