Osy#betaRun it on Osyrin: osyrin.com

Reference / Agent

Shape 3 — agents inside a business process

A business process is a workflow, and an agent's work is one of its steps: an ordinary awaited call that answers the skill's typed result. The process decides what happens when the step is done, fails, is stopped or runs out of money, and a person can take over. It adds `Osysharp.Processes`; the bridge that tells each agent step on the run arrives by itself.

preview1 example compiled by CIagentsguide

You are here: Building with agents › Deciding what to build › Shape 3

Summary#

At the translation bureau every commission now goes through the same steps: it is prepared, then it is ready for a translator. Preparing is the agent's job. If the agent cannot finish — the model is down, the money ran out, somebody stopped it — the commission does not get stuck: the process sends it to the manager instead.

This is the third shape: agents inside a business process. The process is in charge. The agent does one step of it.

One run of a business process, told from start to now, with its agent steps

One run of a supplier-qualification process, from a tool maker's app: where it stands, each state it was in, and what each agent did there.

Description#

What does it add?#

You addIt gives you
a [[workflow-index|workflow]]the process itself: its states, and what moves it from one to the next
[[agent-kit-processes|Osysharp.Processes]]every run listed, drawn and told in order, for somebody who was not there
[[agent-kit-agents-processes|Osysharp.Agents.Processes]]nothing to write: it arrives by itself, tells each agent step on the run, and charges the step to the run
[[agent-kit-agents-inspector|Osysharp.Agents.Inspector]]one piece of an agent's work on one page, opened from the run

