Osy#betaRun it on Osyrin: osyrin.com

Reference / Agent

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

use Osysharp.Agents.Inspector; · WorkInspector(task, step:, embedded:) · InspectAgentWork(task) → WorkInspection? · app.AgentInspector = new AgentInspectorSetup { WorkPage = "/work/{task}/{step}" } · WorkPageOf(task, step) · [Slot] AgentWorkPanel(task) · [Slot] AgentWorkOrigin(task)

Osysharp.Agents.Inspector draws one page for one piece of an agent's work: what it was and how it ended; who gave it, who it was for and as which account it ran; what it was given and what it handed back; what it spent against what it may; each proposal with who decided and after how long, each question with its answer; the work it handed on, as a tree; every step in order — model calls with the model, why that one and its cost, tools with their arguments as a person reads them and what they answered, waits with how long they lasted; the instruction set it ran with and what has changed since; what it may do with each tool; whether its record's chain still checks; and what was erased, shown as erased. Read-only, read as whoever is looking, filterable, and each step has its own address.

preview1 example compiled by CIagentskitui

Summary#

use Osysharp.Agents.Inspector;

app.AgentInspector = new AgentInspectorSetup { WorkPage = "/work/{task}/{step}" };

[Route("/work/{task}/{step}")] [Authorize(UsesAgents)]
component WorkPage(Guid task, int step) { render { WorkInspector(task, step: step); } }

Signature#

component WorkInspector(Guid task, int step = 0, bool embedded = false)
component InspectedStepLine(InspectedStep step, bool marked = false, bool showWho = false)
component InspectedProposalCard(InspectedProposal p) · component InspectedHelpCard(InspectedHelp h)
[Slot] component AgentWorkActions(Guid task)      // what can be done about it, at the head of the page
[Slot] component AgentWorkPanel(Guid task)        // more about the work, under its record
[Slot] component AgentWorkOrigin(Guid task)       // where the work came from, beside who asked

InspectAgentWork(Guid task) → WorkInspection?      // null: not whoever-is-looking's to read
class AgentInspectorSetup { string WorkPage; }     // app.AgentInspector = …
WorkPageOf(Guid task, int step = 0) → string       // the work's address on the app's own page; "" when it has none

ArgumentsAsRead(agent, skill, action, argumentsJson) → List<ProposalLine>   // Osysharp.Agents: a call's arguments, named

Description#

What does the page show, and in what order?#

The order a person asks in, when they were not there:

