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

Reference / Workflow

Acting on an inbox row (deposit, claim, release)

Workflow.Deposit(row, Event(args…)) · Workflow.Claim(row) · Workflow.Release(row)

Answer a queued slot from the row itself. The event is named at the call site because a queue's rows are heterogeneous — which slot a row is, and so which verb it takes, is known only when the queue is read.

stable1 example compiled by CIworkflowauthoring

Summary#

Workflow.Inbox<T>() tells a person what is waiting for them. These three verbs are how they answer it — from the row, without knowing in advance which slot it turned out to be.

Signature#

Workflow.Deposit(row, <Event>(args…))   // answer the slot this row is
Workflow.Claim(row)                      // take an unassigned slot you are eligible for
Workflow.Release(row)                    // hand a claimed slot back to the pool

Description#

Why the event is named, and not called#

Everywhere else a deposit is spelled <Workflow>.For(entity).<Slot>.<Event>(…) — every part of it written by the author. A queue does not work that way. One row may be a manager's decision and the next a finance sign-off, so row.Approve(…) cannot exist: the verb set differs per workflow, and a given row's verb is only known once the queue has been read.

So the event is an argument, the same shape Workflow.Run (start a workflow) uses when you raise one by name (OrderFlow.RaisePayment(order, 100)) — and the split in checking follows from that:

checked bywhat it checks
the compilerEvent is declared by a workflow that Tracks T, and its arguments type-check
the runthis row's slot is actually waiting for that event — and Candidates, Requires and quorum

Naming an event the row is not waiting for is refused, not deposited. That matters more than it sounds: arguments bind to the arm by name, so a wrong event's payload would otherwise arrive as parameters nobody set.

It is the same deposit#

There is one deposit in the platform and this is it. A row does not carry permission — obtaining it changes nothing about who may act. A principal holding someone else's row and naming the right event is still refused by the slot's own Candidates, because the queue has no rules of its own and deliberately nowhere to keep any.

Claiming#

The queue shows unassigned work you are eligible for, so Workflow.Claim(row) takes it and Workflow.Release(row) gives it back. Both need a principal — nothing can hold a slot on nobody's behalf. Only the current holder may release.

To hand a slot to a named colleague rather than back to the pool, there is a third verb — Workflow.Assign(row, principal). It is separated out because it is the one act here that asks about the actor as well as the target: eligibility to hold work is not authority to move it.

Examples#

The morning screen, and the button on it:

enum ClaimStage { Filed, Approved, Rejected }

[Principal] entity Employee {
  [Required] [MaxLength(80)] string DisplayName;
  security {
    allow read   when IsAuthenticated;
    allow create when IsAuthenticated;
  }
}

entity Invoice {
  [Required] [MaxLength(120)] string Title;
  [Required] decimal Amount;
  [Required] Employee Owner;
  ClaimStage Stage;
  security {
    allow read, update when IsAuthenticated;
    allow create       when IsAuthenticated;
  }
}

workflow ExpenseApproval {
  Tracks    = Invoice.Stage;
  Autostart = true;
  Initial   = Filed;

  event Decide(bool approved);

  state Filed {
    subscribe Decide(bool approved) as Manager { Assignee = this.Item.Owner; }
    on Manager(bool approved) {
      when (approved) { goto Approved; }
      default { goto Rejected; }
    }
  }

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

void ApproveOldest() {
  var oldest = Workflow.Inbox<Invoice>()
                       .OrderBy(r => r.OpenedAt)
                       .First();
  Workflow.Deposit(oldest, Decide(approved: true));
}

Once it is answered the row leaves the queue, because the queue is re-read rather than remembered.

Notes#

A fanned-out row acts on its own slot. A row addresses the slot it is a row for, so one principal holding two instances of the same fanned-out slot answers the one they picked — not whichever an alias lookup would have found.

The compiler checks less here, and nothing enforces less. The event and its arguments are supplied at the call site rather than derived from a slot the author named, so a mistake that the static form would catch at compile time is caught at run time instead. What is allowed is unchanged: the same authorization, the same requirements, the same quorum.

See also#

Related

Workflow.Inbox&lt;T&gt; (what is waiting for me)

The current principal's queue: every slot they can act on, across every run of every workflow that tracks T. Rows carry…

subscribe

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

Candidates (slot)

Declares WHO may hold or satisfy a `subscribe` slot. `Candidates` is one expression surface that dispatches on its…

Assign — handing a slot to a named colleague

Give a slot to somebody else. Claiming takes work for yourself and releasing puts it back in the pool; assigning is the…

slot dependencies (After / When / Pending)

Per-slot ordering and conditioning. `After = [A, B]` holds a slot CLOSED (status `Pending`, no clock) until every named…