# One piece of work, followed end to end

> One piece of an agent's work followed through every kit it touches: it is given, runs as the agent's account for a person, calls a tool, proposes something, lands in an inbox, is approved, goes on, hands a piece to a specialist, has its cost rolled up, leaves a receipt and a record, is read in the inspector, is corrected, and the correction becomes an instruction. Thirteen hops, each with the few lines that make it and the page that explains it. The listings on this page are one app, compiled and run.

<!-- id: agent-guide-work-end-to-end · area: agent · stability: preview · html: https://osysharp.com/reference/agent/guide-work-end-to-end/ -->

> **You are here:** [Building with agents](https://osysharp.com/reference/agent/guide/) › One piece of work, end to end

## Summary        {#summary}
Otto manages a small translation bureau. On Monday he asks the bureau's quoting agent for a quote on a commission.
On Wednesday he approves the fee it proposed. By then the agent has asked a second agent to check the terms, the
whole thing has cost a fraction of a cent, and every step of it can be read by somebody who was not there. Later
Otto corrects the quote, and from then on the agent is told to do it differently.

That is thirteen hops through six kits. This page takes them one at a time.

![The thirteen hops of one piece of work, and the kit each one belongs to](https://osysharp.com/media/ref/8d0171-work-end-to-end.svg)

*One piece of work from the asking to the instruction. Each box is a hop on this page; the colour says which kit it
belongs to.*

## Description    {#description}

### What is the app around it?   {#stage}
The bureau's app: its people, its commission, and the kits it uses. Nothing here is about the hops yet. Each
listing on this page is one file of the same app, which is why each opens with its own `using` lines.

```osy title="the stage: the bureau's kits, people, policies and its one entity" run app=agent-guide-work-end-to-end
app Bureau {
  use Osysharp.Agents;
  use Osysharp.Agents.Testing;
  use Osysharp.Agents.HumanRequests;
  use Osysharp.Agents.Inspector;
  use Osysharp.HumanRequests;
  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.Ui;
  model "model/**/*.osy";
}

using Osysharp.Identity;
using Osysharp.Organizations.ServicePlans;
using Osysharp.WorkQueues;
using Osysharp.HumanRequests;
using Osysharp.Agents;
using Osysharp.Agents.Testing;
using Osysharp.Agents.HumanRequests;
using Osysharp.Agents.Inspector;
using Osysharp.Llm;
using Osysharp.Workflow;
using Osysharp.ServiceHours;

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 Quoter(), new Terminologist()], TaskBudget = 0.05m };
app.Inbox = new InboxSetup { DefaultQueue = "Bureau" };

[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 IsOffice     => IsTranslator || IsManager;

policy UsesAgents          => IsOffice;
policy SupervisesAgents    => IsManager;
policy AuditsAgents        => IsManager;
policy TeachesAgents       => IsManager;
policy ReadsModelUsage     => IsManager;
policy ManagesModelSpend   => IsManager;
policy WorksInbox       => IsOffice || IsAssistant;
policy LeadsInbox       => IsManager;
policy AdministersInbox => IsManager;
policy ManagesOrganizations  => IsManager;
policy CollaboratesOnRecords => IsOffice;
policy AsksPeople       => IsOffice;
policy OverseesRequests => IsManager;

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

/// One text a customer has sent in to be translated.
entity Commission {
  [Required, Unique, MaxLength(40)] string Reference;
  [Required, MaxLength(200)] string Title;
  [MaxLength(40)] string Into = "";
  int Words = 0;
  decimal Fee;
  security {
    allow read, update when IsOffice || IsAssistant;
    allow create when IsOffice;
    deny update Fee when !IsManager && !IsAssistant;
  }
}

public class Quote { public decimal Fee; public int Terms; public string Summary = ""; }
public class TermReport { public int Terms; public string Finding = ""; }
```

### 1. It is given   {#given}
Work starts with one call. It names the agent, the job, what the job is about, and a title a person will
recognise later.

```osy title="hop 1 — giving the work: an agent, a skill, an input, and what it is about" run app=agent-guide-work-end-to-end
using Osysharp.Agents;

public AgentOutcome<Quote> QuoteFor(Commission commission) {
  return await Agents.Of<Quoter>().Do(new QuoteCommission(), commission, about: "Quote for " + commission.Reference);
}
```

The call answers an outcome: done with a typed result, waiting, failed or stopped. It never throws for work that
started. More: [[agent-ask#do]].

### 2. It runs as its own account, for a person   {#runs-as}
The agent is a class. The attribute gives it an account with one role. Because Otto is signed in when he asks, the
work is **for Otto**: it can read and change only what both the agent's account and Otto may.

```osy title="hops 2, 4 and 8 — the agent: its account, what it may do alone, and what it hands on" run app=agent-guide-work-end-to-end
using Osysharp.Agents;
using Osysharp.Identity;

[Actor(Roles = [BureauRole.Assistant])]                                     // hop 2: the account it acts as
public class Quoter : Agent {
  public static Person Principal => new Person { Email = "quoter@bureau.example", DisplayName = "Quoter" };
  public override string Purpose => "You quote the commissions of a small translation bureau.";
  public override List<SkillBase> Skills => [new QuoteCommission()];
  // Hop 4: nothing is listed as "alone", so every tool that changes something is proposed.
  // Hop 8: what it may hand on, and to whom.
  public override Delegation Delegates => new Delegation { To = [Specialist.Of<Terminologist>(new CheckTerms())] };
}

[Actor(Roles = [BureauRole.Assistant])]
public class Terminologist : Agent {
  public static Person Principal => new Person { Email = "terminologist@bureau.example", DisplayName = "Terminologist" };
  public override string Purpose => "You check texts against the bureau's house terms.";
  public override List<SkillBase> Skills => [new CheckTerms()];
}
```

More: [[agent-agents#rights]], [[Actor] — a principal that code may run as](https://osysharp.com/reference/security/declared-actor/).

### 3. It calls a tool   {#tool}
A tool is one of the app's own functions with `[Action]` on it. `Query` says it only reads, so the agent runs it
alone. The compiler works out what the other one does: it changes data.

```osy title="hop 3 — tools are the app's own functions; the compiler knows which only read" run app=agent-guide-work-end-to-end
[Action("Look up the rate per word", Query)]
public decimal RatePerWord(string language) { return language == "Finnish" ? 0.13m : 0.10m; }

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

[Action("Set the fee")]
public string SetFee(Commission commission, decimal fee) { commission.Fee = fee; return $"{commission.Reference} is charged {fee}."; }
```

More: [[Action] — what an app offers to be done, by name](https://osysharp.com/reference/function/action/), [[agent-skills#tools]].

### 4. It proposes   {#proposes}
The model asks for `SetFee`. That tool changes data and the agent's class did not say it may do so alone, so the
call does not run. It is kept, exactly as asked for, as a **proposal**: the tool, its arguments, why, and what the
agent had looked at. The work stops where it stands.

The skill says how a person will read the proposal: the commission's reference instead of its id.

```osy title="hop 4 — the skills: typed in, typed out, their tools, and how a proposal reads to a person" run app=agent-guide-work-end-to-end
using Osysharp.Agents;

public class QuoteCommission : Skill<Commission, Quote> {
  public override string Description => "Quote one commission: look up the rate, set the fee, and have its terms checked.";
  public override List<ActionInfo> Tools => [Actions.Of(RatePerWord), Actions.Of(SetFee)];
  public override string Brief(Commission c) => $"Commission {c.Reference}: {c.Title}, {c.Words} words into {c.Into}. Its id is {c.Id}.";
  public override string Names(string action, string argument, string value) {
    if (argument != "commission") { return value; }
    var c = Commission.FirstOrDefault(x => x.Id == Guid.Parse(value));
    return c == null ? value : "Commission " + c!.Reference;
  }
}

public class CheckTerms : Skill<Commission, TermReport> {
  public override string Description => "Check one commission against the house terms for its language.";
  public override List<ActionInfo> Tools => [Actions.Of(HouseTerms)];
  public override string Brief(Commission c) => $"Commission {c.Reference}, into {c.Into}. Its id is {c.Id}.";
  public override string HandedSchema() => HandOn.ById("The commission to check.");        // hop 8: handed on by id
  public override Commission Handed(string inputJson) {
    var id = HandOn.IdIn(inputJson);
    return Commission.Single(c => c.Id == id);
  }
}
```

More: [[agent-autonomy#proposal]].

### 5. It lands in an inbox   {#inbox}
With the requests kit and the agents kit both in the app, the proposal is an approval request in the inbox of the
person the work is for. One line says which queue and how soon a decision is wanted.

```osy title="hop 5 — where proposals wait, and how soon a decision is wanted" run app=agent-guide-work-end-to-end
using Osysharp.Agents.HumanRequests;

app.AgentRequests = new AgentRequestsSetup { Queue = "Bureau", DecideWithin = TimeSpan.FromHours(8) };
```

More: [[agent-requests#inbox]], [Requests to people — a question, an approval, a form, and the answer coming back (HumanRequests kit)](https://osysharp.com/reference/agent/human-requests/).

### 6. It is approved by a person   {#approved}
Otto opens the item, reads what will happen and why, and approves. No code of the app's is involved: the inbox
carries his answer to the proposal. The tool runs **now, as the agent on behalf of Otto**. Had Otto not been allowed
to set a fee, his yes would not have set one.

More: [[agent-autonomy#approve]], [[agent-requests#sheet]].

### 7. It goes on   {#continues}
The work goes on from the point it stopped at, two days later, with the tool's answer in hand. Nothing ran and no
model was asked in between. More: [[agent-people#waiting]].

### 8. It hands a piece to a specialist   {#hands-on}
The quoter's class named a specialist (`Delegates`, in hop 2). Its model is offered that as a tool, and uses it.
The terminologist's piece is a piece of work of its own, with its own receipt, done for the same person. More:
[[agent-delegation#hand-on]].

### 9. Its cost is rolled up   {#cost}
Every model call is charged to the piece of work that made it and to every piece above it. The quote costs what
the quoter's calls and the terminologist's calls cost together, and the limit on the whole binds both. More:
[[agent-delegation#money]], [What an agent may spend — a hard limit on every piece of work (Agents kit)](https://osysharp.com/reference/agent/budgets/).

### 10. It leaves a receipt and a record   {#record}
One [[agent-guide-glossary#receipt|receipt]] for each piece of work, and one
[[agent-guide-glossary#record|record]] of every step for the whole tree: each model call, each tool, the wait, who
decided. The record is chained, so a line changed afterwards is found. More: [The receipt and the record — what every piece of an agent's work leaves (Agents kit)](https://osysharp.com/reference/agent/task-log/).

### 11. Somebody reads it in the inspector   {#inspector}
`InspectAgentWork(task)` answers everything about one piece of work in words, for whoever is looking, under the
rules of who may read what. `WorkInspector(task)` draws the same thing as a page. More: [The inspector — one piece of an agent's work, readable by a person who was not there (Agents.Inspector kit)](https://osysharp.com/reference/agent/inspector/).

### 12. It is corrected by a person   {#correction}
Otto says what was wrong, what right looks like, and why. The correction is kept with that piece of work. It
changes nothing about what the agent does. More: [[agent-teaching#correction]].

### 13. The correction becomes an instruction   {#instruction}
An instruction is drawn from the correction and **proposed**. The agent is sent it only after a person who teaches
makes it active. Each change is a numbered instruction set, and the agent can be put back to an earlier one. More:
[[agent-teaching#instruction]], [[agent-teaching#sets]].

### How does the whole path run?   {#run}
All thirteen hops in one test. The two agents' models are scripted; everything else is real.

```osy title="all thirteen hops in one test, numbered as above" run app=agent-guide-work-end-to-end
using Osysharp.Identity;
using Osysharp.Organizations.ServicePlans;
using Osysharp.WorkQueues;
using Osysharp.HumanRequests;
using Osysharp.Agents;
using Osysharp.Agents.Testing;
using Osysharp.Agents.HumanRequests;
using Osysharp.Agents.Inspector;
using Osysharp.Workflow;

[TestFixture]
void Office() {
  var ines = new Person { Email = "ines@bureau.example", DisplayName = "Ines Varga" };
  var otto = new Person { Email = "otto@bureau.example", DisplayName = "Otto Reyes" };
  new RoleGrant { Member = ines, Role = BureauRole.Translator };
  new RoleGrant { Member = otto, Role = BureauRole.Manager };
  new OrganizationTier { Name = "Standard", Rank = 1, IsDefault = true };
  var queue = new Queue { Name = "Bureau", Position = 1 };
  new QueueMember { Queue = queue, Member = ines };
  new QueueMember { Queue = queue, Member = otto };
  new Commission { Reference = "C-31", Title = "Supply agreement", Into = "Finnish", Words = 2400 };
  new AgentSeat { Agent = "Quoter", Sponsor = otto };
  new AgentSeat { Agent = "Terminologist", Sponsor = otto };
}
principal Otto => Person.Single(p => p.Email == "otto@bureau.example");

/// Let the work a person has answered go on: the request's own handlers, then the wait's run.
void GoOn(Guid task) {
  Background.Settle();
  Workflow.Settle(AgentWait.Where(w => w.Task.Id == task).OrderByDescending(w => w.Since).First());
  Background.Settle();
}

[Test(Office)]
void One_piece_of_work_from_the_asking_to_the_instruction() {
  TestClock.Set(new DateTime(2031, 4, 7, 9, 0, 0, DateTimeKind.Utc));
  Guid task = Guid.Empty;
  runas (Otto) {
    var commission = Commission.Single();
    var id = "{\"commission\":\"" + commission.Id.ToString() + "\"";
    var cast = AgentCast.Of()
      .Plays<Quoter>(AgentScript.Of(
        Says.Tool(Actions.Of(RatePerWord), "{\"language\":\"Finnish\"}"),
        Says.Tool("2,400 words at 0.13 a word.", Actions.Of(SetFee), id + ",\"fee\":312}"),
        Says.HandTo<Terminologist>(new CheckTerms(), Says.Row(commission.Id)),
        Says.HandBack(new Quote { Fee = 312, Terms = 2, Summary = "312 euros; two house terms apply." })))
      .Plays<Terminologist>(AgentScript.Of(
        Says.Tool(Actions.Of(HouseTerms), "{\"language\":\"Finnish\"}"),
        Says.HandBack(new TermReport { Terms = 2, Finding = "Both apply." })));
    ScriptedAnswer.Stub((model, request) => cast.Answer(model, request));

    // 1. It is given.
    var outcome = QuoteFor(commission);
    task = outcome.Task;
    Background.Settle();

    // 2. It runs for the person who asked.
    Assert.Equal(Otto.Id, AgentTask.Single(t => t.Id == task).Requester!.Id);
    // 3. It called the tool that only reads, alone.
    Assert.Equal(["RatePerWord"], Trajectory.Of(task));
    // 4. It proposed the one that changes data, and waits. Nothing is charged yet.
    Assert.True(outcome.Waiting);
    Assert.Equal(0, Commission.Single().Fee);
    Assert.Equal("2,400 words at 0.13 a word.", AgentProposal.Single().Why);
    // 5. The proposal is an approval in his inbox.
    var approval = Requests.Mine().Single();
    Assert.Equal(RequestKind.Approval, approval.Asks);
    Assert.Equal("Quoter proposes: Set the fee", approval.Title);
    Assert.Equal("Commission C-31", ProposalCardOf(AgentProposal.Single().Id)!.Lines[0].Value);
  }

  // Two days. Nothing runs, and no model is asked.
  TestClock.Advance(TimeSpan.FromDays(2));
  runas (Otto) {
    var callsBefore = AgentTask.ToList().Sum(t => t.ModelCalls);
    Background.Settle();
    Assert.Equal(callsBefore, AgentTask.ToList().Sum(t => t.ModelCalls));

    // 6. He approves in the inbox: the tool runs at once, as the agent on behalf of him.
    AnswerRequest(Requests.Mine().Single(), RequestAnswer.Approve());
    Assert.Equal(312, Commission.Single().Fee);
    // 7. The work goes on from where it stood…
    GoOn(task);
    // 8. …and hands a piece to the terminologist: a piece of work of its own, under the quoter's.
    Assert.Equal(["Quoter · Quote commission: Done", "  Terminologist · Check terms: Done"], Trajectory.Tree(task));
    var piece = WorkTree.HandedOnBy(AgentTask.Single(t => t.Id == task)).Single();
    Assert.Equal(Otto.Id, piece.Requester!.Id);
    // 9. The whole costs what its pieces cost.
    Assert.Equal(AgentTask.ToList().Sum(t => t.SpentMicros), Trajectory.TreeCost(task));
    // 10. The receipt, and the record of every step — which still reads as it was written.
    var receipt = AgentTask.Single(t => t.Id == task);
    Assert.Equal(AgentTaskStatus.Done, receipt.Status);
    Assert.Contains("312 euros; two house terms apply.", receipt.Result!);
    Assert.Contains("Otto Reyes approved SetFee, and it ran as them.", Trajectory.Said(task));
    Assert.True(VerifyTrace(receipt).Intact);

    // 11. The inspector tells it to somebody who was not there.
    var seen = InspectAgentWork(task)!;
    Assert.Equal("Quote for C-31", seen.About);
    Assert.Equal("Done for Otto Reyes: never past what they may do themselves.", seen.For);
    Assert.Equal("It ran as Quoter, Quoter's own account.", seen.RanAs);
    Assert.StartsWith("Approved by Otto Reyes", seen.Proposals.Single().Decided);

    // 12. He corrects it. Nothing changes yet.
    var correction = CorrectAgentWork(receipt, new Correction {
      Wrong = "It quoted the plain rate for a legal text.", Right = "A legal text is quoted at 0.16 a word.",
      Because = "A second translator checks every legal text.",
    });
    // 13. An instruction is drawn from it and PROPOSED; a person who teaches makes it active.
    var instruction = ProposeAgentInstruction(new InstructionDraft {
      Agent = "Quoter", Guidance = "Quote a legal text at 0.16 a word.", Why = "A second translator checks it.", From = [correction.Id],
    });
    Assert.Equal(InstructionState.Proposed, instruction.State);
    Assert.Equal(0, InstructionSetOf("Quoter"));
    var set = ActivateAgentInstruction(instruction, "Agreed.");
    Assert.Equal(1, set.Number);
    Assert.Equal(1, InstructionSetOf("Quoter"));
  }
}
```

### Which kit does each hop belong to?   {#kits}

| Hop | Kit | You wrote |
|---|---|---|
| 1 given · 2 runs as · 3 tool · 4 proposes | [[agent-kit-agents\|Agents]] | one call, two classes, three functions |
| 5 inbox · 6 approved | [[agent-kit-human-requests\|HumanRequests]], [[agent-kit-work-queues\|WorkQueues]], and the bridge [[agent-kit-agents-human-requests\|Agents.HumanRequests]] | one settings line |
| 7 goes on · 8 hands on | [[agent-kit-agents\|Agents]] | one `Delegates` |
| 9 cost | [[agent-kit-llm\|Llm]] | nothing |
| 10 receipt and record | [[agent-kit-agents\|Agents]] | nothing |
| 11 inspector | [[agent-kit-agents-inspector\|Agents.Inspector]] | one line on a page |
| 12 correction · 13 instruction | [[agent-kit-agents\|Agents]]; the screens are [[agent-kit-agents-teaching\|Agents.Teaching]] | nothing, or one line on a page |

## What to read next   {#read-next}
- [The map of the kits — which sits on which, and what each one is for](https://osysharp.com/reference/agent/guide-kits/) — the map of the kits these hops belong to
- [Choosing — I want X, so use Y, not Z, because](https://osysharp.com/reference/agent/guide-choosing/) — the choices behind this path, as tables
- [What a person sees — the screens of an app with agents in it](https://osysharp.com/reference/agent/overview-what-people-see/) — hops 5, 6, 11 and 12 as a person sees them

## Go deeper   {#see-also}
- [Giving an agent work — Do, Ask, Stream, For and Decide (Agents kit)](https://osysharp.com/reference/agent/ask/) — giving an agent work, and what comes back
- [What an agent does alone, what it proposes, what it never does — the gate and proposals (Agents kit)](https://osysharp.com/reference/agent/autonomy/) — proposals, and what a yes covers
- [An agent's proposals and questions in a person's inbox, and an inbox item in an agent's hands (Agents.HumanRequests kit)](https://osysharp.com/reference/agent/requests/) — proposals in the inbox
- [An agent hands work on to other agents — at once, typed, and never with more than it holds (Agents kit)](https://osysharp.com/reference/agent/delegation/) — handing work on
- [The receipt and the record — what every piece of an agent's work leaves (Agents kit)](https://osysharp.com/reference/agent/task-log/) — the receipt and the record
- [The inspector — one piece of an agent's work, readable by a person who was not there (Agents.Inspector kit)](https://osysharp.com/reference/agent/inspector/) — one piece of work on one page
- [Correcting an agent, and what it is told from then on — corrections, instructions, instruction sets and rollback (Agents kit)](https://osysharp.com/reference/agent/teaching/) — corrections and instructions