The workthe agent and the skill, what the work is about, how it ended and why — or what it is waiting on, and for how long so far; and anything that has been erased of it
What was askedwho gave the work, who it was for, as which account it ran, the process it was a step of (Agents in business processes — an agent's work as a step of a workflow, told on the run (Agents.Processes kit)), and what it was given, as the model was shown it
What it handed backthe result
In figureswhen it started, how long it took, what it spent on models against what it may, its calls and tokens, and what else it was charged to
People and helpeach proposal — the call, its arguments as a person reads them, why, who it was put to, who decided, after how long, their reason, what came of it (What an agent does alone, what it proposes, what it never does — the gate and proposals (Agents kit)); each request for help — of whom, the question, the answer, how long it took or what it cost (An agent asks a person, and waits — the person rung of the ladder of help (Agents kit))
The work it is part ofwhen work was handed on (An agent hands work on to other agents — at once, typed, and never with more than it holds (Agents kit)): the tree, each piece with how it ended and what it cost — and the steps of every piece, in the one record they share
Stepsevery line of the record, in order (The receipt and the record — what every piece of an agent's work leaves (Agents kit)), narrowed to Model, Tools, People or Problems; a wait says how long it lasted; More opens what the step said
What it had been told, and what it may dothe instruction set it ran with and what has been made active since that it had not been sent (Correcting an agent, and what it is told from then on — corrections, instructions, instruction sets and rollback (Agents kit)); each tool with whether the agent runs it alone, proposes it, or never does
The recordwhether its chain still checks: no line changed, removed or slipped in

What does a step say when it is opened?#

A model call: the model, the rule of the router that chose it, how many were tried, and the words the model said. A tool call: what the tool is called, and its arguments each with its name and its value as the skill names it (SkillBase.Names — a supplier's name, not its id). A tool result, a decision and the end: what was answered. The step's number is its link: with {step} in WorkPage, /work/<task>/7 opens the page with step 7 marked.

Who may open it, and what do they see?#

It changes nothing and widens nothing: InspectAgentWork reads the receipt, the record, the proposals and the requests for help as whoever is looking, under the rules Osysharp.Agents declares.

the readersees
whoever the work was for, and whoever gave itall of it, with what each step said
whoever supervises the agentsthat each step happened, what it cost and how it ended — a step's More says that what it said is kept from them
whoever audits the agentsall of it
anybody elseThis piece of work is not yours to see

What does erased work look like?#

Like work that was erased. The page says when what it said was erased and why, a step whose content is gone carries Erased, and a step whose sentence is gone says so in its place — never an empty line, and never as if nothing had been there. What happened, in what order, by which model and at what cost is unchanged, and the record still checks (Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request).

How do other kits add to it?#

Three slots ([Slot] and contributions — a place on a page that other kits, and the app, add to). AgentWorkActions(task) is at the head of the page, straight under what the work is and how it stands — where Osysharp.Agents.Management puts calling it off, trying it again and handing it to a person, so nobody scrolls past a long record to stop it. AgentWorkPanel(task) is under the record: "Correct this" from Osysharp.Agents.Teaching goes there in three lines (Correct this, and an agent's Instructions page — the screens people teach an agent on (Agents.Teaching kit)):

[Contribution("Teaching")]
component TeachHere(Guid task) : AgentWorkPanel { render { WorkTeaching(task, beside: true); } }

AgentWorkOrigin(task) is beside who asked: Osysharp.Agents.Processes fills it with the run of the process the work is a step of, and the way to it. embedded: true leaves the headline out, for the work shown inside another page — which is how a run's page opens a step in place.

Examples#

app Kennel {
  use Osysharp.Agents;
  use Osysharp.Agents.Testing;
  use Osysharp.Agents.Inspector;
  use Osysharp.Llm;
  use Osysharp.Identity;
  use Osysharp.Observability;
  use Osysharp.Workflow;
  use Osysharp.Ui;
  model "model/**/*.osy";
}

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

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 Feeder()] };
// Where a piece of work is shown: each step's number links to its own address.
app.AgentInspector = new AgentInspectorSetup { WorkPage = "/work/{task}/{step}" };

[Role] enum KennelRole { Authenticator, Keeper, Feeding }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsKeeper  => RoleGrant.Any(g => g.Member == user && g.Role == KennelRole.Keeper);
policy IsFeeding => RoleGrant.Any(g => g.Member == user && g.Role == KennelRole.Feeding);
policy UsesAgents       => IsKeeper;
policy SupervisesAgents => IsKeeper;

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

entity Dog {
  [Required, Unique, MaxLength(60)] string Name;
  decimal Kilos = 0;
  [MaxLength(80)] string Ration = "";
  security { allow read, update when IsKeeper || IsFeeding; allow create when IsKeeper; }
}

/// What a dog weighs.
[Action("Weigh the dog", Query)]
public decimal WeightOf(Dog dog) { return dog.Kilos; }

/// Change a dog's ration.
[Action("Set the ration")]
public string SetRation(Dog dog, string ration) { dog.Ration = ration; return dog.Name + " gets " + ration + "."; }

public class Plan { public string Ration = ""; }

public class PlanRation : Skill<Dog, Plan> {
  public override string Description => "Work out one dog's daily ration.";
  public override List<ActionInfo> Tools => [Actions.Of(WeightOf), Actions.Of(SetRation)];
  public override string Brief(Dog d) => $"{d.Name}. Its id is {d.Id}.";
  // A person reads the dog's name, not its id.
  public override string Names(string action, string argument, string value) {
    if (argument != "dog") { return value; }
    var d = Dog.FirstOrDefault(x => x.Id == Guid.Parse(value));
    return d == null ? value : d!.Name;
  }
}

/// Plans the rations. It weighs by itself; changing a ration is proposed to a person.
[Actor(Roles = [KennelRole.Feeding])]
public class Feeder : Agent {
  public static Person Principal => new Person { Email = "[email protected]", DisplayName = "Feeder" };
  public override string Purpose => "You plan the daily rations of a small kennel.";
  public override List<SkillBase> Skills => [new PlanRation()];
}

public AgentOutcome<Plan> PlanFor(Dog dog) { return await Agents.Of<Feeder>().Do(new PlanRation(), dog, about: "Ration for " + dog.Name); }

/// One piece of the feeder's work, readable by somebody who was not there; `step` marks the step a link named.
[Route("/work/{task}/{step}")] [Authorize(UsesAgents)] [Render(CSR)] [Title("The feeder's work")]
component WorkPage(Guid task, int step) {
  render { Stack(p: 5, maxW: "920px") { WorkInspector(task, step: step); } }
}

[TestFixture]
void Yard() {
  var keeper = new Person { Email = "[email protected]", DisplayName = "Runa Halvorsen" };
  new RoleGrant { Member = keeper, Role = KennelRole.Keeper };
  new Dog { Name = "Biscuit", Kilos = 31 };
  new AgentSeat { Agent = "Feeder", Sponsor = keeper };
}
principal Keeper => Person.Single(p => p.Email == "[email protected]");

[Test(Yard)]
void a_piece_of_work_is_read_back_by_somebody_who_was_not_there() {
  TestClock.Set(new DateTime(2031, 2, 3, 7, 0, 0, DateTimeKind.Utc));
  Guid task = Guid.Empty;
  runas (Keeper) {
    var dog = Dog.Single();
    var id = dog.Id.ToString();
    var script = AgentScript.Of(
      Says.Tool(Actions.Of(WeightOf), "{\"dog\":\"" + id + "\"}", 300, 20),
      Says.Tool("At 31 kg it needs more than a standard bowl.", Actions.Of(SetRation), "{\"dog\":\"" + id + "\",\"ration\":\"450 g, twice a day\"}", 400, 40),
      Says.HandBack(new Plan { Ration = "450 g, twice a day" }, 500, 20));
    ScriptedAnswer.Stub((model, request) => script.Answer(model, request));
    var outcome = PlanFor(dog);
    Assert.True(outcome.Waiting);                 // changing a ration is proposed first
    task = outcome.Task;
  }
  TestClock.Advance(TimeSpan.FromHours(5));
  runas (Keeper) {
    ApproveAgentProposal(ProposalsForMe().Single());
    Workflow.Settle(AgentWait.Single());
  }

  runas (Keeper) {
    var seen = InspectAgentWork(task)!;
    // What it was, who asked and as whom it ran.
    Assert.Equal("Ration for Biscuit", seen.About);
    Assert.Equal(AgentTaskStatus.Done, seen.Status);
    Assert.Equal("Given by Runa Halvorsen.", seen.GivenBy);
    Assert.Equal("Done for Runa Halvorsen: never past what they may do themselves.", seen.For);
    Assert.Equal("It ran as Feeder, Feeder's own account.", seen.RanAs);
    Assert.Equal("450 g, twice a day", seen.Result);
    // What it may do with each tool.
    Assert.Equal(["Weigh the dog: Alone", "Set the ration: Proposes first"], seen.Tools.Select(t => t.Label + ": " + t.Leave.Label).ToList());
    // What it proposed, and who decided after how long.
    var proposed = seen.Proposals.Single();
    Assert.Equal(["dog: Biscuit", "ration: 450 g, twice a day"], proposed.Shown.Select(l => l.Name + ": " + l.Value).ToList());
    Assert.Equal("Approved by Runa Halvorsen on 3 Feb 2031, 12:00 after 5 hours, and it ran as them.", proposed.Decided);
    // Every step in order; a tool's arguments as a person reads them; the wait and how long it lasted.
    Assert.Equal(["Started", "ModelCall", "ToolCall", "ToolResult", "ModelCall", "ToolCall", "Waiting", "Decided", "ModelCall", "Ended"],
                 seen.Steps.Select(s => s.Kind.Name).ToList());
    Assert.Equal("dog: Biscuit", seen.Steps[2].Given.Select(g => g.Name + ": " + g.Value).Single());
    Assert.Equal("31", seen.Steps[3].Content);
    Assert.Equal("5 hours", seen.Steps.Single(s => s.Kind == TraceKind.Waiting).Took);
    Assert.Equal("/work/" + task.ToString() + "/3", seen.Steps[2].Href);
    // What it cost against what it may, and whether its record still checks.
    Assert.Equal("$0.0016", seen.Spent);
    Assert.Equal("$0.50", seen.Limit);
    Assert.True(seen.Intact);
    Assert.Equal("All 10 lines of its record are as they were written.", seen.Record);
  }
}

Notes#

  • A record that does not check says so, with the first line that does not fit; the page still shows everything it can read.
  • A wait's length is the time until the next line of the record; the last line of work that is still waiting says how long it has waited so far.

See also#

Related

The receipt and the record — what every piece of an agent's work leaves (Agents kit)

Every piece of work leaves one receipt, `AgentTask` — who asked, the account it ran as, what it cost against its limit…

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…

Correct this, and an agent's Instructions page — the screens people teach an agent on (Agents.Teaching kit)

Osysharp.Agents.Teaching draws the two places people teach an agent. WorkTeaching(task) goes on the page of one piece…

What an agent does alone, what it proposes, what it never does — the gate and proposals (Agents kit)

An agent's class says what it may do alone, what it must propose to a person first, and what it never does — by what a…

An agent asks a person, and waits — the person rung of the ladder of help (Agents kit)

An agent that says so may put one focused question to a person: the model chooses to (a tool it is offered), or work…

Correcting an agent, and what it is told from then on — corrections, instructions, instruction sets and rollback (Agents kit)

An agent gets better through people. Somebody who sees a piece of its work corrects it — what was wrong, what right…

Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request

An app that uses `Osysharp.Privacy` beside the agent kits gets a holder for each without writing one:…

What an agent may spend — a hard limit on every piece of work (Agents kit)

Every piece of work has a limit in dollars — the skill's `Budget`, else the agent's `Limits.PerTask`, else the app's…

[Slot] and contributions — a place on a page that other kits, and the app, add to

A slot is a place on a page others add to. A kit marks one (`[Slot] public abstract component QuotePanel(Quote…

Agents (a class with a job, its own account, a budget and a record)

An agent is a class of the `Osysharp.Agents` kit: a purpose, the skills it holds and what it may spend. A skill is a…