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

Reference / Workflow

slot dependencies (After / When / Pending)

subscribe Event(...) as Alias { When = <predicate>; After = [Predecessor, ...]; }

Per-slot ordering and conditioning. `After = [A, B]` holds a slot CLOSED (status `Pending`, no clock) until every named sibling slot is satisfied; `When = <predicate>` decides — WHEN THE SLOT WOULD OPEN — whether it exists at all. Together they express the parallel-then-serial shape a real approval chain has.

stable1 example compiled by CIworkflowauthoring

Summary#

Two per-slot settings order and condition a wait. After = [A, B] makes a slot open only once every named sibling slot is satisfied — until then it sits Pending: visible, but not claimable and with no SLA clock running. When = <predicate> decides whether the slot EXISTS at all, evaluated when the slot would open (not at state entry) — so a value that changes mid-review (an expense amount raised past a threshold) correctly grows a slot that did not exist before. Slots with neither open immediately, in parallel.

Signature#

subscribe <Event>(<typed params>) as <Alias> {
  When  = <predicate over this.Item>;      // the slot only EXISTS while this holds
  After = [<SiblingAlias>, <SiblingAlias>]; // …and only OPENS once all of these are satisfied
}

Description#

  • After — a dependency, not a mode. After names sibling slots of the same state. A slot with predecessors is created Pending at state entry and cannot be claimed or deposited into (a deposit is refused, like an out-of-pool one); no clock runs, so it can never breach for a predecessor's slowness. When the LAST predecessor is satisfied the slot opensUnassigned (or Assigned if it declares an Assignee) — and its SLA clock starts.
  • When is evaluated at OPEN time. For a slot with When and After, the condition is checked at the moment its predecessors complete, over the current this.Item. A slot whose When is false is never created — not Pending, not shown, not counted toward completion. If the condition only becomes true later (the amount was raised), the slot grows then. For a slot with When but no After, the condition is evaluated at state entry.
  • Completion. on Complete fires when every slot that EXISTS AND IS OPEN is satisfied. A When-false (never created) slot does not block completion; a Pending or open-but-unsatisfied slot does.
  • Observing it — SlotStatus + .Slots. The SlotStatus enum (Pending, Unassigned, Assigned, Satisfied, Cancelled, Breached) is the type of Wf.For(entity).<Slot>.Status. Wf.For(entity).Slots returns the run's live slots (List<SlotView>, each with .Name and .Status) — a slot the When gate never created is simply absent, so Wf.For(e).Slots.Any(s => s.Name == "Cfo") is false for an expense below the threshold.

Examples#

Manager and Finance approve in parallel; the CFO slot opens only after both, and only for large expenses:

enum Decision      { Approve, Reject }
enum RequisitionStatus { Approvals, Approved, Rejected }
enum Role          { Staff, Finance, Cfo }
enum Dept          { Engineering, Finance, Legal }

// The entities the workflow reaches into. The example named all four and declared none — which the
// gate could not see while the fence was exempt from it.
[Principal]
entity Person {
  [Required, MaxLength(200)] string Email;
  Role Role = Role.Staff;
  Dept Department = Dept.Engineering;
  Person Manager;
  security { allow read, create when IsAuthenticated; }
}

entity Requisition {
  [Required] Person Employee;
  decimal Cost;
  RequisitionStatus Status;   // no default: the workflow owns this field
  security { allow read, create, update when IsAuthenticated; }
}

entity Approval {
  [Required] Requisition Requisition;
  Person By;
  DateTime At;
  security { allow read, create when IsAuthenticated; }
}

workflow RequisitionApproval {
  Tracks    = Requisition.Status;
  Autostart = true;
  Initial   = Approvals;

  event Approve(Decision decision);

  state Approvals {
    subscribe Approve(Decision decision) as Manager {
      Assignee = this.Item.Employee.Manager;
    }
    subscribe Approve(Decision decision) as Finance {
      Candidates = u => u.Department == Dept.Finance;
    }
    // Pending until BOTH predecessors are satisfied — and only exists for large expenses.
    subscribe Approve(Decision decision) as Cfo {
      When       = this.Item.Cost > 10000;
      After      = [Manager, Finance];
      Candidates = u => u.Role == Role.Cfo;
    }

    on Approve(Decision decision, Slot slot) {
      new Approval { Requisition = this.Item, By = slot.Assignee, At = DurableClock.Now };
    }
    on Complete { goto Approved; }
  }

  terminal success Approved { }
  terminal error   Rejected { Message = "expense rejected"; }
}

Observing the CFO slot's lifecycle from a test (or a UI):

Assert.Equal(SlotStatus.Pending, RequisitionApproval.For(e).Cfo.Status);      // visible, not open
runas (Mia)  { RequisitionApproval.For(e).Manager.Approve(Decision.Approve); }
Assert.Equal(SlotStatus.Pending, RequisitionApproval.For(e).Cfo.Status);      // one predecessor down
runas (Otto) { RequisitionApproval.For(e).Finance.Approve(Decision.Approve); }
Assert.Equal(SlotStatus.Unassigned, RequisitionApproval.For(e).Cfo.Status);   // NOW it opens — clock starts

// a small expense never grows a CFO slot at all
Assert.False(RequisitionApproval.For(small).Slots.Any(s => s.Name == "Cfo"));

See also#

  • subscribe — the wait these settings condition
  • Requires — named preconditions, and the live checklist — the other completion gate (a quorum predicate)
  • <span class="planned" title="this page is planned and not written yet">workflow-route</span> — the event-keyed arm shared by the sibling slots (and its Slot slot param)
  • fan-out (foreach subscribe) — many parallel slots from one declaration
  • <span class="planned" title="this page is planned and not written yet">workflow-state</span> — the enclosing state

Related

subscribe

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

Requires — named preconditions, and the live checklist

Named conditions that must hold before something may happen, declared on a state or on a slot — and readable as a live…

fan-out (foreach subscribe)

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