Osy#betaRun it on Osyrin: osyrin.com

Reference / Workflow

Workflow.Current (which run is this code in?)

Workflow.Current → WorkflowRunInfo? · .Id → Guid · .Workflow → string · .State → string · .Item → Guid? · .Starter → Guid?

`Workflow.Current` is the workflow run this code is executing in — one value saying its id, the workflow's declared name, the state whose body is running, the id of the row the run tracks, and the run that started it from its own body. It answers in a workflow's body and in everything that body calls; anywhere else it is null. A kit that records what was done for a process — an agent's receipt, the cost of a model call — reads it to say which run the work belongs to, with nothing written at the call.

preview1 example compiled by CIworkflowauthoringstdlib

Summary#

A function called from a workflow's body often needs to say which run it worked for — to write that on a row it keeps, or to charge what it spent there. It reads it:

var run = Workflow.Current;               // WorkflowRunInfo? — null when this code is in no run
if (run != null) {
  note.Run = run.Id;                      // the run's own id
  note.Workflow = run.Workflow;           // "Onboarding" — the workflow's declared name
  note.State = run.State;                 // "Vetting" — the state whose body is running
  note.About = run.Item;                  // the id of the row the run tracks
}
note.Run = Workflow.Current?.Id;          // …or one fact, null outside a run

Signature#

Workflow.Current -> WorkflowRunInfo?      // the run this code is in; null outside one

public sealed class WorkflowRunInfo {     // a core class: always in scope, no `using`
  public Guid   Id;                       // the run
  public string Workflow;                 // the workflow's declared name
  public string State;                    // the state whose body is running, by name
  public Guid?  Item;                     // the id of the row the run tracks (`this.Item.Id`)
  public Guid?  Starter;                  // the run whose body started this one; null for a run nothing started from a body
}

Description#

Where does it answer?#

Wherever Workflow.InRun is true: in a workflow's enter, exit, on, Start and reminder bodies, and in every function, method and lambda those call, to any depth. A page's action, a function called over the API, an [On] handler and a test's own body are in no run: Workflow.Current is null there.

What is it for?#

For code that works for a run without being part of its workflow — a kit, usually. The workflow's own body has this.Item and knows its own name; a function three calls down has neither, and must not be handed them as arguments by every caller in between.

What is Item, and what may be done with it?#

The id of the row the run tracks — what the body itself calls this.Item.Id. It says which row, and nothing of what is in it: reading that row is an ordinary read, under the row's own security, by whoever is reading.

What is Starter?#

The run whose body started this one — by await Workflow.Run("step", child), as a parallel leg, or as a saga's undo. It is null for a run that autostarted or that Workflow.Run(entity) started from outside any body. Walking it upward finds the run at the top of a tree of runs.

Is it the same for the whole of a run?#

Yes. Inside one run every read answers the same id, workflow, item and starter — also after the run was parked for a week and resumed — and State is the state whose body is running. So it is read where it is needed, twice in one body if it helps; there is nothing to keep and nothing to name. Each read answers a value of its own, which is yours to hold: it does not change under you, and changing it changes nothing.

A run that was started under an older version of the app answers with the names that version declared.

Examples#

app Letters {
  use Osysharp.Workflow;
  model "model/**/*.osy";
}

using Osysharp.Workflow;

enum LetterStatus { Drafting, Checking, Sent }
enum CheckStatus  { Asked, Passed }

entity Letter {
  LetterStatus Status;
  /// What the body says of its own run, state by state.
  [MaxLength(400)] string Seen = "";
  Guid? Run;
  /// The row the run says it tracks.
  Guid? Tracked;
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

entity Check {
  CheckStatus Status = CheckStatus.Asked;
  [MaxLength(200)] string Seen = "";
  Guid? Run;
  Guid? StartedBy;
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

/// ⭐ Which run is this code in? A plain function: it answers for whichever body called it.
string WhereAmI() {
  var run = Workflow.Current;
  return run == null ? "in no run" : run.Workflow + " at " + run.State;
}

workflow CheckFlow {
  Tracks = Check.Status; Autostart = false; Initial = Asked;
  state Asked {
    enter {
      this.Item.Seen = WhereAmI();
      this.Item.Run = Workflow.Current?.Id;
      this.Item.StartedBy = Workflow.Current?.Starter;  // the run whose body started this one
      goto Passed;
    }
  }
  terminal success Passed { }
}

workflow LetterFlow {
  Tracks = Letter.Status; Autostart = true; Initial = Drafting;
  state Drafting {
    enter {
      this.Item.Run = Workflow.Current?.Id;
      this.Item.Tracked = Workflow.Current?.Item;       // this.Item's own id — for code that has no `this.Item`
      this.Item.Seen = WhereAmI();
      goto Checking;
    }
  }
  state Checking {
    enter {
      var check = Workflow.Once("check", () => new Check { });
      await Workflow.Run("check", check);
      this.Item.Seen = this.Item.Seen + ", " + WhereAmI();
      goto Sent;
    }
  }
  terminal success Sent { }
}

[Test]
void outside_a_run_there_is_none() {
  Assert.Null(Workflow.Current);
  Assert.Null(Workflow.Current?.Id);
  Assert.Equal("in no run", WhereAmI());
}

[Test]
void a_body_and_everything_it_calls_know_their_run() {
  var letter = new Letter { };
  Workflow.Settle(letter);
  Assert.Equal(LetterStatus.Sent, letter.Status);
  Assert.Equal("LetterFlow at Drafting, LetterFlow at Checking", letter.Seen);
  // The run it awaited is another run: its own id, its own workflow — and it knows which run started it.
  var check = Check.Single();
  Assert.Equal("CheckFlow at Asked", check.Seen);
  Assert.NotNull(letter.Run);
  Assert.Equal(letter.Id, letter.Tracked);
  Assert.True(check.Run != letter.Run);
  Assert.Equal(letter.Run, check.StartedBy);
}

[Test]
void the_value_is_the_callers_to_keep() {
  var letter = new Letter { };
  Workflow.Settle(letter);
  // What a body read is a plain value: it was stored on the row, and says the same long after the run has ended.
  Assert.Equal(letter.Id, Letter.Single().Tracked);
  Assert.Null(Workflow.Current);                        // …and the test's own body is still in no run
}

Notes#

  • They say where the code is running, not who is running it — that is actor — who just did this.
  • The run's rows are the engine's own, and an app cannot read them (For(entity).Audit is how an app reads what happened to a run, by the row it tracks). Workflow.Current is what a run says of itself.
  • A function reached from an [On] handler is in no run, even when the event was raised in one: record the run where the event is raised, and carry it on the event or on the row it is about.

See also#

Related

Workflow.InRun (can this code wait?)

`Workflow.InRun` is true in a workflow's body and in everything that body calls — where `await Workflow.Run(…)` may…

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…

For(entity).Audit

Reads a running instance's lifecycle timeline — every transition, claim, deposit, reminder and refusal as an…

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…

Agents in business processes — an agent's work as a step of a workflow, told on the run (Agents.Processes kit)

An agent's work is a step of a business process by being called from the process's workflow: an awaited call in a…

Processes — every run of a business process, listed and read from start to now (Processes kit)

Osysharp.Processes makes the runs of an app's workflows followable by the people who answer for them. The app names…

Model spend — budgets, the meter and the price book (Llm kit)

The Llm kit keeps three things about what model calls cost. The METER: one `ModelCall` row for every attempt, with the…