# The management screens — where a person runs an app's agents (Agents.Management kit)

> Osysharp.Agents.Management draws the place a person runs an app's agents from, eight screens under one path. Agents: every agent with who answers for it, whether it is working, what it has under way, what it has spent against what it may and how often people corrected it. One agent: the account it acts as, its sponsor, what it does alone, proposes and never does, its skills and versions, the agents it hands work to, what it is told, its limits, its work, and Pause, Resume, naming a sponsor and calling its work off — each with the reason, each written down. Work: all of it, narrowed by where it stands, agent, time, person and what it was charged to. One piece of work: the inspector's reading of it, and — for whoever is in charge of it — Call it off, Try it again and Hand it to a person. Waiting on people: every open request an agent put, by person, with how long. Autonomy: every standing rule with who gave it and how often it was used, Take back, and what a rule could still change. Spending: by agent, skill, person and charge, against each limit, and what a limit stopped. Roles: who may do what. Every figure is summed from the receipts and the meter as whoever is looking may read them; nothing asks a model anything.

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

## Summary        {#summary}
```osy syntax
use Osysharp.Agents.Management;

// Three routes, and all of it is there. A request opens in the inbox.
[Route("/agents")]                [Authorize(UsesAgents)] component Agents()                              { render { AgentsManagement(request: r => "/inbox/open/i/" + r); } }
[Route("/agents/{section}")]      [Authorize(UsesAgents)] component AgentsSection(string section)         { render { AgentsManagement(section, request: r => "/inbox/open/i/" + r); } }
[Route("/agents/{section}/{id}")] [Authorize(UsesAgents)] component AgentsItem(string section, string id) { render { AgentsManagement(section, id, request: r => "/inbox/open/i/" + r); } }

app.AgentInspector = new AgentInspectorSetup { WorkPage = "/agents/work/{task}" };   // a piece of work's own page
```

## Signature      {#signature}
```osy syntax
component AgentsManagement(string section = "", string id = "", string at = "/agents", Func<Guid, string>? request = null)

component AgentRoster(string at = "/agents")                                                   // every agent
component AgentProfilePage(string agent, string at = "/agents", Func<Guid, string>? request = null)   // one agent
component AgentWorkList(string at = "/agents", string agent = "")                              // all the work
component AgentWorkDetail(Guid task, string at = "/agents", Func<Guid, string>? request = null)   // one piece of work
component WorkInCharge(Guid task)                                                              // call off · try again · hand over
component PeopleAgentsWaitOn(string at = "/agents", Func<Guid, string>? request = null)        // waiting on people
component AgentStandingRules(string at = "/agents")                                            // autonomy
component AgentSpending(string at = "/agents")                                                 // spending
component AgentRoles(string at = "/agents")                                                    // roles
[Slot] component AgentRolesBeside()                                                            // another kit's policies, on Roles
component RoleRow(string policy, string kit, string admits, string untilSaid, bool you, bool mayAskWho = false)

RosterOf() → Roster                      AgentProfileOf(string agent) → AgentProfile
WorkBoardOf(WorkFilter filter) → WorkBoard                WorkControlsOf(Guid task) → WorkControls?
WaitingBoardOf() → WaitingBoard          RulesBoardOf() → RulesBoard
SpendingBoardOf(string period = "month") → SpendingBoard  // "today" · "week" · "month" · "all"
RolesBoardOf() → RolesBoard              PeopleAdmittedTo(string policy) → List<string>
class WorkFilter { string Agent, Status, Person, Since, Process, ChargedTo; bool Pieces; int Take; }

HandWorkToPerson(Guid task, Guid person, string note) → Guid   // the request put to them
StopWorkFromManagement(Guid task, string why)     RetryFromManagement(Guid task) → Guid     StopAllWorkOf(string agent, string why) → int
PauseFromManagement(string agent, string why)     ResumeFromManagement(string agent, string why = "")   SponsorFromManagement(string agent, Guid person)
TakeBackRule(Guid rule, string why)               AllowAloneFromManagement(string agent, string action, string skill, bool forEverybody)
```

## Description    {#description}

### What is on each screen?   {#screens}
`AgentsManagement` draws a strip of six links and, under it, the screen the path names. `at` is where it is mounted.

