Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Steps

A step is one unit of work in a workflow: a task run by a worker, a child workflow, a durable timer, a wait for a signal, or a value computed by the runtime. Each step declares what it does, what it needs, and the policies that apply to it. This page is the reference for the step block: its actions, every member, where each member is allowed, and the type other expressions see when they reference the step.

Anatomy of a step

step process_payment {
    worker "payments.charge"          // the action: a task contract
    using python                      // only route to Python workers
    after reserve_inventory           // explicit ordering
    when analyze_risk.score < 0.8     // run only if true, otherwise skip
    with {                            // the task input, checked against the contract
        order_id: input.order_id
        amount: input.amount
        currency: input.currency
    }
    idempotency_key: "charge-" + input.order_id
    timeout 30s                       // per attempt
    queue_timeout 2m                  // fail if no worker picks it up in time
    retry {
        attempts: 5                   // total attempts, including the first
        backoff: exponential
        initial_delay: 500ms
        max_delay: 10s
        non_retryable: ["CARD_DECLINED"]
    }
    compensate {
        worker "payments.refund"
        with { transaction_id: process_payment.transaction_id, amount: input.amount }
    }
}

The step name is an identifier. It is unique within the workflow (E0302), and expressions use it to read the step’s output (process_payment.transaction_id). Members can appear in any order, and separators between them are optional.

Actions

Every step has exactly one action:

ActionSyntaxWhat runs
Taskworker "task.name"A task, executed by a worker that implements the contract, or by the runtime for built-in tasks.
Child workflowworkflow "Name"Another workflow, as a durable child execution. See Child workflows.
Timersleep 10sA durable timer. See Durable sleep.
Signal waitawait reviewWaits for the next review signal sent to the execution; the payload is the output. See Signals and queries.
Computecompute <expression>Evaluates an expression in the runtime; the value is the output. No worker. See Compute steps.

A step without an action is E0301. A step with two actions is E0106:

task "reports.build" {
    output { url: String }
}

workflow Report {
    version "1.0.0"
    step build {
        worker "reports.build"
        sleep 10s
    }
}
error[E0106]: a step can only have one `worker`, `workflow`, `sleep`, `await` or `compute`
 --> report.orbit:9:9
  |
9 |         sleep 10s
  |         ^^^^^
  = help: split the work into separate steps

error: compilation failed: 1 error(s), 0 warning(s)

Members

MemberSyntaxDefaultMeaningPage
workerworker "name"Run a task.Task contracts
workflowworkflow "Name"Start a child workflow.Child workflows
sleepsleep DURATIONWait durably.Durable sleep
awaitawait SIGNALWait for a signal declared in the workflow.Signals and queries
computecompute EXPREvaluate an expression in the runtime.Compute steps
withwith EXPR{}The input passed to the task or child workflow.below
usingusing LANGUAGEany workerRoute the task only to workers of this runtime.below
afterafter a, bnoneRun after these steps.Dependencies
whenwhen EXPRalways runRun only if the condition is true; otherwise skip.Conditions
forfor x in EXPRnoneRun once per list element.Fan-out
max_parallelmax_parallel NunlimitedConcurrency limit for a for step, 1 to 10000.Fan-out
timeouttimeout DURATIONno limitLimit for one attempt, from dispatch to result. On an await step: limit for the wait.Retries and timeouts
queue_timeoutqueue_timeout DURATIONno limitLimit for waiting for a worker.Retries and timeouts
retryretry { ... }one attemptRetry policy.Retries and timeouts
idempotency_keyidempotency_key EXPR<execution id>/<unit>The key workers use to deduplicate side effects.Idempotency
compensatecompensate { ... }noneThe action that undoes this step.Compensation

