# A skill — a typed job an agent can be given, its check and its tools (Agents kit)

> A skill is a class: what goes in, what comes back, and the app's own functions the agent may use to get there. The model is held to the result type's schema, `Check` sees the value before your code does, and a tool the skill does not list never runs. `Skills.Catalog()` describes every skill of every agent from its own declaration.

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

## Summary        {#summary}
```osy syntax
public class EstimateRepair : Skill<Repair, Estimate> {
  public override string Description => "Estimate one repair.";
  public override string Instructions => "Look up the parts first.";
  public override List<ActionInfo> Tools => [Actions.Of(PartsFor), Actions.Of(NoteRepair)];
  public override string? Check(Estimate e) => e.Hours <= 0 ? "An estimate takes some time." : null;
}
```

## Signature      {#signature}
```osy syntax
public abstract class Skill<TIn, TOut> : SkillBase {
  virtual string Description;            // what the job is — the model is told, and a catalogue shows it
  virtual string Instructions;           // how to do it
  virtual List<ActionInfo> Tools;        // the app's [Action] functions it may use
  virtual string Brief(TIn input);       // what the model is shown of the input
  virtual string? Check(TOut result);    // null when it fits; otherwise the reason, sent back to the model
  virtual decimal? Budget;               // this job's own limit, in dollars
  virtual ModelNeeds Needs;              // what the app's model router decides from
  virtual StopWhen Stop;                 // MaxTurns, SameCall, ClearResultsPast
  virtual string Version;                // written on every receipt
}

List<SkillInfo> Skills.Catalog()         // every skill of every listed agent
List<SkillInfo> Skills.Of(string agent)
SkillInfo?      Skills.Find(string agent, string skill)
```

## Description    {#description}

