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

Reference / Workflow

Workflow.Inbox<T> (what is waiting for me)

Workflow.Inbox<T>()

The current principal's queue: every slot they can act on, across every run of every workflow that tracks T. Rows carry the tracked entity typed, so a screen renders it without a read per row.

stable1 example compiled by CIworkflowauthoring

Summary#

Every other way of reading a workflow starts from one row: what is the state of this invoice? An inbox asks the opposite question — what is waiting for me? — and it crosses every run.

Workflow.Inbox<T>() answers it for whoever is asking right now.

Signature#

Workflow.Inbox<TrackedEntity>()

No arguments: it is the CURRENT principal's queue. Filter, order and count it like any other list.

Description#

What is in it#

A slot is in your queue when it is assigned to you, or it is open and you satisfy its Candidates. Slots that are waiting on something else are included too, carrying their status — so a screen can grey out "CFO, waiting for Manager" rather than hiding work that is coming.

T is the entity a workflow Tracks. Asking for an entity no workflow drives is a compile error, because a queue is a thing a workflow produces.

The row#

memberwhat it is
Itemthe tracked entity, typed — an Invoice, not an id
SlotAliaswhich slot is asking
WorkflowNamethe workflow it belongs to
Statusthe slot's live status
OpenedAt / BreachesAtwhen it arrived, and when it runs out of time
RunId / SlotIdthe run and the slot themselves

The alias is not decoration. "Waiting on you as their manager" and "waiting on you as finance" are different asks, and the same person can hold both on different items. A queue that could not tell them apart would be showing one list where there are two.

BreachesAt is the sort key. For a workflow that rejects on breach, "3 hours left" is the most actionable thing on the row. It is null when a slot has no deadline — absence, not a far-future date to filter around.

OpenedAt is when the slot opened, not when the run reached the state. For a slot held closed by After those are different moments — it waits Pending while its predecessors run — and the row reports the later one. So "waiting since" means waiting on you, and it agrees with BreachesAt, whose clock starts at the same moment.

Reading through Item#

Item is the tracked entity, so a screen reads it directly — that is the point of the queue being typed rather than a list of ids. Navigating it needs it included, as any reference does:

enum ClaimStage { Filed, Approved, Rejected }

[Principal] entity Employee {
  [Required] [MaxLength(80)] string DisplayName;
  security {
    allow read   when IsAuthenticated;   // a principal row is not public — say who may read it
    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"; }
}

int WaitingOnMe() {
  return Workflow.Inbox<Invoice>()
                 .Include(r => r.Item.Owner)
                 .OrderBy(r => r.BreachesAt)
                 .Count();
}

Whoever is asking#

The queue is defined against the current principal, and it follows runas. That is deliberate: a workflow can put an agent in a slot as readily as a person, so "what is waiting on this principal" stays one question rather than growing a second surface for the non-human case.

An anonymous caller has an empty queue — nothing is waiting on nobody. That is an answer, not an error, so a public page still renders.

It is live#

The queue is re-read, not remembered. Act on a slot and it leaves your queue; it does not linger as a row that does nothing when clicked. One person acting does not change anyone else's queue.

Answering a row#

Workflow.Deposit(row, Decide(true)) answers the slot a row is, and Workflow.Claim(row) / Workflow.Release(row) take and hand back unassigned work. The event is named at the call site because a queue's rows are heterogeneous — which slot a row turned out to be is known only once the queue has been read.

Examples#

Only the work of one kind, most urgent first:

Workflow.Inbox<Invoice>()
  .Include(r => r.Item)
  .Where(r => r.SlotAlias == "Finance")
  .OrderBy(r => r.BreachesAt)

Notes#

Authorization is the workflow's, not the queue's. Membership is decided by the same Candidates evaluation that governs claiming and depositing — the queue does not get its own rules. A queue is exactly the place a second, looser answer would otherwise appear, and there is deliberately nowhere for one to live.

It runs on the server. Deciding what a principal may act on is not a question a client can be trusted to answer about itself.

See also#

Related

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

Answer a queued slot from the row itself. The event is named at the call site because a queue's rows are heterogeneous…

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…

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…

Include (pre-loading relations)

Pre-load the related rows a query's results are about to navigate to. `Include(o => o.Lines)` does not change what…