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

Reference / Workflow

enter and exit (a state's arrival and departure hooks)

state Open { enter { … } exit { … } }

Run code when a run arrives in a state, and when it leaves. enter may redirect the run somewhere else; exit may not, because by the time it runs the move has already been decided. Both are optional and neither changes when the state's deadline clock counts.

stable1 example compiled by CIworkflowauthoring

Summary#

A state can run code at two moments: when a run arrives in it, and when a run leaves it.

state UnderReview {
  enter { this.Item.ReviewStartedAt = DateTime.UtcNow; }
  exit  { this.Item.ReviewMinutes = (DateTime.UtcNow - this.Item.ReviewStartedAt).TotalMinutes; }
}

Both are optional, and both run inside the same transaction as the move that triggered them — so if either throws, the move does not happen at all.

Signature#

state <Name> {
  enter { <statements> }    // on arrival — MAY `goto` to redirect the run
  exit  { <statements> }    // on departure — may NOT `goto`
}

Description#

Can enter/exit redirect with goto?#

enter may redirect: enter { if (order.Total > 10000) goto NeedsApproval; } sends the run somewhere else instead of settling in this state. That works because on arrival, where the run ends up is still the thing being decided.

exit may not, and a goto in one is a compile error. By the time an exit body runs the transition has already been decided — something chose the destination, and this body is running because of that choice. A redirect here would either silently override a decision already made, or, if two states' exit bodies each redirected into the other, bounce between them for ever with no arm to break the cycle.

The refusal names where the decision does belong:

an `exit { }` body may not `goto` — state 'Open' runs its exit body BECAUSE a transition was already decided, so a
`goto` here would override a choice already made (and two states redirecting to each other would never settle). Put
the decision where it is still open: an `on` arm, a `complete when`, or the destination state's own `enter { }`,
which MAY redirect.

It reads the same way as the rule for milestones, which likewise may not goto from their enter: a body that runs as a consequence of a decision does not get to re-make it.

In what order do exit, enter and the deadline run?#

Leaving A for B runs, in one transaction:

  1. A's exit { }
  2. B's enter { }
  3. B's waits and deadline are armed

So an exit body sees the world as it was in the state it is leaving, before anything about the destination applies.

Deadlines and Accrues#

A state's SLA clock has already stopped by the time its exit body runs. Accrues measures time spent in the state, and work done on the way out is not that — so an exit body can never inflate the very measurement it is often there to record.

Do enter/exit run on a terminal state?#

A terminal state can declare an enter { }, which runs as the run finishes. It can declare an exit { } too, but nothing will ever run it: a terminal is where runs stop. Prefer putting the work in enter.

Examples#

Recording how long a review took — the pair working together, which is what exit exists for:

entity Review {
  [Required, MaxLength(200)] string Title;
  DateTime? StartedAt;
  int Minutes;
  ReviewStatus Status = ReviewStatus.Pending;
}
enum ReviewStatus { Pending, UnderReview, Done }

workflow ReviewFlow {
  Tracks    = Review.Status;
  Autostart = false;
  Initial   = Pending;

  event Begin();
  event Finish();

  state Pending {
    subscribe Begin() as B;
    on B { goto UnderReview; }
  }

  state UnderReview {
    enter { this.Item.StartedAt = DateTime.UtcNow; }
    // Runs on the way out, whichever arm caused the move — so a second way out of this state cannot forget it.
    exit  { this.Item.Minutes = this.Item.Minutes + 1; }

    subscribe Finish() as F;
    on F { goto Done; }
  }

  terminal success Done { }
}

That last point is the practical argument for exit over repeating the line at the end of every arm: a state with three ways out needs the bookkeeping written once, and a fourth arm added later cannot silently omit it.

See also#

Related

Tracks and Initial (the field a workflow drives)

Names the enum field a workflow owns and the state a run starts in. No application code may write that field, and when…

complete when (a state's own completion condition)

Declare, once on a state, the condition under which that state is finished and where the run goes next. It is…

Assigned / Finished (milestones)

A milestone puts an SLA on a slot's progress — Assigned (someone must PICK IT UP within Within) and Finished (it must…

ServiceHours (SLA-accrual windows)

A schedule the SLA clock accrues within — the platform WALKS its weekly windows (and holiday exceptions) to advance…