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

Simulation

orbit simulate runs one execution of a workflow on your machine, without a server or workers. Tasks you do not describe get a generated result that matches their contract, so you can try a new workflow, explore a branch or watch a failure and its compensations as soon as the workflow type-checks. The simulator is the one orbit test uses: the real engine and decider, an in-memory store, and a clock that jumps straight to the next timer, retry or timeout.

Use orbit simulate to explore; when a scenario is worth keeping, write it down as a test.

Usage

orbit simulate [OPTIONS] <FILE>
Argument or optionMeaning
<FILE>A .orbit file (imports are followed) or a compiled IR .json file.
--workflow <WORKFLOW>Workflow to run when the file defines several. Without it, a file with one workflow, or with a parent and its children, runs the workflow no other workflow starts.
-i, --input <JSON>The execution input. Default {}. It is validated like an input sent to the runtime, with defaults applied.
--input-file <PATH>Read the input from a JSON file.
--mocks <PATH>A JSON object mapping task names to the result each returns.
--fail <TASK=CODE>Make every attempt of a task fail permanently with this error code. Repeatable.
--signal <NAME=JSON>Send a signal once the execution waits for it, for example review='{"approved":true,"reviewer":"dana"}'. NAME=@file.json reads the payload from a file, and NAME alone sends {}. Repeatable; signals are sent in order.
--jsonPrint the result as JSON instead of the timeline.

A task named in --mocks or --fail must be one the workflow can call; anything else is an error that lists the tasks. --fail takes precedence over --mocks.

Exit codes: 0 when the execution ends COMPLETED; 1 when it ends in any other status, cannot finish, or the file does not compile.

Generated results

A task with neither a mock nor a --fail returns a sample built from its contract’s output type. The rules are deterministic, so the same command always produces the same execution:

TypeSample
String"<field>-sample", using the field’s name, for example "transaction_id-sample"
Int1
Float1.0
Booltrue
Bytes"c2FtcGxl" (base64 of sample)
Duration1000 (one second)
Timestamp"2026-01-01T00:00:00Z"
UUID"00000000-0000-4000-8000-000000000001"
JSON{}
List<T>a list with one sample T
Map<String, T>{"key": <sample T>}
T?null
recordevery field: its default if it has one, otherwise a sample of its type

Samples follow the contract, not your business logic. In the order example, the generated risk score 1.0 is above the 0.8 threshold, so the payment step is skipped; give the task a realistic result with --mocks to take the other branch. The note on stderr lists which tasks got generated results.

Child workflows run as real child executions, and their tasks follow the same rules. The built-in orbit.http.request task is simulated like any other task, so a simulation never makes network requests.

Examples

A large order with a reviewer’s approval:

$ orbit simulate examples/approval.orbit --input '{"order_id":"T1","customer_id":"c-1","amount":2500}' --signal 'review={"approved":true,"reviewer":"dana"}'
simulated OrderApproval@1.0.0 (0ms simulated, no server or workers)
note: generated sample results for: notifications.send, payments.charge

+     0ms   #1  WorkflowCreated
+     0ms   #2  WorkflowStarted
+     0ms   #3  StepSucceeded             step=assess attempt=1
+     0ms   #4  SignalAwaitStarted        step=decision signal=review
+     0ms   #5  SignalReceived            name=review
+     0ms   #6  SignalDelivered           step=decision signal_seq=5
+     0ms   #7  StepScheduled             step=charge attempt=1
+     0ms   #8  StepDispatched            step=charge attempt=1 worker_id=simulator-python
+     0ms   #9  StepSucceeded             step=charge attempt=1
+     0ms  #10  StepScheduled             step=notify attempt=1
+     0ms  #11  StepDispatched            step=notify attempt=1 worker_id=simulator-python
+     0ms  #12  StepSucceeded             step=notify attempt=1
+     0ms  #13  WorkflowCompleted

COMPLETED sim  OrderApproval@1.0.0
  elapsed: 0ms

  STEP                         STATUS       ATTEMPT     TIME  DETAIL
  assess                       SUCCEEDED          1
  charge                       SUCCEEDED          1      0ms  worker simulator-python
  decision                     SUCCEEDED          1      0ms
  notify                       SUCCEEDED          1      0ms  worker simulator-python

  output: {"status":"charged","transaction_id":"transaction_id-sample"}

