# Who is in charge of an app's agents — every policy in one table, pausing, sponsoring, and trying work again

> One table of who may do what with an app's agents. An app answers UsesAgents and SupervisesAgents; supervising brings four things with it — naming an agent's sponsor, pausing it, reading the receipt of every piece of work and deciding a proposal that was put to somebody else — and each of those four is a policy of its own, an app's to hand to somebody else in one line. Widening what an agent does alone, teaching it, giving it work for somebody else, reading what its work said and erasing it are never implied. A receipt read through ReadsAllAgentWork alone shows what work was done and what it cost, and not a word of it. Pausing, resuming and sponsoring are each written down with who did it and why. RetryAgentWork starts a piece of work again from what it was given, for whoever may read that.

<!-- id: agent-roles · area: agent · stability: preview · html: https://osysharp.com/reference/agent/roles/ -->

## Summary        {#summary}
```osy syntax
policy UsesAgents       => IsStaff;     // gives agents work, reads the work they asked for
policy SupervisesAgents => IsLead;      // …and with it: sponsors, pauses, reads every receipt, decides others' proposals

policy PausesAgents      => SupervisesAgents || IsOnCall;                      // hand one of the four to somebody else
policy ReadsAllAgentWork => SupervisesAgents || AuditsAgents || IsFinance;     // finance reads the costs, not the words

PauseAgent("Sorter", "It flagged forty crates in ten minutes.");   ResumeAgent("Sorter", "The scale is fixed.");
SponsorAgent("Sorter", person);                                    SeatChangesOf("Sorter");   // who, what, when, why
var again = RetryAgentWork(task);                                  // the same brief, a receipt of its own
```

## Signature      {#signature}
```osy syntax
virtual policy SponsorsAgents        => SupervisesAgents;                   // name the person who answers for an agent
virtual policy PausesAgents          => SupervisesAgents;                   // pause it, let it work again
virtual policy ReadsAllAgentWork     => SupervisesAgents || AuditsAgents;   // every receipt — not what the work said
virtual policy ReadsAgentWorkOf(string chargedTo) => false;                // a receipt by what it is charged to
virtual policy DecidesAgentProposals => SupervisesAgents;                   // decide a proposal put to somebody else

void PauseAgent(string agent, string why = "")       // [Authorize(PausesAgents)]
void ResumeAgent(string agent, string why = "")      // [Authorize(PausesAgents)]
void SponsorAgent(string agent, P sponsor)           // [Authorize(SponsorsAgents)]
List<AgentSeatChange> SeatChangesOf(string agent)    // newest first
entity AgentSeatChange { string Agent; AgentSeatChangeKind What; P? By; DateTime At; string Why; P? Sponsor; }
enum AgentSeatChangeKind { Sponsored, Paused, Resumed }
[Event] AgentSeatChanged { AgentSeatChange Change }

Guid RetryAgentWork(AgentTask task)                  // [Authorize(UsesAgents)] → the new attempt's receipt
```

## Description    {#description}

### Who may do what?   {#table}
Every policy about an app's agents, with the kit that declares it. A policy admits **nobody** until the app answers
it, unless the last column says otherwise.