What do you decide now?#

  1. Who a step works for. In a process nobody is signed in. Said nothing, the agent works with its own roles alone. Agents.Of<X>().For(person) makes a step work for a person — the buyer who asked, say — and then it never does more than that person may. See [[agent-processes#for]].
  2. What the process does when a step does not finish. The call answers an outcome, not an exception: Done, or not. Your workflow branches on it like on anything else. See [[agent-processes#failed]].
  3. What each run may spend. SetProcessBudget("CommissionFlow", 2.00m) holds every run of that workflow to two dollars: its agent steps and its own model calls together. See [[agent-processes#cost]].
  4. Who follows processes. The policy FollowsProcesses says who may open the list of runs and a run. Each thing on a run's page is then shown under the rule of the record it is read from: the money to whoever reads model spend, an agent's work to whoever may read that work. See [[workflow-processes-kit#who]].
  5. Who an approval inside a step goes to. As in shape 2. When the step works for nobody, a proposal goes to the agent's [[agent-guide-glossary#sponsor|sponsor]]. The process waits, for days if it takes days.

What do you get without writing it?#

  • A step that waits on a person makes the whole process wait, and it goes on from the same point.
  • Stopping the process calls off the work it was waiting for. See [[agent-processes#stopped]].
  • Every agent step is a line on the run: given, waiting on a person, finished, failed, stopped.
  • The receipt of each piece of work says which run, which workflow and which state gave it.
  • What the run cost is what its agent steps cost. Nothing is counted twice.

What does the whole app look like?#

What is new since shape 1 is marked NEW. Approvals work here exactly as in shape 2; they are left out so the page stays short.

app Bureau {
  use Osysharp.Agents;
  use Osysharp.Agents.Testing;
  use Osysharp.Agents.Inspector;         // NEW
  use Osysharp.Agents.Processes;         // NEW: the bridge
  use Osysharp.Processes;                // NEW
  use Osysharp.Llm;
  use Osysharp.Identity;
  use Osysharp.Observability;
  use Osysharp.Workflow;
  use Osysharp.Diagrams;
  use Osysharp.Ui;
  model "model/**/*.osy";
}

using Osysharp.Identity;
using Osysharp.Llm;
using Osysharp.Workflow;
using Osysharp.Agents;
using Osysharp.Agents.Testing;
using Osysharp.Agents.Inspector;
using Osysharp.Processes;
using Osysharp.Agents.Processes;

app.Llm = new LlmSetup {
  Models = [ new LlmModel { Name = "Everyday", Client = new ScriptedModel { Name = "Everyday" }, Model = "scripted-everyday",
                            Price = new LlmPrice { Input = 1, Output = 5 }, MaxOutputTokens = 1000 } ],
  Default = "Everyday",
};
app.Agents = new AgentsSetup { Agents = [new Preparer()] };
app.Processes = new ProcessesSetup { Processes = [new CommissionHandling()] };      // NEW

[Role] enum BureauRole { Authenticator, Translator, Manager, Assistant }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsTranslator => RoleGrant.Any(g => g.Member == user && g.Role == BureauRole.Translator);
policy IsManager    => RoleGrant.Any(g => g.Member == user && g.Role == BureauRole.Manager);
policy IsAssistant  => RoleGrant.Any(g => g.Member == user && g.Role == BureauRole.Assistant);

policy UsesAgents        => IsTranslator || IsManager;
policy SupervisesAgents  => IsManager;
policy FollowsProcesses  => IsTranslator || IsManager;      // NEW — decision 4
policy ReadsModelUsage   => IsManager;
policy ManagesModelSpend => IsManager;

entity Person : Principal {
  security { allow read when IsAuthenticated || IsAuthenticator; allow create when IsAuthenticator; }
}

// NEW: where a commission stands.
enum CommissionStage { Preparing, Ready, [Label("With the manager")] WithTheManager }

entity Commission {
  [Required, MaxLength(200)] string Title;
  [MaxLength(40)] string Into = "";
  [MaxLength(400)] string Note = "";
  CommissionStage Stage = CommissionStage.Preparing;
  security { allow read, create, update when IsTranslator || IsManager || IsAssistant; }
}

public class Preparation {
  public int Terms;
  public string Summary = "";
}

[Action("Look up the house terms for a language", Query)]
public List<string> HouseTerms(string language) {
  return language == "Finnish" ? ["sopimus: agreement", "toimitus: delivery"] : [];
}

public class PrepareCommission : Skill<Commission, Preparation> {
  public override string Description => "Prepare one commission for its translator.";
  public override List<ActionInfo> Tools => [Actions.Of(HouseTerms)];
  public override string Brief(Commission c) => $"{c.Title}, into {c.Into}. Its id is {c.Id}.";
}

[Actor(Roles = [BureauRole.Assistant])]
public class Preparer : Agent {
  public static Person Principal => new Person { Email = "[email protected]", DisplayName = "Preparer" };
  public override string Purpose => "You prepare commissions for the translators of a small bureau.";
  public override List<SkillBase> Skills => [new PrepareCommission()];
}

/// NEW — the process. Its agent step is an ordinary call: typed in, typed out, and the outcome decides.
workflow CommissionFlow {
  Tracks = Commission.Stage; Autostart = false; Initial = Preparing;
  state Preparing {
    enter {
      var prepared = await Agents.Of<Preparer>().Do(new PrepareCommission(), this.Item, about: "Preparing " + this.Item.Title);
      if (!prepared.Done) {
        // Decision 2: the step did not finish. The process does not get stuck: a person takes it.
        Processes.Problem("The preparer could not finish, so the manager prepares this one.");
        goto WithTheManager;
      }
      this.Item.Note = prepared.Result.Summary;
      goto Ready;
    }
  }
  terminal success Ready { }
  terminal cancel  WithTheManager { Message = "handed to the manager"; }
}

/// NEW — says which workflow is a business process, so its runs are listed and told.
public class CommissionHandling : Process<Commission> {
  public override string Name => "Commission handling";
  public override string Workflow => "CommissionFlow";
  public override WorkflowGraph Graph => CommissionFlow.Graph;
  public override string TitleOf(Commission c) => c.Title;
  public override List<WorkflowAuditEntry> HistoryOf(Commission c) => CommissionFlow.For(c).Audit;
}

Commission TakeIn(string title) {
  var commission = new Commission { Title = title, Into = "Finnish" };
  UnitOfWork.Commit();
  Workflow.Run(commission);
  return commission;
}

[TestFixture]
void Office() {
  var otto = new Person { Email = "[email protected]", DisplayName = "Otto Reyes" };
  new RoleGrant { Member = otto, Role = BureauRole.Manager };
  new AgentSeat { Agent = "Preparer", Sponsor = otto };
}
principal Otto => Person.Single(p => p.Email == "[email protected]");

[Test(Office)]
void The_agents_step_moves_the_process_on_and_is_told_on_its_run() {
  // The preparer, scripted: it looks the house terms up and hands back what it found.
  ScriptedAnswer.Stub((model, request) => {
    if (request.Messages.Count(m => m.Role == "assistant") == 0) { return Says.Tool(Actions.Of(HouseTerms), "{\"language\":\"Finnish\"}", 300, 20); }
    return Says.HandBack(new Preparation { Terms = 2, Summary = "Two house terms apply." }, 400, 40);
  });
  runas (Otto) {
    var commission = TakeIn("Supply agreement");
    Workflow.Settle(commission);
    Assert.Equal(CommissionStage.Ready, Commission.Single().Stage);          // the typed result decided
    Assert.Equal("Two house terms apply.", Commission.Single().Note);

    // The receipt says which run of which workflow gave it the work, and in which state.
    var step = AgentTask.Single();
    Assert.Equal("CommissionFlow", step.RunWorkflow);
    Assert.Equal("Preparing", step.RunState);

    // The run tells the step, in the state that gave it — and costs what the step cost.
    var run = ProcessRunOf(ProcessRunAbout("CommissionFlow", commission.Id)!.Id)!;
    Assert.Equal("Supply agreement", run.Card.Title);
    Assert.Equal(ProcessStatus.Finished, run.Card.Status);
    Assert.Equal(["Preparer was given work: prepare commission.", "Preparer finished: prepare commission."], run.Stays[0].Moments.Select(m => m.Said).ToList());
    Assert.Equal(step.SpentMicros / 1000000m, run.Cost.Spent);
  }
}

[Test(Office)]
void A_limit_for_each_run_stops_the_step_and_a_person_takes_over() {
  // The preparer, scripted: it looks the house terms up and hands back what it found.
  ScriptedAnswer.Stub((model, request) => {
    if (request.Messages.Count(m => m.Role == "assistant") == 0) { return Says.Tool(Actions.Of(HouseTerms), "{\"language\":\"Finnish\"}", 300, 20); }
    return Says.HandBack(new Preparation { Terms = 2, Summary = "Two house terms apply." }, 400, 40);
  });
  runas (Otto) {
    // Decision 3: each run may spend a tenth of a cent. The preparer's first call would hold more: nothing is sent.
    SetProcessBudget("CommissionFlow", 0.001m);
    UnitOfWork.Commit();
    var commission = TakeIn("Supply agreement");
    Workflow.Settle(commission);

    Assert.Equal(CommissionStage.WithTheManager, Commission.Single().Stage);
    Assert.Equal(AgentTaskStatus.Stopped, AgentTask.Single().Status);
    var run = ProcessRunOf(ProcessRunAbout("CommissionFlow", commission.Id)!.Id)!;
    Assert.Equal("The preparer could not finish, so the manager prepares this one.", run.Stays[0].Moments.Last().Said);
  }
}

What would make this the wrong shape?#

  • There are no fixed steps: somebody owns an outcome and tries things until it holds. That is an [[agent-guide-glossary#assignment|assignment]], not a process. See [[agent-guide-choosing#assignment-or-process]].
  • There is only one step. That is shape 1 or shape 2.

Go deeper#

Related

Building with agents — the developer's guide

Where a developer starts with agents in an Osy# app. Decide what you are building, read the smallest working app for…

Deciding what to build — four shapes of app with agents in it

Start from what the app is for, not from the kits. There are four shapes: an assistant for one person, work done for…

Shape 2 — work done for people, with approvals

The agent may do some things alone and must propose the rest. A proposal lands in a person's inbox as an approval…

Shape 4 — a team of agents with a coordinator

One agent is in charge of a job and hands typed pieces of it to specialists, several at once. Each piece is a piece of…

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…

The inspector — one piece of an agent's work, readable by a person who was not there (Agents.Inspector kit)

Osysharp.Agents.Inspector draws one page for one piece of an agent's work: what it was and how it ended; who gave it…

Workflows (the run that outlives the request)

A `workflow` is a run that survives the process it started in — it parks, waits days for a person or a timer, and…