### What are `TIn` and `TOut`?   {#types}
`TIn` is what the job is given — a row, a class or a `string`. `TOut` is what it hands back: a class, whose
[schema](https://osysharp.com/reference/json/schema-of/) the model is held to, or `string` for a job that answers in words. With a class result the
model finishes by calling a tool the kit adds, `hand_back`, whose arguments are that schema.

### What does `Check` do that the schema does not?   {#check}
The schema says a score is a number; `Check` says it is from 0 to 10. A result that is not the shape, or that `Check`
refuses, goes back to the model with the reason and it tries again. After `StuckAfter` failures in a row the agent
[asks for help](https://osysharp.com/reference/agent/help/) once if it has somebody to ask, and then the work ends as `Failed`.

### What is a tool?   {#tools}
One of your app's [[function-action|`[Action]`]] functions, named with `Actions.Of(F)`. Nothing is described twice:
the tool's name, its description and the schema of its arguments are the function's own, and what it reads, changes
and sends is what the compiler proved. A row argument is passed by its id and looked up with the rights the tool
runs under. A tool the skill does not list is refused whatever the model calls it, and the refusal is on the record.

### How does a job stop?   {#stop}
`Stop` bounds it: `MaxTurns` model calls, `SameCall` identical tool calls in a row (it is going in circles), and
`ClearResultsPast`, past which the results of old tool calls are replaced by one line each in what the model is
sent — the record still holds each one whole.

### What does the catalogue hold?   {#catalog}
`Skills.Catalog()` answers a `SkillInfo` per agent and skill: `Agent`, `Skill`, `Label`, `Description`, `Version`,
`Takes` and `HandsBack` with their schemas, and `Tools` — each with its `Action`, `Label`, `Description`,
`ParametersSchema` and whether it `ReadsOnly`, `Changes` or `Sends`. It is what a picker or a canvas lists.

### How does a person run a skill on demand?   {#run}
`RunSkill(agent, skill, inputJson)` gives the agent that job, with what the skill takes as JSON, for whoever is
signed in — exactly as `Agents.Of<T>().Do(…)` would: the input is read as the agent and never past what that person
may read, its proposals come to them, and it answers the receipt's id. It is for whoever the app lets give agents
work (`UsesAgents`).

`InputFieldsOf(skill)` is what a form asks for, built from the same schema another agent is handed the work by
(`HandedSchema`): one field per member of a class, the id of a row the skill takes by `HandOn.ById`, or one line for
a skill that takes text — each with its label, whether it is a number or a yes or no, and whether it is required.
`InputJsonOf(fields, values)` turns what was typed into the JSON the skill takes, and says what is missing or not a
number. The agent's page in [Agents.Management](https://osysharp.com/reference/agent/management/) has a **Run** button on each skill built from them.

```osy syntax
var fields = InputFieldsOf(new GradeCrate());                          // [ id: "The crate to grade." ]
var task = RunSkill("Packhouse.Inspector", "Packhouse.GradeCrate",
                    InputJsonOf(fields, new Dictionary<string, string> { ["id"] = crate.Id.ToString() }));
```

A skill that takes a row it does not say how to find (no `HandedSchema` / `Handed`) cannot be run this way, and says
so.

## Examples       {#examples}
```osy title="a result the check refuses is sent back, a tool that is not listed never runs, and the catalogue" run app=agent-skills
app Workshop {
  use Osysharp.Agents;
  use Osysharp.Agents.Testing;
  use Osysharp.Llm;
  use Osysharp.Identity;
  use Osysharp.Observability;
  model "model/**/*.osy";
}

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

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 } ],
};
app.Agents = new AgentsSetup { Agents = [new Estimator()], TaskBudget = 0.05m };

[Role] enum ShopRole { Authenticator, Mechanic, Manager, Helper }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsMechanic => RoleGrant.Any(g => g.Member == user && g.Role == ShopRole.Mechanic);
policy IsManager  => RoleGrant.Any(g => g.Member == user && g.Role == ShopRole.Manager);
policy IsHelper   => RoleGrant.Any(g => g.Member == user && g.Role == ShopRole.Helper);

// The kit's questions, answered by this app.
policy UsesAgents       => IsMechanic || IsManager;   // gives agents work, reads the work they asked for
policy SupervisesAgents => IsManager;                 // names sponsors, pauses and stops, reads that work happened
policy AuditsAgents     => IsManager;                 // reads everything, content included
policy ReadsModelUsage   => IsManager;
policy ManagesModelSpend => IsManager;

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

/// One bike in for repair.
entity Repair {
  [Required, Unique, MaxLength(40)] string Ticket;
  [Required, MaxLength(200)] string Fault;
  [MaxLength(400)] string Note = "";
  /// What the customer is charged. Only a mechanic or the manager sets it.
  decimal Price;
  security {
    allow read, update when IsMechanic || IsManager || IsHelper;
    allow create when IsMechanic || IsManager;
    deny update Price when IsHelper;
  }
}

/// What an estimate says.
public class Estimate {
  public decimal Hours;
  public string Summary = "";
  public bool NeedsParts;
}

/// The parts a fault needs.
[Action("Look up the parts a fault needs", Query)]
public List<string> PartsFor(string fault) { return fault.Contains("brake") ? ["brake pads", "cable"] : []; }

/// Write a note on the repair's ticket.
[Action("Write a note on the repair")]
public string NoteRepair(Repair repair, string note) { repair.Note = note; return $"Noted on {repair.Ticket}."; }

/// Set what the customer is charged.
[Action("Set the price")]
public string SetPrice(Repair repair, decimal price) { repair.Price = price; return $"{repair.Ticket} costs {price}."; }

/// Works out how long one repair takes.
public class EstimateRepair : Skill<Repair, Estimate> {
  public override string Description => "Estimate one repair.";
  public override string Instructions => "Look up the parts first. Write the estimate as a note on the repair.";
  public override List<ActionInfo> Tools => [Actions.Of(PartsFor), Actions.Of(NoteRepair), Actions.Of(SetPrice)];
  public override string Brief(Repair r) => $"Ticket {r.Ticket}: {r.Fault}. Its id is {r.Id}.";
  public override string? Check(Estimate e) => e.Hours <= 0 ? "An estimate takes some time." : null;
}

/// Estimates the repairs that come in. It holds the Helper role and nothing more.
[Actor(Roles = [ShopRole.Helper])]
public class Estimator : Agent {
  public static Person Principal => new Person { Email = "estimator@workshop.example", DisplayName = "Estimator" };
  public override string Purpose => "You estimate bicycle repairs for a small workshop.";
  public override List<SkillBase> Skills => [new EstimateRepair()];
  public override Permits May => Permits.ChangesAlone();        // it notes and prices by itself
}

/// Not one of the skill's tools.
[Action("Scrap the bike")]
public string ScrapBike(Repair repair) { repair.Delete(); return "Scrapped."; }

[TestFixture]
void Shop() {
  var mia = new Person { Email = "mia@workshop.example", DisplayName = "Mia" };
  var noor = new Person { Email = "noor@workshop.example", DisplayName = "Noor" };
  new RoleGrant { Member = mia, Role = ShopRole.Mechanic };
  new RoleGrant { Member = noor, Role = ShopRole.Manager };
  new Repair { Ticket = "T-7", Fault = "rear brake does not bite" };
  new AgentSeat { Agent = "Estimator", Sponsor = noor };          // the manager answers for the agent
}
principal Mia  => Person.Single(p => p.Email == "mia@workshop.example");
principal Noor => Person.Single(p => p.Email == "noor@workshop.example");

string RepairArgs(Repair r, string rest) { return "{\"repair\":\"" + r.Id.ToString() + "\"" + rest + "}"; }

[Test(Shop)]
[runas(Mia)]
void A_result_the_check_refuses_is_sent_back_with_the_reason() {
  var repair = Repair.Single(r => r.Ticket == "T-7");
  var script = AgentScript.Of(
    Says.HandBack(new Estimate { Hours = 0, Summary = "Quick." }),          // refused: "An estimate takes some time."
    Says.HandBack(new Estimate { Hours = 1, Summary = "New pads." }));
  ScriptedAnswer.Stub((model, request) => script.Answer(model, request));

  var outcome = await Agents.Of<Estimator>().Do(new EstimateRepair(), repair);

  Assert.True(outcome.Done);
  Assert.Equal(1, outcome.Result.Hours);
  Assert.Contains("SentBack", Trajectory.Kinds(outcome.Task));
}

[Test(Shop)]
[runas(Mia)]
void A_tool_the_skill_does_not_list_never_runs() {
  var repair = Repair.Single(r => r.Ticket == "T-7");
  var script = AgentScript.Of(
    Says.Tool(Actions.Of(ScrapBike), RepairArgs(repair, "")),
    Says.HandBack(new Estimate { Hours = 1, Summary = "Still here." }));
  ScriptedAnswer.Stub((model, request) => script.Answer(model, request));

  var outcome = await Agents.Of<Estimator>().Do(new EstimateRepair(), repair);

  Assert.True(outcome.Done);
  Assert.Equal(1, Repair.Count(r => r.Ticket == "T-7"));
  Assert.False(Trajectory.Called(outcome.Task, Actions.Of(ScrapBike)));
}

[Test(Shop)]
[runas(Mia)]
void The_catalogue_describes_a_skill_from_its_own_declaration() {
  var skill = Skills.Find("Estimator", "EstimateRepair")!;
  Assert.Equal("Estimate one repair.", skill.Description);
  Assert.Equal("Repair", skill.Takes);
  Assert.Contains("\"Hours\":{\"type\":\"number\"", skill.HandsBackSchema);
  Assert.Equal(["PartsFor", "NoteRepair", "SetPrice"], skill.Tools.Select(t => t.Action).ToList());
  Assert.True(skill.Tools[0].ReadsOnly);
  Assert.True(skill.Tools[1].Changes);
}
```

## See also       {#see-also}
- [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's `May`, `VersionOf` and `Names`: what its work proposes, and what a yes covers
- [Guards and trying again — the app's own checks on an agent's work, and what happens when work fails (Agents kit)](https://osysharp.com/reference/agent/guards/) — `Retry`: work that fails is tried again
- [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 who is in charge of it
- [Giving an agent work — Do, Ask, Stream, For and Decide (Agents kit)](https://osysharp.com/reference/agent/ask/) — giving an agent work: `Do`, `Ask`, `Stream`, `For`, `Decide`
- [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 every piece of work leaves
- [Testing an agent without a model — Says, AgentScript and Trajectory (Agents.Testing kit)](https://osysharp.com/reference/agent/testing/) — scripting the model in a test
- [[Action] — what an app offers to be done, by name](https://osysharp.com/reference/function/action/) — `[Action]`: the functions a skill's tools are
