Osy#betaRun it on Osyrin: osyrin.com

Reference / Agent

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

CorrectAgentWork(task, new Correction { Wrong, Right, Because, Step, Proposal, Despite }) · ProposeAgentInstruction(new InstructionDraft { Agent, Skill, For, OnScope, Guidance, Why, From }) · DraftAgentInstruction(corrections) · ActivateAgentInstruction(instruction, why) · RejectAgentInstruction(instruction, why) · ReviseAgentInstruction(instruction, guidance, why) · RetireAgentInstruction(instruction, why) · RollBackAgentInstructions(agent, toSet, why, skill:) · InstructionsOf(task) · EffectOf(instruction) · AgentTask.InstructionSet · policy TeachesAgents

An agent gets better through people. Somebody who sees a piece of its work corrects it — what was wrong, what right looks like, why — on that exact piece of work, step or proposal. An instruction is drawn from corrections by a person, by a model, or by the agent itself, and is only PROPOSED: no model is sent a word of it until a signed-in person the app's TeachesAgents admits makes it active. Every activation, retirement and rollback is a numbered instruction set with who, when and why. A piece of work records the set it started under and exactly what it was sent; a proposal's fingerprint covers the set; and an agent can be put back to an earlier set.

preview1 example compiled by CIagentskitsecurity

Summary#

var correction = CorrectAgentWork(task, new Correction {          // on the work it is about
  Wrong = "The tag gives only the Latin name.", Right = "The common name first.", Because = "Customers ask for lavender.",
});
var proposed = ProposeAgentInstruction(new InstructionDraft {     // PROPOSED: no model is sent it
  Agent = Agents.Of<Tagger>().Name, Guidance = "Put the common name first.", Why = "Customers ask by it.", From = [correction.Id],
});
var set = ActivateAgentInstruction(proposed, "Agreed.");          // a person who teaches: now it is sent
// task.InstructionSet, task.InstructionsSent, task.InstructionsHash   — what a piece of work ran with
RollBackAgentInstructions(Agents.Of<Tagger>().Name, 0, "Start again.");   // back to an earlier set

Signature#

virtual policy TeachesAgents => false;             // who activates, rejects, revises, retires and rolls back

CorrectAgentWork(AgentTask task, Correction correction) → AgentCorrection
WithdrawAgentCorrection(AgentCorrection correction, string why)
class Correction { string Wrong, Right, Because; string? CorrectedOutput; CorrectionSeverity Severity;
                   int Step; Guid? Proposal; Guid? Despite; }
enum CorrectionSeverity { Minor, Mistake, Serious }

ProposeAgentInstruction(InstructionDraft draft) → AgentInstruction                      // always Proposed
await DraftAgentInstruction(List<AgentCorrection> corrections, Guid? forPerson = null, string onScope = "") → AgentInstruction
class InstructionDraft { string Agent, Skill; Guid? For; string OnScope; string Guidance, Why; List<Guid> From; }
class Agent { virtual bool SuggestsInstructions => false; }   // the model may propose one: `suggest_instruction`

ReviseAgentInstruction(AgentInstruction instruction, string guidance, string why)       // while it is proposed
ActivateAgentInstruction(AgentInstruction instruction, string why = "")  → AgentInstructionSet
RejectAgentInstruction(AgentInstruction instruction, string why)
RetireAgentInstruction(AgentInstruction instruction, string why)         → AgentInstructionSet
RollBackAgentInstructions(string agent, int toSet, string why, string skill = "") → AgentInstructionSet

entity AgentInstruction    { string Agent, Skill; Principal? For; string OnScope; string Guidance, Why;
                             InstructionState State;          // Proposed, Active, Rejected, Retired
                             Principal? ProposedBy; string ProposedByAgent, DraftedBy; int DrawnFrom;
                             Principal? ReviewedBy; DateTime? ActiveSince; Principal? RetiredBy; string RetiredWhy; }
