Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Workflow

Workflow.Run (start a workflow)

Workflow.Run(entity) · await Workflow.Run("label", entity)

Start the workflow bound to an entity's type, on that entity. Bare — `Workflow.Run(order)` — is fire-and-forget: start it and carry on. Awaited — `await Workflow.Run("fulfil", order)` — is a durable wait on the started (child) workflow: hold until it reaches a terminal, then return on success or throw `WorkflowError` / `WorkflowCancelled` on an error / cancel terminal, so a parent flow can compensate with an ordinary `try`/`catch`.

stable1 example compiled by CIworkflowauthoringsaga

Summary#

Workflow.Run(order) starts the workflow whose target binds the entity's type, on that entity — the same inference Raising a workflow event uses for its events. There are two forms, and the only difference is await:

  • Workflow.Run(order) (bare) — fire-and-forget: start it and carry straight on. The call returns nothing.
  • await Workflow.Run("fulfil", order) — a durable wait: hold here until the started workflow reaches a terminal, then hand its outcome back. A success terminal lets the next line run; an error terminal throws WorkflowError; a cancel terminal throws WorkflowCancelled. That makes a child workflow a step you can wrap in an ordinary try/catch and compensate — the Saga pattern.

await carries meaning only on Workflow.Run — it is the one place in Osy# where you wait. Everywhere else effects run in place, so await is never written.

Signature#

Workflow.Run(order)          // fire-and-forget — start it, carry on; returns nothing
await Workflow.Run("fulfil", order)    // wait for the started workflow's terminal, then return / throw on its outcome

entity is an entity-typed value whose type has exactly one workflow bound to it in the same unit. More than one is a compile error (the target is ambiguous); none is a compile error (nothing to start).

Description#

A workflow is bound to an entity type (Tracks = <Entity>.<Enum>;). Workflow.Run(order) starts that workflow on the given order row. Use the bare form when the started workflow runs independently — you don't need its result:

var welcome = new WelcomeEmail { Customer = this.Item };
Workflow.Run(welcome);        // kick it off; this flow carries on

Use the awaited form when the started workflow is a step whose outcome you act on — the essence of a Saga. The started (child) workflow's terminal surfaces at the await:

  • a success terminal → the await returns and the next line runs;
  • an error terminal → the await throws WorkflowError;
  • a cancel terminal → the await throws WorkflowCancelled.

Both faults carry the terminal state's message, and both are ordinary catchable exceptions — so a compensating flow is just try/catch:

try {
  await Workflow.Run("payment", payment);        // hold until the payment workflow reaches a terminal
  goto Confirmed;                      // reached only if it SUCCEEDED
} catch (WorkflowError e) {
  await Workflow.Run("refundHold", new RefundHold { Order = this.Item });   // compensate — undo the earlier step
  goto Refunded;
}

WorkflowError (an error terminal) and WorkflowCancelled (a deliberate cancel) are distinct on purpose: catch them separately when a cancel is not a failure. A catch (Exception e) still catches either.

Examples#

enum OrderState { Placed, Done }

[Principal]
entity Person {
  [Required, MaxLength(200)] string Email;
  security { allow read, create when IsAuthenticated; }
}

entity Order {
  [Required, MaxLength(60)] string Reference;
  OrderState Status;                       // no default: the workflow owns this field
  security { allow read, create, update when IsAuthenticated; }
}

enum NoticeState { Sending, Sent }

entity ShipmentNotice {
  [Required] Order Order;
  NoticeState Status;
  security { allow read, create, update when IsAuthenticated; }
}

workflow NoticeFlow {
  Tracks    = ShipmentNotice.Status;
  Autostart = true;
  Initial   = Sending;
  state Sending { on Complete { goto Sent; } }
  terminal success Sent { }
}

workflow OrderFlow {
  Tracks    = Order.Status;
  Autostart = true;
  Initial   = Placed;

  event Ship();

  state Placed {
    subscribe Ship();
    on Ship {
      // Fire-and-forget: the notice runs on its own, with its own durability.
      // The argument is an entity-typed VARIABLE — `Workflow.Run(new …)` is refused.
      var notice = new ShipmentNotice { Order = this.Item };
      Workflow.Run(notice);
      goto Done;
    }
  }
  terminal success Done { }
}

Fire-and-forget — start a notification workflow and move on:

var notice = new ShipmentNotice { Order = this.Item };
Workflow.Run(notice);

Awaited step with compensation — the Saga shape:

try {
  await Workflow.Run("reservation", reservation);     // a child workflow; wait for its terminal
} catch (WorkflowError e) {
  goto Rejected;                       // it failed — route accordingly
} catch (WorkflowCancelled e) {
  goto Cancelled;                      // it was cancelled — a different route
}
goto Reserved;                         // it succeeded

Notes#

  • await is the wait, and the only wait. Workflow.Run without await never blocks; with await it holds until the started workflow terminates. Writing await on anything else is a compile error.
  • The started workflow is inferred from the entity's type, exactly like Raising a workflow event. Keep one workflow per bound type, or the target is ambiguous.
  • The wait is durable. When the started workflow waits — on a human step, a timer, or its own child — the awaiting flow parks: it is persisted and lifted off the thread, then resumes exactly where it paused when the child reaches a terminal, even across a restart. You write straight-line await; the platform owns the pause. Because it parks, the compensating await inside a catch (or a finally) works too — the whole try/catch survives the wait.

See also#

Related

Raising a workflow event

Raise a typed event on the run bound to an entity, from anywhere — an ordinary server function, a webhook handler, a…

Step labels (naming a child run so it survives a new version)

Every child workflow you AWAIT carries a label — a literal string you choose, naming that step. Awaiting parks the run…

For(entity).Audit

Reads a running instance's lifecycle timeline — every transition, claim, deposit, reminder and refusal as an…