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 runSignature#
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.
Osysharp.Llmcharges every model call made in a run to the run's workflow and to the run itself, so what a process cost is counted as it happens (Model spend — budgets, the meter and the price book (Llm kit)).Osysharp.Agentswrites the run on the receipt of a piece of work given in one, so the work is found from the run and the run from the work (Agents in business processes — an agent's work as a step of a workflow, told on the run (Agents.Processes kit)).Osysharp.Processeskeeps each line a process says about itself (Processes.Note(…)) with the state it was said in (Processes — every run of a business process, listed and read from start to now (Processes kit)).
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.Currentis 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#
- Workflow.InRun (can this code wait?) — is this code in a run at all, and may it wait?
- Workflow.Run (start a workflow) — starting a run, and waiting for one
- 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 as a step of a process
- Processes — every run of a business process, listed and read from start to now (Processes kit) — every run of a process, listed and read