Syntax notes:

  • Every member except retry, compensate and for accepts an optional : after the keyword: timeout: 30s is the same as timeout 30s. retry and compensate are followed directly by {, and retry: { is a syntax error.

  • DURATION is a duration literal such as 30s. Expressions are not accepted where a duration is required.

  • after takes a comma-separated list of step names. You can repeat after; the lists are combined.

  • Every other member may appear at most once per step (E0106).

  • An unknown member is E0102, with a suggestion for close misspellings:

    task "reports.build" {
        output { url: String }
    }
    
    workflow Report {
        version "1.0.0"
        step build {
            wroker "reports.build"
        }
    }
    
    error[E0301]: step `build` does nothing
     --> report.orbit:7:10
      |
    7 |     step build {
      |          ^^^^^
      = help: add `worker "task.name"`, `workflow "Name"`, `sleep 10s`, `await <signal>` or `compute <expression>`
    
    error[E0102]: unexpected `wroker` in step
     --> report.orbit:8:9
      |
    8 |         wroker "reports.build"
      |         ^^^^^^
      = help: did you mean `worker`?
    
    error: compilation failed: 2 error(s), 0 warning(s)
    

Where members are allowed

Not every member makes sense for every kind of step. The compiler rejects misplaced members with E0307 (or E0308 for compensation):

MemberTask stepWorkflow stepSleep stepAwait stepCompute stepcompensate block
withyesyesnononoyes
usingyesnonononoyes
afteryesyesyesyesyesno
whenyesyesyesyesyesno
foryesyesnonoyesno
max_parallelwith forwith fornonowith forno
timeoutyesyesnoyesnoyes
queue_timeoutyesnonononoyes
retryyesnonononoyes
idempotency_keyyesnonononoyes
compensateyesyesno (E0308)no (E0308)no (E0308)not allowed (E0102)

A compensate block must call a task with worker; sleep, workflow, await or compute there is E0308.

Step names

Step names are identifiers. Some names are reserved because expressions use them for other purposes, and declaring a step with one of them is E0306:

input, execution, output, if, then, else

Input: with

with is an expression whose value becomes the task input (or the child workflow input). It is usually an object literal, but any expression of a compatible type works:

schema PaymentRequest {
    order_id: String
    amount: Float
    currency: String
}

task "payments.authorize" {
    input PaymentRequest
    output { authorization_id: String }
}

workflow Authorize {
    version "1.0.0"
    input { payment: PaymentRequest, note: String? }

    // Pass a whole record from the input.
    step authorize { worker "payments.authorize" with input.payment }
}

The expression is checked against the contract input (see Types). It is evaluated once, when the step is scheduled, and the result is stored in the execution history. Retries and redeliveries reuse the stored value. If evaluation fails at runtime, for example because of a division by zero, the step fails with the non-retryable error EXPRESSION_ERROR.

A step without with passes {}. That is accepted only when the contract input has no required fields; otherwise the compiler reports E0403.

Reading another step’s output in with makes this step depend on that step. See Dependencies.

Routing: using

using restricts which workers receive the task: only workers that announce the given runtime are eligible. The value is an identifier, compared case-insensitively with the runtime a worker announces. using any places no restriction.

The compiler knows these runtime names: python, typescript, javascript, go, rust, java, kotlin, csharp, ruby, php, elixir, swift, cpp, http and any. Any other name produces warning W0301, with a suggestion when the name is close to a known one:

task "risk.analyze" {
    input { customer_id: String }
    output { score: Float }
}

workflow Risk {
    version "1.0.0"
    input { customer_id: String }
    step score {
        worker "risk.analyze"
        using pyhton
        with { customer_id: input.customer_id }
    }
}
warning[W0301]: unknown worker language `pyhton`
  --> risk.orbit:11:15
   |
11 |         using pyhton
   |               ^^^^^^
   = help: did you mean `python`?

The warning does not stop compilation, but a misspelled runtime means no worker ever receives the task.

Step output types

Expressions refer to a step by name. The type they see depends on the step’s action and on when and for:

StepType of name
worker "t"the output type of task t
workflow "W"the output type of workflow W
sleep d{}
await sthe payload record type of signal s
compute ethe type of expression e
with whenthe type above, made optional (T?)
with forList<T>, one element per list item
with for and whenList<T>?

Inside the step’s own compensate block, the step’s output is never optional, and for a for step it is the output of the element being compensated. See Compensation.

Units and status

At runtime each step is one or more units. A plain step is one unit, keyed by its name. A for step has one unit per element, keyed name[0], name[1], and so on. A compensation is a unit keyed compensate:<unit>, and a step of an outcome handler is keyed on_<kind>:<step>. Unit keys appear in orbit status, orbit logs, idempotency keys and child execution ids.

A unit moves through these states:

StateMeaning
PENDINGWaiting for dependencies.
SCHEDULEDInput evaluated, queued for a worker.
DISPATCHEDA worker (or the runtime) is executing the attempt.
RETRY_WAITA retry is scheduled after a backoff delay.
WAITINGA sleep timer is running, or an await step is waiting for a signal.
RUNNINGA for step whose elements are in progress.
SUCCEEDEDFinished successfully.
SKIPPEDIts when condition was false or null.
FAILEDFailed permanently.
CANCELLEDWithdrawn because the workflow was aborted.

See Executions and history for the events behind these transitions.

See also