A declined card in the order pipeline, with a realistic risk score from a mocks file:

{ "risk.analyze": { "score": 0.3, "reasons": [] } }
$ orbit simulate examples/order_processing.orbit --input '{"order_id":"A4","customer_id":"c-4","amount":75,"items":[{"sku":"MUG","quantity":2}]}' --mocks mocks.json --fail payments.charge=CARD_DECLINED
simulated OrderProcessing@1.0.0 (0ms simulated, no server or workers)
note: generated sample results for: inventory.release, inventory.reserve, orders.validate

+     0ms   #1  WorkflowCreated
+     0ms   #2  WorkflowStarted
+     0ms   #3  StepScheduled             step=validate_order attempt=1
+     0ms   #4  StepScheduled             step=analyze_risk attempt=1
+     0ms   #5  StepDispatched            step=analyze_risk attempt=1 worker_id=simulator-python
+     0ms   #6  StepDispatched            step=validate_order attempt=1 worker_id=simulator-typescript
+     0ms   #7  StepSucceeded             step=analyze_risk attempt=1
+     0ms   #8  StepSucceeded             step=validate_order attempt=1
+     0ms   #9  StepScheduled             step=reserve_inventory attempt=1
+     0ms  #10  StepDispatched            step=reserve_inventory attempt=1 worker_id=simulator-typescript
+     0ms  #11  StepSucceeded             step=reserve_inventory attempt=1
+     0ms  #12  StepScheduled             step=process_payment attempt=1
+     0ms  #13  StepDispatched            step=process_payment attempt=1 worker_id=simulator-python
+     0ms  #14  StepAttemptFailed         step=process_payment attempt=1 error=CARD_DECLINED simulated failure of `payments.charge` (--fail)
+     0ms  #15  StepFailed                step=process_payment error=CARD_DECLINED simulated failure of `payments.charge` (--fail)
+     0ms  #16  StepCancelled             step=send_notification
+     0ms  #17  CompensationStarted       reason=step `process_payment` failed: simulated failure of `payments.charge` (--fail)
+     0ms  #18  StepScheduled             step=compensate:reserve_inventory attempt=1
+     0ms  #19  StepDispatched            step=compensate:reserve_inventory attempt=1 worker_id=simulator-python
+     0ms  #20  StepSucceeded             step=compensate:reserve_inventory attempt=1
+     0ms  #21  CompensationCompleted
+     0ms  #22  WorkflowFailed            error=CARD_DECLINED step `process_payment` failed: simulated failure of `payments.charge` (--fail)

FAILED sim  OrderProcessing@1.0.0
  elapsed: 0ms
  compensation: completed
  error: CARD_DECLINED step `process_payment` failed: simulated failure of `payments.charge` (--fail)

  STEP                         STATUS       ATTEMPT     TIME  DETAIL
  analyze_risk                 SUCCEEDED          1      0ms  worker simulator-python
  compensate:reserve_inventory SUCCEEDED          1      0ms  worker simulator-python
  process_payment              FAILED             1      0ms  simulated failure of `payments.charge` (--fail)
  reserve_inventory            SUCCEEDED          1      0ms  worker simulator-typescript
  send_notification            CANCELLED          0
  validate_order               SUCCEEDED          1      0ms  worker simulator-typescript

Child executions are printed after the parent’s timeline, under child execution <id> (<workflow>):.

When a simulation cannot finish

The simulation stops, with a warning on stderr, when the execution cannot make progress: an await step without a timeout and no --signal left to send for it, or a step whose worker never answers. A signal the execution never waited for is reported as warning: signal `review` was not sent: the execution never waited for it. In both cases the timeline and the state show where the execution stopped, and the exit code is 1.

JSON output

--json prints one object on stdout:

FieldMeaning
workflow, versionThe workflow that ran.
status, output, errorHow the execution ended.
simulated_msSimulated time from start to finish.
sampled_tasksTasks that got generated results.
problems, unsent_signalsWhy the simulation stopped early, and signals that were never sent.
executionThe final execution state, as GET /v1/executions/{id} returns it.
historyEvery history event, as GET /v1/executions/{id}/history returns them.
childrenChild executions, each with state and history.

The history array can be saved and replayed with orbit replay --history.

See also