| policy | kit | what it admits somebody to | until the app says otherwise |
|---|---|---|---|
| `UsesAgents` | Agents | give agents work; read the work they asked for | nobody |
| `SupervisesAgents` | Agents | call off any work, answer any question an agent asked, take back any standing rule, correct any work, read every piece of work | nobody |
| `SponsorsAgents` | Agents | name the person who answers for an agent | whoever supervises |
| `PausesAgents` | Agents | pause an agent, and let it work again | whoever supervises |
| `ReadsAllAgentWork` | Agents | read the receipt of every piece of work: which agent, for whom, where it stands, what it cost — not what it said | whoever supervises or audits |
| `ReadsAgentWorkOf(chargedTo)` | Agents | read the receipt — its figures, not its words — of each piece of work whose `ChargedTo` the app's answer admits ([[#tenants]]) | nobody |
| `DecidesAgentProposals` | Agents | decide a proposal that was put to somebody else | whoever supervises |
| `WidensAgentAutonomy` | Agents | say "from now on, alone" for the work an agent does for them, and take it 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/)) | nobody |
| `TeachesAgents` | Agents | make an instruction active, retire one, put an agent back to an earlier set ([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/)); read every piece of work **with** what it said, and correct it | nobody |
| `DirectsAgentsForOthers` | Agents | give an agent work *for* somebody else, which then runs with what that person may do ([Giving an agent work — Do, Ask, Stream, For and Decide (Agents kit)](https://osysharp.com/reference/agent/ask/)) | nobody |
| `AuditsAgents` | Agents | read every piece of work **with** what it said | nobody |
| `ErasesAgentRecords` | Agents | erase what a piece of work said, and whose it was ([Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request](https://osysharp.com/reference/agent/privacy/)) | nobody |
| `AgentsMayConsult` | Agents | whether an agent may ask the model it consults ([Asking for help — an agent that consults a more capable model (Agents kit)](https://osysharp.com/reference/agent/help/)) | everybody |
| `ReadsModelUsage` | Llm | read the meter: every model call with its model, tokens and cost ([Model spend — budgets, the meter and the price book (Llm kit)](https://osysharp.com/reference/agent/llm-spend/)) | nobody |
| `ReadsModelUsageOf(scopes)` | Llm | read each model call whose `Scopes` the app's answer admits ([[#tenants]]) | nobody |
| `ManagesModelSpend` | Llm | set prices and budgets | nobody |
| `AsksPeople` | HumanRequests | put a request to a person — how a piece of work is handed to somebody | nobody |
| `OverseesRequests` | HumanRequests | read every request an agent put to a person, call one off, give it more time | nobody |
| `FollowsProcesses` | Processes | open the list of process runs, and a run ([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/)) | nobody |

### What does one line give a supervisor?   {#one-line}
`policy SupervisesAgents => IsLead;` is one line for five things: supervising itself, and the four policies that start
out as *whoever supervises*. An app that wants one of the four elsewhere says that one:

```osy syntax
policy PausesAgents          => SupervisesAgents || IsOnCall;                    // whoever is on call may pause at night
policy ReadsAllAgentWork     => SupervisesAgents || AuditsAgents || IsFinance;   // finance reads every receipt
policy DecidesAgentProposals => SupervisesAgents || IsDeputy;                    // the deputy decides for the lead
```

Everything else is answered by name or not at all: a supervisor does not teach, widen, give work for others, read
the content of a record or erase it until the app says so, a line each. The policies of the kits beside — money, requests,
processes — are those kits' own, and an app answers each where it uses that kit.

### What does somebody who reads every receipt see?   {#receipts}
The figures, and not the words. Read through `ReadsAllAgentWork` by somebody who neither supervises nor audits, a
receipt of somebody else's work gives the agent and the skill, who it was for, where it stands, when it started and
ended, what it spent and may spend, and its counts of calls — and reads as nothing for what it was about, what it
handed back, how it ended in words and what it waits for. The record of its steps, its proposals and its questions
are not read at all. So finance can be given what the agents cost without reading a word of the work.

### Who reads what a piece of work said?   {#words}
Whoever the work was for and whoever gave it; whoever `AuditsAgents` admits; and whoever `TeachesAgents` admits —
nobody can judge whether an agent did the right thing, or correct it, without the data it acted on. A teacher reads
the receipt with its words, every line of the record with its content, the proposals and the questions, for every
agent: `TeachesAgents` is one answer for the whole app, so a teacher teaches — and reads — all of them. A supervisor
reads every receipt and every sentence of a record, and not the content of its lines. What was erased
([Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request](https://osysharp.com/reference/agent/privacy/)) is gone from the row, for a teacher as for anybody.

### In an app of many organisations?   {#tenants}
Every policy above is ONE answer for the whole app, which is right for a team and wrong for an app of many
organisations, shops or clients: `ReadsAllAgentWork => IsAuditor` would show one organisation's auditor every other's
work. Two policies are asked a row at a time instead, of what that row is charged to — a piece of work's `ChargedTo`
(`Agents.Of<T>().On("report:" + report.Id)`) and a model call's `Scopes`, one scope a line:

```osy syntax
// an organisation's auditor reads the receipts and the model calls of that organisation's reports, and no others
policy ReadsAgentWorkOf(string chargedTo) => Report.Any(r => chargedTo.Contains("report:" + r.Id.ToString())
  && OrgRole.Any(g => g.Member == user && g.Role == Role.Auditor && g.Organization == r.Organization));
policy ReadsModelUsageOf(string scopes)   => Report.Any(r => scopes.Contains("report:" + r.Id.ToString())
  && OrgRole.Any(g => g.Member == user && g.Role == Role.Auditor && g.Organization == r.Organization));
```

A receipt read this way gives its figures and not its words, exactly as one read through `ReadsAllAgentWork`. Nobody
in any organisation needs to read the agent's own account, or the person who answers for it: an agent's account
belongs to no organisation, and the work runs — and is sponsored — whether or not whoever gives it may read either.

### Who decides a proposal?   {#deciders}
A proposal is put to the person the work is for — to the agent's sponsor when it is for nobody — and that person
decides it ([What an agent does alone, what it proposes, what it never does — the gate and proposals (Agents kit)](https://osysharp.com/reference/agent/autonomy/)). A skill may send what it proposes somewhere else: to a work queue, or to one
person it names ([[agent-autonomy#route]]); the person the work is for then reads the proposal and does not decide
it. `DecidesAgentProposals` says who *else* may, in both cases: whoever supervises, until the app names more. A yes
runs as whoever said it, and never past what they may do themselves. Whoever a proposal is put to by name reads the
work's receipt — its figures, not its words — and needs no other policy to decide.

### How is an agent paused, and what is written down?   {#pausing}
`PauseAgent(agent, why)` stops an agent taking **new** work: whoever gives it work next is refused with `AgentPaused`
and the reason. Work under way finishes; calling that off is `StopAgentTask`. `ResumeAgent(agent, why)` lets it work
again. `SponsorAgent(agent, person)` names who answers for it — an agent with no sponsor does not run.

Each of the three leaves an `AgentSeatChange` — who did it, what, when and why — in the same unit of work, and raises
`AgentSeatChanged`. The record is written only by doing the thing, and is never changed or deleted;
`SeatChangesOf(agent)` reads it, newest first. Pausing what is paused, or resuming what works, writes nothing.

### Can a piece of work be tried again?   {#again}
`RetryAgentWork(task)` starts it again from the beginning: the same agent, skill and brief, on a receipt of its own
that says which attempt it is (`Attempt`) and which it follows (`RetryOf`), for the person the first was for, charged
to what the first was charged to.

It starts from what the work was *given*, which is the work's content — so it is for whoever may read that: the
person the work was for, whoever gave it, and an auditor who gives agents work. Somebody who reads only that the work
happened is refused; and trying somebody else's work again needs `DirectsAgentsForOthers`, because it runs for that
person again.

| the work | answer |
|---|---|
| failed, was stopped or was called off | a new attempt |
| is still under way | refused: call it off first |
| finished | refused: give the agent the work again where it is given |
| was handed on by another piece of work | refused: try the work at the top again |
| was a step of a process | refused: the process was told how it ended, and has gone on |
| has been tried again already | refused: open the attempt that followed |

### What will a standing rule tell me about itself?   {#rule-use}
How often it has been used. `AgentStandingRule.Used` counts each time a tool ran alone because of the rule, and
`LastUsedAt` says when it last did — counted by the kit as the tool runs, never by hand.

## Examples       {#examples}
```osy title="on call she pauses it, the bookkeeper reads what it cost, and its work is tried again" run app=agent-roles
app SeedBank {
  use Osysharp.Agents;
  use Osysharp.Agents.Testing;
  use Osysharp.Llm;
  use Osysharp.Identity;
  use Osysharp.Observability;
  use Osysharp.Workflow;
  model "model/**/*.osy";
}

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

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 Sampler()] };

[Role] enum VaultRole { Authenticator, Keeper, Warden, OnCall, Bookkeeper, Sampling }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsKeeper     => RoleGrant.Any(g => g.Member == user && g.Role == VaultRole.Keeper);
policy IsWarden     => RoleGrant.Any(g => g.Member == user && g.Role == VaultRole.Warden);
policy IsOnCall     => RoleGrant.Any(g => g.Member == user && g.Role == VaultRole.OnCall);
policy IsBookkeeper => RoleGrant.Any(g => g.Member == user && g.Role == VaultRole.Bookkeeper);
policy IsSampling   => RoleGrant.Any(g => g.Member == user && g.Role == VaultRole.Sampling);

// The keepers give the sampler work; the warden is in charge of it — and that one line names its sponsor, pauses
// it, reads every receipt and decides the keepers' proposals too.
policy UsesAgents       => IsKeeper || IsWarden;
policy SupervisesAgents => IsWarden;
// Two of the four, handed on: whoever is on call may pause, and the bookkeeper reads every receipt.
policy PausesAgents      => SupervisesAgents || IsOnCall;
policy ReadsAllAgentWork => SupervisesAgents || AuditsAgents || IsBookkeeper;

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

/// A packet of seed in the vault.
entity Packet {
  [Required, Unique, MaxLength(40)] string Code;
  int Sprouted = 0;
  bool Viable = false;
  security { allow read, update when IsKeeper || IsWarden || IsSampling; allow create when IsKeeper; }
}

/// How many of a packet's twenty test seeds sprouted.
[Action("Count the sprouted seeds", Query)]
public int SproutedOf(Packet packet) { return packet.Sprouted; }

/// Mark a packet as still worth keeping.
[Action("Mark the packet viable")]
public string MarkViable(Packet packet) { packet.Viable = true; return packet.Code + " is viable."; }

public class Verdict { public bool Keep; public string Why = ""; }

public class TestPacket : Skill<Packet, Verdict> {
  public override string Description => "Say whether a packet of seed is still worth keeping.";
  public override List<ActionInfo> Tools => [Actions.Of(SproutedOf), Actions.Of(MarkViable)];
  public override string Brief(Packet p) => $"Packet {p.Code}. Its id is {p.Id}.";
}

/// Tests the seed. It marks a packet by itself.
[Actor(Roles = [VaultRole.Sampling])]
public class Sampler : Agent {
  public static Person Principal => new Person { Email = "sampler@seedbank.example", DisplayName = "Sampler" };
  public override string Purpose => "You test whether stored seed still sprouts.";
  public override List<SkillBase> Skills => [new TestPacket()];
  public override Permits May => Permits.ChangesAlone();
}

public AgentOutcome<Verdict> Test(Packet packet) { return await Agents.Of<Sampler>().Do(new TestPacket(), packet, about: "Packet " + packet.Code); }

[TestFixture]
void Vault() {
  var keeper = new Person { Email = "keeper@seedbank.example", DisplayName = "Tove Lindqvist" };
  var warden = new Person { Email = "warden@seedbank.example", DisplayName = "Amara Nwosu" };
  var night  = new Person { Email = "night@seedbank.example",  DisplayName = "Rafael Ortega" };
  var books  = new Person { Email = "books@seedbank.example",  DisplayName = "Hana Sato" };
  new RoleGrant { Member = keeper, Role = VaultRole.Keeper };
  new RoleGrant { Member = warden, Role = VaultRole.Warden };
  new RoleGrant { Member = night,  Role = VaultRole.OnCall };
  new RoleGrant { Member = books,  Role = VaultRole.Bookkeeper };
  new Packet { Code = "RYE-1987", Sprouted = 17 };
  new AgentSeat { Agent = "Sampler", Sponsor = warden };
}
principal Keeper => Person.Single(p => p.Email == "keeper@seedbank.example");
principal Warden => Person.Single(p => p.Email == "warden@seedbank.example");
principal Night  => Person.Single(p => p.Email == "night@seedbank.example");
principal Books  => Person.Single(p => p.Email == "books@seedbank.example");

/// What the scripted sampler does with a packet: counts, marks it, and hands back.
AgentScript TheSampler() {
  var packet = Packet.Single();
  var id = "{\"packet\":\"" + packet.Id.ToString() + "\"}";
  return AgentScript.Of(
    Says.Tool(Actions.Of(SproutedOf), id, 400, 40),
    Says.Tool(Actions.Of(MarkViable), id),
    Says.HandBack(new Verdict { Keep = true, Why = "Seventeen of twenty sprouted." }));
}

[Test(Vault)]
void whoever_is_on_call_pauses_it_and_the_warden_lets_it_work_again() {
  // A keeper gives the sampler work, and that is all.
  runas (Keeper) { Assert.Denied(() => PauseAgent("Sampler", "I do not trust it.")); }
  // Whoever is on call pauses it, with the reason — and may not name its sponsor.
  runas (Night) {
    PauseAgent("Sampler", "The growth cabinet's thermostat has failed.");
    UnitOfWork.Commit();
    Assert.Denied(() => SponsorAgent("Sampler", Night));
  }
  // Paused, it takes no new work, and whoever asks is told why.
  runas (Keeper) {
    var refused = Assert.Throws<AgentPaused>(() => Test(Packet.Single()));
    Assert.Equal("Sampler is paused: The growth cabinet's thermostat has failed.", refused.Message);
    Assert.Denied(() => ResumeAgent("Sampler"));
  }
  // The warden lets it work again. Both changes are on the record: who, what and why.
  runas (Warden) {
    ResumeAgent("Sampler", "The cabinet is repaired.");
    UnitOfWork.Commit();
    var changes = SeatChangesOf("Sampler");
    Assert.Equal(2, changes.Count);
    var paused = changes.Single(c => c.What == AgentSeatChangeKind.Paused);
    Assert.Equal(Night.Id, paused.By!.Id);
    Assert.Equal("The growth cabinet's thermostat has failed.", paused.Why);
    Assert.Equal(Warden.Id, changes.Single(c => c.What == AgentSeatChangeKind.Resumed).By!.Id);
  }
  runas (Keeper) {
    var sampler = TheSampler();
    ScriptedAnswer.Stub((model, request) => sampler.Answer(model, request));
    Assert.True(Test(Packet.Single()).Done);
  }
}

[Test(Vault)]
void the_bookkeeper_reads_what_the_work_cost_and_not_what_it_said() {
  runas (Keeper) {
    var sampler = TheSampler();
    ScriptedAnswer.Stub((model, request) => sampler.Answer(model, request));
    Assert.True(Test(Packet.Single()).Done);
    Assert.Equal("Packet RYE-1987", AgentTask.Single().About);       // her own: all of it
  }
  runas (Books) {
    var receipt = AgentTask.Single();
    Assert.Equal("Sampler", receipt.Agent);
    Assert.Equal(AgentTaskStatus.Done, receipt.Status);
    Assert.Equal(Keeper.Id, receipt.Requester!.Id);
    Assert.Equal(1000, receipt.SpentMicros);                          // 600 + 200 + 200 millionths of a dollar
    Assert.Null(receipt.About);                                       // …and none of its words
    Assert.Null(receipt.Result);
    Assert.Equal(0, TraceEvent.Count());                              // nor the record of its steps
  }
  // Whoever is on call may pause the sampler, and reads nobody's work.
  runas (Night) { Assert.Equal(0, AgentTask.Count()); }
  runas (Warden) { Assert.Equal("Packet RYE-1987", AgentTask.Single().About); }
}

[Test(Vault)]
void work_that_failed_is_tried_again_by_whoever_it_was_for() {
  Guid first = Guid.Empty;
  runas (Keeper) {
    // What the model hands back never fits what the skill promises, so the work fails.
    var broken = AgentScript.Of(Says.HandBackJson("{\"Keep\":\"perhaps\"}"), Says.HandBackJson("{\"Keep\":\"perhaps\"}"),
                                Says.HandBackJson("{\"Keep\":\"perhaps\"}"), Says.HandBackJson("{\"Keep\":\"perhaps\"}"));
    ScriptedAnswer.Stub((model, request) => broken.Answer(model, request));
    var outcome = Test(Packet.Single());
    Assert.Equal(AgentTaskStatus.Failed, outcome.Status);
    first = outcome.Task;
  }
  // The warden reads that the work happened, and not what it was given: it is not hers to run again.
  runas (Warden) {
    var refused = Assert.Throws<AgentRefused>(() => RetryAgentWork(AgentTask.Single()));
    Assert.StartsWith("Trying it again starts from what it was given", refused.Message);
  }
  runas (Keeper) {
    var sampler = TheSampler();
    ScriptedAnswer.Stub((model, request) => sampler.Answer(model, request));
    var again = RetryAgentWork(AgentTask.Single(t => t.Id == first));
    var second = AgentTask.Single(t => t.Id == again);
    Assert.Equal(2, second.Attempt);
    Assert.Equal(first, second.RetryOf!.Id);
    Assert.Equal(AgentTaskStatus.Done, second.Status);
    Assert.True(Packet.Single().Viable);
    // Once: the first attempt says it has been tried again.
    Assert.Throws<AgentRefused>(() => RetryAgentWork(AgentTask.Single(t => t.Id == first)));
  }
}
```

## See also       {#see-also}
- [Writing an agent — a class with a purpose, skills and an account of its own (Agents kit)](https://osysharp.com/reference/agent/agents/) — the agent class, its account and its sponsor
- [The management screens — where a person runs an app's agents (Agents.Management kit)](https://osysharp.com/reference/agent/management/) — the screens a person runs agents from, which offer each of these to whoever may
- [What an agent does alone, what it proposes, what it never does — the gate and proposals (Agents kit)](https://osysharp.com/reference/agent/autonomy/) — the gate, proposals and standing rules
- [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/) — who makes an instruction active
- [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
- [Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request](https://osysharp.com/reference/agent/privacy/) — erasing what work said
- [policy](https://osysharp.com/reference/security/naming-a-policy/) — a policy a kit asks and an app answers