| path | screen | what it shows, and what can be done there |
|---|---|---|
| `at` | **Agents** | every agent: who answers for it, *Working · Waiting · Idle · Paused · No sponsor*, what it has under way, what it has spent today against its limit for a day and this month, and how often it was corrected in the last 30 days |
| `at/agent/{agent}` | **one agent** | the account it acts as and its sponsor; its tools under *Alone · Proposes first · Proposes every time · Never*; its skills with versions; the agents it hands work to and each one's share; what it is told, with a link to its instructions; one piece of work's limit, its day against the meter, today and this month; its recent work and what it waits on; every pause, resume and sponsor. **Pause**, **Resume**, **Change** (sponsor), **Call off its work**, and **Run** on each skill — a form of what the skill takes ([[agent-skills#run]]), and the work it starts is yours |
| `at/instructions/{agent}` | **its instructions** | the Instructions page of [Correct this, and an agent's Instructions page — the screens people teach an agent on (Agents.Teaching kit)](https://osysharp.com/reference/agent/teaching-screens/) |
| `at/work` | **Work** | all the work, newest first: narrowed by where it stands, by agent, by when it started, by who it is for, by the process it is a step of and by what it was charged to; handed-on pieces on request |
| `at/work/{task}` | **one piece of work** | the inspector ([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/)), the requests it waits on, what teaching adds — and **In charge of this work** |
| `at/waiting` | **Waiting on people** | each person the agents wait on, with how many requests and how long the oldest has waited; then every request — what is asked, by which agent and through whom, about which work, since when, late or not |
| `at/autonomy` | **Autonomy** | every standing rule: who gave it, for whose work, how often it was used; **Take back**; what a rule could still change, with **Alone, for my work** and **Alone, for everybody** |
| `at/spending` | **Spending** | the whole for a period, and by agent, skill, person, process and charge; every limit with the meter's count against it; the work a limit stopped |
| `at/roles` | **Roles** | every policy of [Who is in charge of an app's agents — every policy in one table, pausing, sponsoring, and trying work again](https://osysharp.com/reference/agent/roles/) with what it admits, and whether it admits whoever is looking; **Who?** for those in charge; each agent's sponsor |

Each screen is also a component of its own, for a page the app lays out itself.

### What can be done to a piece of work?   {#work-controls}
Three things, offered at the head of the inspector to whoever may do each, each with its reason:

- **Call it off** — `StopAgentTask`: the work stops at its next step and keeps what it has done and spent; work it
  handed to other agents stops with it, and what it had put to people is withdrawn. For the person the work is for,
  whoever gave it, and whoever supervises.
- **Try it again** — `RetryAgentWork` ([[agent-roles#again]]): a new attempt from the same brief, for work that
  failed, was stopped or was called off. When it is not offered, the screen says why in a sentence.
- **Hand it to a person** — `HandWorkToPerson(task, person, note)`: the agent is called off where it stands, when it
  is still under way, and the person is asked to take it from here — a request in their inbox ([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/))
  with the note and how the work stood. For whoever may call the work off and may put a request to somebody
  (`AsksPeople`). Never to an agent's own account, and to one person at a time.

The kit contributes these to the inspector's `AgentWorkActions` slot, so they are at the head of **every**
`WorkInspector` the app draws — a process run's steps included. `WorkInCharge(task)` is the same block, to place
yourself.

### Where did a proposal go?   {#where}
A skill may send what it proposes to a work queue or to one person ([[agent-autonomy#route]]). The screens say so:
the agent's page writes "put to the Finance queue" beside each tool it proposes there; *who is waited on* counts the
queue by its name, as it counts a person; and a piece of work's page says "Waiting for the Finance queue since …".

### Whose figures are they?   {#figures}
The reader's. Every screen reads as whoever is looking, under the rules [Who is in charge of an app's agents — every policy in one table, pausing, sponsoring, and trying work again](https://osysharp.com/reference/agent/roles/) describes:

- somebody who reads every receipt (`ReadsAllAgentWork`) sees the whole — and, when they neither supervise nor
  audit, every figure and no words: the work is listed by its skill, with nothing of what it was about;
- somebody who only gives agents work sees the work they asked for, and each screen says so (*The figures count the
  work you asked for*);
- a piece of work that is not theirs says so, and shows nothing.

Sums are taken where the receipts are stored — a line a group, never a row a piece of work — and a limit is shown
against the meter's own count, which is what the limit is held to. **Spending** counts the work that *started* in
the period. Nothing on these screens calls a model.

### What do the screens decide?   {#decide}
Nothing. Each press calls [`Osysharp.Agents`](https://osysharp.com/reference/agent/roles/)' own function for it — or `Osysharp.HumanRequests`' —
which says who may; a control is offered only to somebody it will work for, and a refusal is said on the screen in
the kit's own words. **Roles** changes nothing at all: who holds a role is said in the app's source.

### How does another kit add its role?   {#roles-slot}
`AgentRolesBeside` is a slot at the foot of *The kits beside*. A kit, or the app, that has a policy about agents of
its own contributes a `RoleRow`:

```osy syntax
[Contribution("Process roles")]
component ProcessRoles() : AgentRolesBeside {
  live var follows = FollowsProcesses;
  render { RoleRow("FollowsProcesses", "Osysharp.Processes", "Open the list of process runs, and a run.", "Nobody", follows); }
}
```

### How is it tested?   {#tests}
As a person: `Ui.Visit("/agents")`, `Ui.Click("Work", within: "Screens")`, `Ui.Click("Waiting", within: "Where it
stands")`, `Ui.Click("Pause")` then fill "Why is it paused?" and `Ui.Click("Pause it")`, `Ui.Click("Hand it to a
person")`. Regions are named for `within:` — "In all", "Agent Describer", "Where it stands" (an agent's head; the
work list's tabs), "Sponsor", "Alone", "Proposes first", "Hands work to", "A day", "Its work", "History",
"Work: Accession of reel 0412", "How many", "Waited on Jonas Reiter", "Rule: ConditionChecker Quarantine the reel",
"By agent: Describer", "Limit: Describer, a day", "Stopped at a limit", "Role PausesAgents", "In charge of this
work". The values each screen draws are functions too — `RosterOf()`, `WorkBoardOf(filter)`, `SpendingBoardOf(period)`
— for a test that asserts figures.

## Examples       {#examples}
```osy title="the three routes, and what the screens read as the keeper of the maps" run app=agent-management
app Chartroom {
  use Osysharp.Agents;
  use Osysharp.Agents.Testing;
  use Osysharp.Agents.Teaching;
  use Osysharp.Agents.Inspector;
  use Osysharp.Agents.HumanRequests;
  use Osysharp.Agents.Management;
  use Osysharp.HumanRequests;
  use Osysharp.WorkQueues;
  use Osysharp.Organizations;
  use Osysharp.Organizations.ServicePlans;
  use Osysharp.Presence;
  use Osysharp.ServiceHours;
  use Osysharp.Llm;
  use Osysharp.Identity;
  use Osysharp.Observability;
  use Osysharp.Workflow;
  use Osysharp.Ui;
  model "model/**/*.osy";
}

using Osysharp.Identity;
using Osysharp.Organizations;
using Osysharp.Organizations.ServicePlans;
using Osysharp.WorkQueues;
using Osysharp.HumanRequests;
using Osysharp.ServiceHours;
using Osysharp.Llm;
using Osysharp.Agents;
using Osysharp.Agents.Testing;
using Osysharp.Agents.Inspector;
using Osysharp.Agents.HumanRequests;
using Osysharp.Agents.Management;
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 Indexer()], TaskBudget = 0.05m };
app.AgentRequests = new AgentRequestsSetup { Queue = "Maps" };
app.Inbox = new InboxSetup { DefaultQueue = "Maps" };
// A piece of work's own page is the management screens'.
app.AgentInspector = new AgentInspectorSetup { WorkPage = "/agents/work/{task}" };

[Role] enum ChartRole { Authenticator, Cartographer, Keeper, Shelving }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsCartographer => RoleGrant.Any(g => g.Member == user && g.Role == ChartRole.Cartographer);
policy IsKeeper       => RoleGrant.Any(g => g.Member == user && g.Role == ChartRole.Keeper);
policy IsShelving     => RoleGrant.Any(g => g.Member == user && g.Role == ChartRole.Shelving);
policy IsStaff        => IsCartographer || IsKeeper;

// The inbox and its requests: the staff work it and ask through it; the keeper leads and oversees.
policy WorksInbox       => IsStaff || IsShelving;
policy LeadsInbox       => IsKeeper;
policy AdministersInbox => IsKeeper;
policy ManagesOrganizations  => IsKeeper;
policy CollaboratesOnRecords => IsStaff;
policy AsksPeople       => IsStaff;
policy OverseesRequests => IsKeeper;
// The agents: the staff give the indexer work, and one line puts the keeper in charge of it.
policy UsesAgents       => IsStaff;
policy SupervisesAgents => IsKeeper;

entity Person : Principal {
  security { allow read when IsAuthenticated || IsAuthenticator; allow create when IsAuthenticator; }
}
partial entity ServiceHours { security { allow read when IsStaff; allow create, update, delete when IsKeeper; } }
partial entity ServiceWindow { security { allow read when IsStaff; allow create, update, delete when IsKeeper; } }
partial entity ServiceException { security { allow read when IsStaff; allow create, update, delete when IsKeeper; } }
partial entity WorkItem { security { allow read when IsStaff || IsShelving; } }

/// One sheet of a map.
entity Sheet {
  [Required, Unique, MaxLength(40)] string Number;
  [MaxLength(200)] string Covers = "";
  [MaxLength(40)] string Drawer = "";
  security { allow read, update when IsStaff || IsShelving; allow create when IsStaff; }
}

/// What a sheet covers.
[Action("Read what the sheet covers", Query)]
public string CoverOf(Sheet sheet) { return sheet.Covers; }

/// Put a sheet in a drawer.
[Action("Move the sheet to a drawer")]
public string MoveToDrawer(Sheet sheet, string drawer) { sheet.Drawer = drawer; return sheet.Number + " is in " + drawer + "."; }

public class Filed { public string Drawer = ""; }

public class FileSheet : Skill<Sheet, Filed> {
  public override string Description => "Decide which drawer a sheet belongs in, and put it there.";
  public override List<ActionInfo> Tools => [Actions.Of(CoverOf), Actions.Of(MoveToDrawer)];
  public override string Brief(Sheet s) => $"Sheet {s.Number}. Its id is {s.Id}.";
}

/// Files the sheets. It reads alone; moving a sheet is proposed to a person.
[Actor(Roles = [ChartRole.Shelving])]
public class Indexer : Agent {
  public static Person Principal => new Person { Email = "indexer@chartroom.example", DisplayName = "Indexer" };
  public override string Purpose => "You file the sheets of a map collection.";
  public override List<SkillBase> Skills => [new FileSheet()];
}

public AgentOutcome<Filed> File(Sheet sheet) { return await Agents.Of<Indexer>().Do(new FileSheet(), sheet, about: "Filing sheet " + sheet.Number); }

// ⭐ The management screens: three routes.
[Route("/agents")] [Authorize(UsesAgents)] [Render(CSR)] [Title("Agents")]
component AgentsHome() { render { AgentsManagement(request: r => "/inbox/open/i/" + r); } }

[Route("/agents/{section}")] [Authorize(UsesAgents)] [Render(CSR)] [Title("Agents")]
component AgentsSection(string section) { render { AgentsManagement(section, request: r => "/inbox/open/i/" + r); } }

[Route("/agents/{section}/{id}")] [Authorize(UsesAgents)] [Render(CSR)] [Title("Agents")]
component AgentsItem(string section, string id) { render { AgentsManagement(section, id, request: r => "/inbox/open/i/" + r); } }

[TestFixture]
void Room() {
  var edda = new Person { Email = "edda@chartroom.example", DisplayName = "Edda Varga" };
  var tomas = new Person { Email = "tomas@chartroom.example", DisplayName = "Tomas Brandt" };
  new RoleGrant { Member = edda, Role = ChartRole.Cartographer };
  new RoleGrant { Member = tomas, Role = ChartRole.Keeper };
  new OrganizationTier { Name = "Standard", Rank = 1, IsDefault = true };
  var queue = new Queue { Name = "Maps", Position = 1 };
  new QueueMember { Queue = queue, Member = edda };
  new QueueMember { Queue = queue, Member = tomas };
  new Sheet { Number = "NW-14", Covers = "The estuary, surveyed 1894." };
  new AgentSeat { Agent = "Indexer", Sponsor = tomas };
}
principal Edda  => Person.Single(p => p.Email == "edda@chartroom.example");
principal Tomas => Person.Single(p => p.Email == "tomas@chartroom.example");

[Test(Room)]
void the_screens_read_the_records_and_a_piece_of_work_is_handed_to_a_person() {
  TestClock.Set(new DateTime(2031, 9, 1, 9, 0, 0, DateTimeKind.Utc));
  Guid task = Guid.Empty;
  runas (Edda) {
    var sheet = Sheet.Single();
    var id = sheet.Id.ToString();
    var script = AgentScript.Of(
      Says.Tool(Actions.Of(CoverOf), "{\"sheet\":\"" + id + "\"}", 400, 40),
      Says.Tool("It covers the estuary, and the estuary sheets are in drawer 7.", Actions.Of(MoveToDrawer), "{\"sheet\":\"" + id + "\",\"drawer\":\"Drawer 7\"}"));
    ScriptedAnswer.Stub((model, request) => script.Answer(model, request));
    var outcome = File(sheet);
    Assert.True(outcome.Waiting);                 // moving a sheet is proposed first
    task = outcome.Task;
    Background.Settle();
  }
  TestClock.Advance(TimeSpan.FromHours(3));
  runas (Tomas) {
    // The roster: one agent, one piece of work that waits, and what it has cost.
    var roster = RosterOf();
    var indexer = roster.Agents.Single();
    Assert.Equal("Waiting", indexer.State);
    Assert.Equal("Tomas Brandt", indexer.Sponsor);
    Assert.Equal(1, roster.Waiting);
    Assert.Equal("$0.0008", roster.Today);        // 600 + 200 millionths of a dollar

    // The agent's page: what it does alone, and what it proposes first.
    var profile = AgentProfileOf("Indexer");
    Assert.Equal(["Read what the sheet covers"], profile.Alone.Select(t => t.Label).ToList());
    Assert.Equal(["Move the sheet to a drawer"], profile.Proposes.Select(t => t.Label).ToList());
    Assert.Equal("$0.0500", profile.PerTask);
    Assert.True(profile.MayPause);

    // The work, narrowed to what waits.
    var waiting = WorkBoardOf(new WorkFilter { Status = "waiting" }).Lines.Single();
    Assert.Equal("Filing sheet NW-14", waiting.About);
    Assert.Equal("Edda Varga", waiting.For);
    Assert.Equal("a decision from Edda Varga", waiting.Waits);
    Assert.Equal("for 3 hours", waiting.Took);

    // Who the agents wait on.
    var board = WaitingBoardOf();
    Assert.Equal("Edda Varga", board.People.Single().Name);
    Assert.Equal("Indexer proposes: Move the sheet to a drawer", board.Lines.Single().Title);
    Assert.Equal("3 hours", board.Oldest);

    // What a rule could change, and what was spent.
    Assert.Equal("Move the sheet to a drawer", RulesBoardOf().CouldWiden.Single().Tool);
    var spent = SpendingBoardOf("today");
    Assert.Equal("$0.0008", spent.Total);
    Assert.Equal("Edda Varga", spent.ByPerson.Single().Label);

    // In charge of the piece of work: the keeper hands it to himself, with a note. The indexer is called off.
    var controls = WorkControlsOf(task)!;
    Assert.True(controls.MayStop);
    Assert.True(controls.MayHand);
    HandWorkToPerson(task, Tomas.Id, "The estuary sheets are being re-drawered this week. I will file this one by hand.");
    UnitOfWork.Commit();
  }
  Background.Settle();
  foreach (var wait in AgentWait.Where(w => w.Status == AgentWaitStatus.Waiting).ToList()) { Workflow.Settle(wait); }
  Background.Settle();
  runas (Tomas) {
    Assert.Equal(AgentTaskStatus.Cancelled, AgentTask.Single().Status);
    Assert.Equal("", Sheet.Single().Drawer);                          // nothing was moved
    var mine = Requests.Mine().Single();
    Assert.Equal("Take over from Indexer: Filing sheet NW-14", mine.Title);
    Assert.Equal(0, WaitingBoardOf().Lines.Count);                    // no agent waits on anybody any more
    Assert.Equal("Tomas Brandt", WorkControlsOf(task)!.HandedTo);
  }
  // Edda, who asked for the work, reads her own figures; who holds a role is not hers to ask.
  runas (Edda) {
    Assert.False(RosterOf().ReadsAll);
    Assert.Throws<AgentRefused>(() => PeopleAdmittedTo("SupervisesAgents"));
    Assert.False(RolesBoardOf().InCharge.Single(r => r.Policy == "PausesAgents").You);
  }
}
```

## See also       {#see-also}
- [Who is in charge of an app's agents — every policy in one table, pausing, sponsoring, and trying work again](https://osysharp.com/reference/agent/roles/) — every policy, pausing and sponsoring, trying work again: what the screens call
- [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, which the work screen is built on
- [Correct this, and an agent's Instructions page — the screens people teach an agent on (Agents.Teaching kit)](https://osysharp.com/reference/agent/teaching-screens/) — "Correct this" and an agent's Instructions page
- [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/) — an agent's proposals and questions in a person's inbox
- [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 and standing rules
- [What an agent may spend — a hard limit on every piece of work (Agents kit)](https://osysharp.com/reference/agent/budgets/) — what an agent may spend
- [[Slot] and contributions — a place on a page that other kits, and the app, add to](https://osysharp.com/reference/ui/contributions/) — slots, and what a contribution is
