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

Reference / Workflow

complete when (a state's own completion condition)

complete when (<predicate>) goto <State>;

Declare, once on a state, the condition under which that state is finished and where the run goes next. It is re-checked after anything happens in the state, so the handlers can just record what happened instead of each one deciding whether the work is over. Reach for it when a state ends because of a condition over your own data rather than because one particular event arrived.

stable1 example compiled by CIworkflowauthoring

Summary#

Some states end because a particular thing happened. Others end because the world reached a particular shape — every item settled, every reviewer answered, nothing left outstanding. For the second kind, the state is not waiting for an event; it is waiting for a condition.

complete when (<predicate>) goto <State>; says exactly that, once, on the state. After anything happens in that state the predicate is re-checked, and when it holds the run moves on.

Signature#

state <Name> {
  complete when (<predicate over this.Item>) goto <State>;
  …
}

One per state. The predicate is an ordinary boolean expression with this.Item — the row the workflow tracks — in scope, so it can call your own functions and read your own tables.

Description#

Without it, the condition has to be repeated at the end of every handler that could be the last thing to happen:

on ShipmentDelivered(Shipment shipment) { …; when (AllItemsSettled(this.Item)) { goto Settled; } }
on ShipmentLost(Shipment shipment)      { …; when (AllItemsSettled(this.Item)) { goto Settled; } }
on CustomerWithdrawsItem(OrderItem item){ …; when (AllItemsSettled(this.Item)) { goto Settled; } }

That works, and it quietly gets worse as the workflow grows. The obligation lands on every handler you add, and it is invisible at the moment it matters: add a fourth handler, forget the line, and the run stays in that state for ever. Nothing reports it — there is no error, no timeout, no failed step. It simply never finishes.

Declared once, the handlers go back to doing one job each:

state Fulfilling {
  // Asked ONCE, on the state — not at the end of each handler. Re-checked after anything happens here.
  complete when (AllItemsSettled(this.Item)) goto Settled;

  on ShipmentDelivered(Shipment shipment)  { shipment.Delivered = true; }
  on ShipmentLost(Shipment shipment)       { shipment.Lost = true; }
  on CustomerWithdrawsItem(OrderItem item) { item.Withdrawn = true; }
}

Written out rather than pulled from an app, because no shipped sample uses complete when yet — the fence is preview for that reason as well as the surface's own. When a sample adopts it, this becomes a sample= pull like the rest, and stops being prose that can drift.

An explicit goto still wins. A state can be left for reasons that have nothing to do with being finished — a cancellation, a customer giving up. A handler that decides to leave goes where it says; the completion condition is only consulted when the handler did not already transition.

When it is checked. After a handler for that state has run and its changes are committed — so the predicate sees the world that handler left, including the rows it just wrote. It is not a poll: nothing re-checks it while the state is idle, because nothing has changed.

A condition that already holds ends the state at the first opportunity. A job whose work is already complete is complete; that is not a special case to guard against.

Examples#

enum Decision   { Approve, Reject }
enum OrderState { Fulfilling, Shipped }

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

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

workflow Fulfilment {
  Tracks    = Order.Status;
  Autostart = true;
  Initial   = Fulfilling;

  event Pack();

  state Fulfilling {
    subscribe Pack();
    on Pack { }

    Requires {
      Packed { Must    = this.Item.Reference != "";
               Message = "Every line has to be packed first."; }
    }
    on Complete { goto Shipped; }
  }
  terminal success Shipped { }
}

An order that is finished when every one of its items has come to rest — whichever way each one got there, and however many shipments it took:

state Fulfilling {
  enter { SourcePendingItems(this.Item); }

  complete when (AllItemsSettled(this.Item)) goto Settled;

  subscribe ShipmentDelivered(Shipment shipment);
  subscribe CustomerWithdrawsItem(OrderItem item);

  on ShipmentDelivered(Shipment shipment) { … }   // records deliveries
  on CustomerWithdrawsItem(OrderItem item) { … }  // records a refund
}

Notes#

This is not a join, and the difference is worth knowing. A join is for "I started this specific set of work and I am waiting for it to come back." A completion condition is for "I am waiting for my own data to reach a shape." The order above cannot use a join: the number of shipments is unknowable when it starts, shipments come and go underneath it, and a lost one puts its items back into the pool to be sent again. What the order waits for is a fact about its items, not a set of tasks.

It is not valid on a terminal. A terminal is where a run ends, so there is no completion left to condition; declaring one there is a compile error rather than a line that is silently ignored.

See also#

Related

subscribe

Declares that a workflow state waits on an event, and configures the wait — who may hold it, who may hand it on…

Workflow.Run (start a workflow)

Start the workflow bound to an entity's type, on that entity. Bare — `Workflow.Run(order)` — is fire-and-forget: start…

fan-out (foreach subscribe)

One `subscribe` declaration that expands into MANY parallel wait slots — one per element of a collection. A fan-out…