# People in business processes — what a run asks of people, told on the run (HumanRequests.Processes kit)

> A process asks a person by putting a request from its workflow and awaiting the answer. With Osysharp.HumanRequests and Osysharp.Processes in an app, the HumanRequests.Processes bridge tells each such request on the run that put it, in the state that put it: who was asked — a person, a queue, somebody outside by a link —, that its date passed and whom it went on to, who answered and after how long, or that nobody did. Each line opens the request in place. A request records the run it was put in; one put from a page, or by an agent's own wait, is no step of a run and says nothing there.

<!-- id: workflow-process-requests · area: workflow · stability: preview · html: https://osysharp.com/reference/workflow/process-requests/ -->

## Summary        {#summary}
```osy syntax
use Osysharp.HumanRequests;
use Osysharp.Processes;          // …and this bridge is there: nothing to wire.

state Inspecting {
  enter {
    var ask = Workflow.Once("ask-the-warden", () => PutRequest(form));
    var answer = await Requests.Answer(ask);          // the run waits here, however long the person takes
    …
  }
}
```

On the run's page, under "Inspecting":

```text
Asked Rufus Okello: “Is plot 14 ready to let?”                              Waiting
Rufus Okello answered “Is plot 14 ready to let?” after 2 days.              Done
```

## Signature      {#signature}
```osy syntax
// Nothing to call: the bridge hears the request's own events.
//   HumanRequestPut · HumanRequestOverdue · HumanRequestAnswered · HumanRequestExpired · HumanRequestCancelled

HumanRequest.Run          → Guid?     // the run whose body put it; null: put outside a run
HumanRequest.RunWorkflow  → string    // that run's workflow, by name
HumanRequest.RunState     → string    // the state whose body put it
HumanRequest.RunItem      → Guid?     // the row the run tracks
HumanRequest.RunStarter   → Guid?     // the run that started it

RequestSubject() → "HumanRequest"     // what a line about a request says its subject is (`ProcessMoment.SubjectKind`)
AskedOf(HumanRequest r) → string      // "Rufus Okello" · "Tove Aldersey, by a link" · "whoever works the Finance queue"
```

## Description    {#description}