entity AgentInstructionSet { string Agent; int Number; InstructionChange Change; AgentInstruction? Instruction;
                             int RestoredFrom; int Count; string Members; string Hash; Principal? By; DateTime At; string Why; }
entity AgentTask           { …; int InstructionSet; string InstructionsSent; string InstructionsHash; }

InstructionsOf(AgentTask task) → RanWith           // Set, Sent, NotYet, RetiredSince, Words
EffectOf(AgentInstruction instruction) → InstructionEffect      // DrawnFrom, WorkSince, CorrectedSince, NotFollowed
InstructionSetOf(string agent) → int · InstructionSetsOf(agent) · InstructionsOfAgent(agent) · SourcesOf(instruction)
CorrectionsOf(task) · CorrectionsOfAgent(agent, skill:)

app.AgentTeaching = new TeachingSetup { MostInstructions = 20, MostCharacters = 6000, DraftsWith = "" };
// events: AgentWorkCorrected · AgentInstructionProposed · AgentInstructionsChanged

Description#

What is a correction?#

A record on one piece of work: what was wrong, what it should have been, and why — all three, because the reason is what lets the next piece of work come out right and not only this one. It is about the result, about one step of the record (Step, the line's place), or about something the work proposed (Proposal); it names who wrote it and how much it mattered, and can carry the right result (CorrectedOutput). What it says is never changed afterwards: its writer can take it back (WithdrawAgentCorrection), and it can be erased (Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request).

Whoever the work was for, whoever gave it, whoever a proposal of it was put to, and whoever supervises or teaches the agents may correct it. Somebody who can merely read the work — an auditor — may not.

A correction changes nothing the agent does. It is what an instruction is drawn from.

What is an instruction, and when is the agent sent it?#

Standing guidance, in the words the model will be sent (Guidance, at most 600 characters) with the reason for the people who read it (Why — not sent). It is for everything the agent does, for one skill (Skill), for the work done for one person (For), or for work charged to one thing (OnScope = "team:" + team.Id).

Whoever proposes it, it is Proposed:

proposed byhow
a person, from a correction they madeProposeAgentInstruction with From
whoever teaches or supervises, outrightProposeAgentInstruction
a model, from one or several correctionsawait DraftAgentInstruction(corrections) — charged to the agent
the agent itself, while it worksits class says SuggestsInstructions => true; the model calls suggest_instruction

⛔ Nothing is sent to any model until a person makes it active. ActivateAgentInstruction is the only way an instruction becomes Active, and it runs only for a signed-in person the app's TeachesAgents admits. An agent's own account is refused whatever roles it holds — also when its tool runs on behalf of a teacher — and so is code that runs as the app (a workflow's body, an event's handler). Writing State any other way is refused by the entity itself. That is the whole defence against a page an agent read planting a rule every later run obeys.

It is words to the model, not leave: what the agent may do alone is still its class's May.

In what order, and how much?#

A piece of work is sent, last in its system prompt — after who the agent is and what the job is — the active instructions that apply to it: those for everything the agent does first, then the skill's, then those for what the work is charged to, then those for the person it is for; within each, in the order they were made active. The most specific has the last word.

Never more than TeachingSetup allows (20 instructions and 6,000 characters by default). An instruction that would take the work it is for past that is refused where somebody tries to make it active; and where more still apply — instructions for a person and for a skill meeting in one piece of work — the least specific are left out and the record's first line says how many.

What does a piece of work record?#

The receipt records, as the work starts and for good: the skill's version (SkillVersion), the agent's instruction set by its number (InstructionSet; 0 before anybody taught it anything), the instructions this work was sent (InstructionsSent, their ids in order) and the SHA-256 of exactly the text the model was handed (InstructionsHash). The record's first line says it in words. InstructionsOf(task) reads it back — with what the agent has been told since that this work was not — so a past result can be explained.

A [[agent-autonomy#approve|proposal's fingerprint]] covers the set too: a yes to something the agent proposed under one set does not carry to another. When an instruction has been made active, retired or rolled back before the person decides, nothing runs; the work takes up the instructions as they stand and proposes again.

What is an instruction set, and how do I go back?#

Every activation, every retirement and every rollback makes the agent's next set: its number, what changed, who made the change, when and why, which instructions were active after it, and a hash of them — the same instructions give the same hash, so an agent put back to an earlier set can be seen to be on it.

RetireAgentInstruction takes one out of use. RollBackAgentInstructions(agent, toSet, why) makes what was active in set toSet active again and retires what came since (0: as before anybody taught it; skill: for one skill's own instructions alone). Both need a reason, and both are for whoever teaches.

Work already under way finishes under the set it started with — work that waits a week for a person goes on told what it was told. The one exception is the one above: a yes refused because the set changed.

Did it help?#

EffectOf(instruction) counts, without asking any model: the corrections it was drawn from, the pieces of work that were sent it, how many of those people still corrected, and how many corrections name it as the instruction the work did not follow (Correction.Despite). The work is counted over what whoever is looking may read — whoever teaches, supervises or audits sees all of it.

Examples#

app Nursery {
  use Osysharp.Agents;
  use Osysharp.Agents.Testing;
  use Osysharp.Llm;
  use Osysharp.Identity;
  use Osysharp.Observability;
  use Osysharp.Workflow;
  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 } ],
  Default = "Everyday",
};
app.Agents = new AgentsSetup { Agents = [new Tagger()], TaskBudget = 0.05m };

[Role] enum NurseryRole { Authenticator, Grower, Head, Tags }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsGrower => RoleGrant.Any(g => g.Member == user && g.Role == NurseryRole.Grower);
policy IsHead   => RoleGrant.Any(g => g.Member == user && g.Role == NurseryRole.Head);

policy UsesAgents       => IsGrower || IsHead;
policy SupervisesAgents => IsHead;
policy AuditsAgents     => IsHead;
policy TeachesAgents    => IsHead;                 // who makes an instruction active, retires it, rolls back

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

/// What the tagger hands back: the words on one plant's tag.
public class Tag { public string Words = ""; }

/// Writes the tag that hangs on a plant.
public class WriteTag : Skill<string, Tag> {
  public override string Description => "Write the tag for one plant.";
  public override string Version => "2";
}

[Actor(Roles = [NurseryRole.Tags])]
public class Tagger : Agent {
  public static Person Principal => new Person { Email = "[email protected]", DisplayName = "Tagger" };
  public override string Purpose => "You write the tags that hang on the plants of a small nursery.";
  public override List<SkillBase> Skills => [new WriteTag()];
}

/// What the scripted model was sent beyond who it is and its job, for the tests to read.
entity Sent {
  [Required, MaxLength(40)] string Run;
  [MaxLength(2000)] string Words = "";
  security { allow read, create when IsAuthenticated || IsAnonymous; }
}

[TestFixture]
void Beds() {
  var gus = new Person { Email = "[email protected]", DisplayName = "Gus" };
  var hale = new Person { Email = "[email protected]", DisplayName = "Hale" };
  new RoleGrant { Member = gus, Role = NurseryRole.Grower };
  new RoleGrant { Member = hale, Role = NurseryRole.Head };
  new AgentSeat { Agent = "Tagger", Sponsor = hale };
}
principal Gus  => Person.Single(p => p.Email == "[email protected]");
principal Hale => Person.Single(p => p.Email == "[email protected]");

/// Have the tagger write the lavender's tag, for whoever is signed in.
AgentOutcome<Tag> TagLavender() { return await Agents.Of<Tagger>().Do(new WriteTag(), "Lavandula angustifolia, 2 litre pot"); }
/// What the model was sent on its n-th run.
string SentIn(int run) { return Sent.Single(s => s.Run == "run " + run.ToString()).Words; }

[Test(Beds)]
void A_correction_becomes_an_instruction_only_when_a_person_activates_it() {
  // ⭐ The model is scripted by what it is SENT: the Latin name alone, unless its instructions say otherwise. Each
  //   run notes the third part of its system prompt — what people have told the agent since it was written.
  ScriptedAnswer.Stub((model, request) => {
    var told = request.SystemSegments.Count > 2 ? request.SystemSegments[2].Content : "";
    new Sent { Run = "run " + (Sent.Count() + 1).ToString(), Words = told };
    return Says.HandBack(new Tag { Words = told.Contains("common name first") ? "Lavender (Lavandula angustifolia)" : "Lavandula angustifolia" });
  });
  Guid first = Guid.Empty;
  Guid proposed = Guid.Empty;
  runas (Gus) {
    // The mistake.
    var wrong = TagLavender();
    first = wrong.Task;
    Assert.Equal("Lavandula angustifolia", wrong.Result.Words);
    // Gus corrects it, and proposes what the tagger should be told. It is PROPOSED.
    var correction = CorrectAgentWork(AgentTask.Single(t => t.Id == first), new Correction {
      Wrong = "The tag gives only the Latin name.", Right = "Lavender (Lavandula angustifolia).", Because = "Customers ask for lavender.",
    });
    var instruction = ProposeAgentInstruction(new InstructionDraft {
      Agent = "Tagger", Guidance = "Put the common name first, the Latin name after it in brackets.",
      Why = "Customers ask by the common name.", From = [correction.Id],
    });
    proposed = instruction.Id;
    Assert.Equal(InstructionState.Proposed, instruction.State);
    UnitOfWork.Commit();
    // Nothing has changed: the model is sent nothing new, and writes the same tag.
    Assert.Equal("Lavandula angustifolia", TagLavender().Result.Words);
    Assert.Equal("", SentIn(2));
    // A grower proposes; a grower does not activate.
    Assert.Denied(() => ActivateAgentInstruction(instruction));
  }
  // The head of the nursery reads it and makes it active: the tagger's instruction set 1.
  runas (Hale) {
    var set = ActivateAgentInstruction(AgentInstruction.Single(i => i.Id == proposed), "That is how we tag.");
    Assert.Equal(1, set.Number);
    Assert.Equal(Hale.Id, set.By!.Id);
    UnitOfWork.Commit();
  }
  runas (Gus) {
    // Now the model IS sent it, and the tag is right.
    var right = TagLavender();
    Assert.Equal("Lavender (Lavandula angustifolia)", right.Result.Words);
    Assert.EndsWith("1. Put the common name first, the Latin name after it in brackets.", SentIn(3));
    // The work records what it ran with: the skill's version, the set, and the hash of exactly what it was sent.
    var receipt = AgentTask.Single(t => t.Id == right.Task);
    Assert.Equal("2", receipt.SkillVersion);
    Assert.Equal(1, receipt.InstructionSet);
    Assert.Equal(proposed.ToString(), receipt.InstructionsSent);
    Assert.Equal(Crypto.Sha256Hex(SentIn(3)), receipt.InstructionsHash);
    // …so the first tag can be explained.
    Assert.Equal("It ran before anybody had taught Tagger anything. Since then 1 has been made active that it had not been sent.",
                 InstructionsOf(AgentTask.Single(t => t.Id == first)).Words);
  }
  // Back to how it was: a rollback is the tagger's next set, and says which it restored.
  runas (Hale) {
    var back = RollBackAgentInstructions("Tagger", 0, "The tags are being redesigned.");
    Assert.Equal(2, back.Number);
    Assert.Equal(0, back.RestoredFrom);
    UnitOfWork.Commit();
  }
  runas (Gus) {
    Assert.Equal("Lavandula angustifolia", TagLavender().Result.Words);
    Assert.Equal("", SentIn(4));
  }
}

See also#

Related

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…

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…

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…

Writing an agent — a class with a purpose, skills and an account of its own (Agents kit)

An agent is a class of the `Osysharp.Agents` kit: what it is for, the skills it holds, what it may spend. It runs as an…

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:…

Testing an agent without a model — Says, AgentScript and Trajectory (Agents.Testing kit)

`Osysharp.Agents.Testing` scripts what the model answers, turn by turn, and reads back what the agent then did. Only…

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…