### How does a process ask a person?   {#ask}
With an ordinary [request](https://osysharp.com/reference/agent/human-requests/) — a question, an approval of one exact thing, a form — put from
a state's body and awaited: `await Requests.Answer(request)` parks the run until the answer comes, the request runs
out of time or is called off, and answers which. The `Workflow.Once` around the put is what keeps a body that is
run again from asking twice ([Workflow.Once (run a step at most once)](https://osysharp.com/reference/workflow/once/)).

One call site may be reached from several states — a process that asks somebody in each of five steps calls
`Requests.Answer` five times — and each waits for its own answer.

### What does the run's page say?   {#lines}
One line for each thing that happened to a request the run put, in the state that put it:

| When | The line | Read as |
|---|---|---|
| it is put | Asked *whom*: “*the question*” | waiting |
| its date passes | “*the question*” was not answered in time. It went on to *whom*. — or: It still waits. | a problem |
| it is answered | *Who* answered “…” after *how long*. · *Who* approved “…” after … · *Who* did not approve “…” after …: *their reason* | done |
| it runs out of the time it could wait at all | Nobody answered “…” in the time it could wait. | a problem |
| it is called off | “…” was called off: *why* | a note |

*Whom* is the person it was put to; the name somebody outside was given, with "by a link"; or the queue whose
people may answer it. *Who* answered is the person — or, through a link, the name they gave.

### What does opening a line show?   {#open}
The request itself, in place under the line: who asked, the question, what was known, and what came back — drawn by
`RequestSheet`, as whoever is looking may read it. Somebody who follows the process and may not read that request
is shown that it is not theirs.

### Which requests are told?   {#which}
The ones put **from the run's own body** — or from a function that body calls: a request records the run it was
put in (`HumanRequest.Run`, read from [`Workflow.Current`](https://osysharp.com/reference/workflow/run-facts/) where it is put). So:

- a request put from a page's action, an API call or an event handler is no step of a run, and no run says it;
- what an [agent](https://osysharp.com/reference/agent/processes/) proposes or asks while it works for a run is put when its work has stopped to
  wait, not from the run's body: the agent's work tells that itself ("…'s work waits for a person."), and the
  request is not said a second time.

### Who reads what?   {#who}
The lines are lines of the run: whoever follows processes and may read the row the run is about reads them
([[workflow-processes-kit#who]]). The request a line opens is read under the request's own rules — the person
asked, whoever asked, whoever oversees requests.

## Examples       {#examples}
```osy title="a plot changes hands: the warden is asked on a form, the next tenant through a link — and the run says so" run app=workflow-process-requests
app Allotments {
  use Osysharp.HumanRequests;
  use Osysharp.Processes;
  use Osysharp.HumanRequests.Processes;      // the bridge: with both kits it arrives by itself
  use Osysharp.WorkQueues;
  use Osysharp.Llm;
  use Osysharp.Identity;
  use Osysharp.Organizations;
  use Osysharp.Organizations.ServicePlans;
  use Osysharp.Presence;
  use Osysharp.Observability;
  use Osysharp.Workflow;
  use Osysharp.ServiceHours;
  use Osysharp.Diagrams;
  use Osysharp.Ui;
  model "model/**/*.osy";
}

using Osysharp.Identity;
using Osysharp.Organizations.ServicePlans;
using Osysharp.WorkQueues;
using Osysharp.HumanRequests;
using Osysharp.Processes;
using Osysharp.Workflow;
using Osysharp.ServiceHours;

app.Inbox = new InboxSetup { DefaultQueue = "Society" };

[Role] enum SocietyRole { Authenticator, Secretary, Warden }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsSecretary => RoleGrant.Any(g => g.Member == user && g.Role == SocietyRole.Secretary);
policy IsWarden    => RoleGrant.Any(g => g.Member == user && g.Role == SocietyRole.Warden);
policy IsOfficer   => IsSecretary || IsWarden;

// Each kit's questions, answered.
policy WorksInbox       => IsOfficer;
policy LeadsInbox       => IsSecretary;
policy AdministersInbox => IsSecretary;
policy ManagesOrganizations  => IsSecretary;
policy CollaboratesOnRecords => IsOfficer;
policy AsksPeople       => IsOfficer;
policy OverseesRequests => IsSecretary;
policy FollowsProcesses => IsSecretary;

entity Person : Principal {
  security { allow read when IsAuthenticated || IsAuthenticator; allow create when IsAuthenticator; }
}
partial entity ServiceHours { security { allow read when IsOfficer; } }
partial entity ServiceWindow { security { allow read when IsOfficer; } }
partial entity ServiceException { security { allow read when IsOfficer; } }
partial entity WorkItem { security { allow read when IsOfficer; } }

enum HandoverStage { Inspecting, Offering, Let, Withheld }

/// A plot that passes from one tenant to the next.
entity Handover {
  [Required, MaxLength(40)] string Plot;
  [Required] Person Warden;
  [MaxLength(120)] string NextTenant = "";
  [MaxLength(200)] string NextTenantEmail = "";
  HandoverStage Stage = HandoverStage.Inspecting;
  [MaxLength(300)] string Note = "";
  security { allow read, create, update when IsOfficer; }
}

/// What the warden is asked for.
public class PlotCondition {
  /// The plot is cleared and ready to let.
  public bool Ready;
  /// Anything the next tenant should know.
  [MaxLength(200)] public string? Remarks;
}

// ⭐ Nothing in the workflow says anything to the run's page: it asks, and waits.
workflow PlotHandover {
  Tracks = Handover.Stage; Autostart = false; Initial = Inspecting;
  state Inspecting {
    enter {
      var ask = Workflow.Once("ask-the-warden", () => AskTheWarden(this.Item));
      var answer = await Requests.Answer(ask);                      // the run waits here for a person
      if (!answer.Answered || !answer.As<PlotCondition>().Ready) {
        this.Item.Note = answer.Answered ? "The plot is not ready." : "The warden never answered: " + answer.ClosedWhy;
        goto Withheld;
      }
      goto Offering;
    }
  }
  state Offering {
    enter {
      var ask = Workflow.Once("offer-the-plot", () => OfferThePlot(this.Item));
      var answer = await Requests.Answer(ask);                      // …and here for somebody outside
      if (answer.Approved) { goto Let; }
      this.Item.Note = answer.Rejected ? "The offer was declined: " + answer.Comment : "The offer ran out: " + answer.ClosedWhy;
      goto Withheld;
    }
  }
  terminal success Let { }
  terminal cancel  Withheld { }
}

/// A form for the warden, made from `PlotCondition`.
public HumanRequest AskTheWarden(Handover h) {
  var ask = NewRequest.For<PlotCondition>();
  ask.To = h.Warden;
  ask.Title = "Is plot " + h.Plot + " ready to let?";
  ask.AskedByName = "Plot handover";
  return PutRequest(ask);
}

/// An offer to somebody with no account: answered through a link, or not at all within a week.
public HumanRequest OfferThePlot(Handover h) {
  return PutRequest(new NewRequest {
    Kind = RequestKind.Approval, OutsideName = h.NextTenant, OutsideEmail = h.NextTenantEmail,
    Title = "Will you take plot " + h.Plot + "?",
    About = Subject.Of("Handover", h.Id, "Plot " + h.Plot, version: "2031 season"),
    ExpireAfter = TimeSpan.FromDays(7), AskedByName = "Plot handover",
  });
}

/// The handover of a plot, for the people who follow it.
public class Handovers : Process<Handover> {
  public override string Name => "Plot handover";
  public override string Workflow => "PlotHandover";
  public override WorkflowGraph Graph => PlotHandover.Graph;
  public override string TitleOf(Handover h) => "Plot " + h.Plot;
  public override List<WorkflowAuditEntry> HistoryOf(Handover h) => PlotHandover.For(h).Audit;
}
app.Processes = new ProcessesSetup { Processes = [new Handovers()] };

public Handover HandOver(string plot, Person warden, string nextTenant, string email) {
  var h = new Handover { Plot = plot, Warden = warden, NextTenant = nextTenant, NextTenantEmail = email };
  UnitOfWork.Commit();
  Workflow.Run(h);
  return h;
}

/// A question put from a page: no process asked it.
public HumanRequest AskFromAPage(Person warden) {
  return PutRequest(new NewRequest { To = warden, Title = "Is the water on yet?" });
}

[TestFixture]
void Society() {
  var edith = new Person { Email = "edith@fennel-lane.example", DisplayName = "Edith Marlow" };
  var rufus = new Person { Email = "rufus@fennel-lane.example", DisplayName = "Rufus Okello" };
  new RoleGrant { Member = edith, Role = SocietyRole.Secretary };
  new RoleGrant { Member = rufus, Role = SocietyRole.Warden };
  new OrganizationTier { Name = "Standard", Rank = 1, IsDefault = true };
  var queue = new Queue { Name = "Society", Position = 1 };
  new QueueMember { Queue = queue, Member = edith };
  new QueueMember { Queue = queue, Member = rufus };
}
principal Edith => Person.Single(p => p.Email == "edith@fennel-lane.example");
principal Rufus => Person.Single(p => p.Email == "rufus@fennel-lane.example");

/// What the run's page says, state by state.
List<string> Told(Guid handover) {
  var view = ProcessRunOf(ProcessRunAbout("PlotHandover", handover)!.Id)!;
  List<string> lines = [];
  foreach (var stay in view.Stays) { foreach (var m in stay.Moments) { lines.Add(stay.Label + ": " + m.Said); } }
  return lines;
}

[Test(Society)]
void the_run_tells_whom_it_asked_and_who_answered() {
  TestClock.Set(new DateTime(2031, 4, 7, 9, 0, 0, DateTimeKind.Utc));
  Guid id = Guid.Empty;
  runas (Edith) {
    var h = HandOver("14", Rufus, "Tove Aldersey", "tove@example.org");
    Workflow.Settle(h);
    id = h.Id;
  }
  Background.Settle();
  // Two days on the warden fills in his form; the offer goes out, to somebody with no account.
  TestClock.Advance(TimeSpan.FromDays(2));
  runas (Rufus) { AnswerRequest(Requests.Mine().Single(), RequestAnswer.Of(new PlotCondition { Ready = true })); }
  Background.Settle();
  runas (Edith) { Workflow.Settle(Handover.Single()); }
  Background.Settle();
  var link = "";
  runas (Edith) { link = AnswerLinkFor(HumanRequest.Single(r => r.OutsideEmail != "")); }
  TestClock.Advance(TimeSpan.FromDays(3));
  AnswerByLink(AnswerLink.From(link), RequestAnswer.Approve(), "Tove Aldersey");
  Background.Settle();
  runas (Edith) {
    Workflow.Settle(Handover.Single());
    Assert.Equal(HandoverStage.Let, Handover.Single().Stage);
    // ⭐ Each state that waited for a person says for whom, and who answered — in that state, in order.
    Assert.Equal([
      "Inspecting: Asked Rufus Okello: “Is plot 14 ready to let?”",
      "Inspecting: Rufus Okello answered “Is plot 14 ready to let?” after 2 days.",
      "Offering: Asked Tove Aldersey, by a link: “Will you take plot 14?”",
      "Offering: Tove Aldersey approved “Will you take plot 14?” after 3 days.",
    ], Told(id));
    // A line about a request names it, for a page to open.
    var view = ProcessRunOf(ProcessRunAbout("PlotHandover", id)!.Id)!;
    Assert.Equal("HumanRequest", view.Stays[0].Moments[0].SubjectKind);
    Assert.NotNull(view.Stays[0].Moments[0].Subject);
  }
}

[Test(Society)]
void an_offer_nobody_takes_is_told_as_it_runs_out_and_a_question_from_a_page_is_no_step_of_a_run() {
  TestClock.Set(new DateTime(2031, 4, 7, 9, 0, 0, DateTimeKind.Utc));
  Guid id = Guid.Empty;
  runas (Edith) {
    var h = HandOver("9", Rufus, "Linus Varga", "linus@example.org");
    Workflow.Settle(h);
    id = h.Id;
    // A question nobody's process asked.
    AskFromAPage(Rufus);
  }
  Background.Settle();
  runas (Rufus) { AnswerRequest(Requests.Mine().Single(r => r.Title.StartsWith("Is plot")), RequestAnswer.Of(new PlotCondition { Ready = true })); }
  Background.Settle();
  runas (Edith) { Workflow.Settle(Handover.Single()); }
  // A week and a day: nobody took the offer.
  TestClock.Advance(TimeSpan.FromDays(8));
  Background.Settle();
  runas (Edith) {
    Workflow.Settle(Handover.Single());
    Background.Settle();
    Assert.Equal(HandoverStage.Withheld, Handover.Single().Stage);
    Assert.Equal("Offering: Nobody answered “Will you take plot 9?” in the time it could wait.", Told(id).Last());
    // The question from the page is on nobody's run.
    Assert.False(Told(id).Any(l => l.Contains("water")));
    Assert.Equal(1, ProcessRun.Count());
  }
}
```

## Notes          {#notes}
- The time in "after 2 days" is from when the request was put to when it was answered, on the app's clock.
- A reminder sent to the person asked is not a line of the run: it changes nothing about where the process stands.
- An [assignment's request](https://osysharp.com/reference/agent/assignment-requests/) put from a run's body is both: a piece of the assignment's
  work, and a line of the run.

## See also       {#see-also}
- [Processes — every run of a business process, listed and read from start to now (Processes kit)](https://osysharp.com/reference/workflow/processes-kit/) — the page of a run
- [Requests to people — a question, an approval, a form, and the answer coming back (HumanRequests kit)](https://osysharp.com/reference/agent/human-requests/) — questions, approvals, forms; who is asked; time; late answers
- [Agents in business processes — an agent's work as a step of a workflow, told on the run (Agents.Processes kit)](https://osysharp.com/reference/agent/processes/) — an agent's work as a step of a process, told on the run the same way
- [Workflow.Current (which run is this code in?)](https://osysharp.com/reference/workflow/run-facts/) — how code knows which run it is in
