Summary#
public class Planner : Agent {
public override Delegation Delegates => new Delegation {
To = [Specialist.Of<Collator>(new CountLeaves()), // another agent's skill
Specialist.Of<Buyer>(new PriceCloth(), budget: 0.006m)], // …held to a share of its own
};
}
var outcome = await Agents.Of<Planner>().Do(new PlanRebinding(), volume); // the planner's model hands both on
foreach (var node in WorkTree.Of(AgentTask.Single(t => t.Id == outcome.Task))) { … node.Agent, node.Status, node.TreeMicros … }Signature#
class Agent { virtual Delegation Delegates; } // said nothing, it hands nothing on
class Delegation {
List<Specialist> To; // the work it may hand on
int MaxDepth = 3; // levels of handed-on work under one piece of this agent's work
int MaxPieces = 8; // pieces one piece of its work may hand on in all
}
Specialist.Of<TAgent>(SkillBase skill, decimal? budget = null) // a skill of another of the app's agents
Specialist.Helper(SkillBase skill, decimal? budget = null) // a skill of its own, done as a piece apart
class Skill<TIn, TOut> {
virtual TIn Handed(string inputJson); // the input, from what the other agent's model wrote
virtual string HandedSchema(); // …and the JSON Schema it writes it to
string BriefHanded(string inputJson); // the brief made from it: Brief(Handed(inputJson))
}
HandOn.ById(string what) → string // the schema of "one row, by its id"
HandOn.IdIn(string inputJson) → Guid // the id such an input names
entity AgentTask { …; AgentTask? Parent; AgentTask? Root; int Depth; string Lineage; }
entity AgentProposal { …; string AskedThrough; } entity AgentHelp { …; string AskedThrough; }
entity AgentWait { …; bool OnWork; } // it waits for the work it handed on
class AgentRun { …; Guid? Parent; int Depth; List<string> Above; List<HandedOn> Legs; int HandedOnCount; }
class HandedOn { string ToolUseId; Guid Task; string Agent, Skill; } // a piece that has not come back yet
enum TraceKind { …, HandedOn, CameBack }
WorkTree.Of(AgentTask task) → List<WorkNode> // the whole tree, the top first
WorkTree.CostOf(AgentTask task) → long // what a piece and everything under it spent
WorkTree.HandedOnBy(AgentTask task) → List<AgentTask> // the pieces it handed on itself
class WorkNode { Guid Task; Guid? Parent; int Depth; string Agent, Skill, About; AgentTaskStatus Status;
string WaitingOn, Outcome; long OwnMicros, TreeMicros, LimitMicros; … }
Delegations.Of(string agent) → List<DelegationInfo> // what an agent may hand on, for a picker or a canvas
Delegations.DepthOf(string agent) → int // how many levels may hang under its work
class DelegationInfo { string From, To, Skill, Label, Description; bool Helper; decimal? Budget;
string TakesSchema, HandsBackSchema; }Description#
How does an agent hand work on?#
Its class names what it may hand on (Delegates). For each entry the model doing the work is offered a tool —
hand_to_Collator_CountLeaves — whose input is what the skill takes and whose answer is what the skill hands back,
as JSON of that type. The model decides when to use one. What it gets back is the piece's typed result, or — when
the piece did not finish — how it ended, why, and what its tools had found.
Every piece asked for in the same turn goes out together: each is opened, and their models are asked side by side, turn after turn, until each has ended or waits. Then the turn's answers go back to the model together.
A piece of handed-on work is a piece of work like any other: its own receipt (Parent, Root,
Depth), its own conversation, its own tools, its own limit, its own skill version.
What may it hand on?#
Only what its class names, to the agents it names — an agent that says nothing hands nothing on, and a hand-on its
model makes up is not one of its tools. Specialist.Of<Buyer>(new PriceCloth()) is another agent's skill, done by
that agent's own account; Specialist.Helper(new Summarise()) is one of the agent's own skills, done apart — its
own conversation and budget, the same account.
MaxDepth says how many levels may hang under a piece of the agent's work, and holds for everything under it: with
MaxDepth = 1 the specialists it hands to are offered no way to hand anything on themselves. MaxPieces bounds how
many pieces one piece of work hands on.
The declaration is read as it stands when the work is handed on, not as it stood when the tool was offered: a
Delegates that depends on something — the yard is closed — and no longer names a specialist by the time the model
asks hands nothing on, and the model is told.
Does "from now on, alone" for an agent cover what it hands on?#
Yes. A piece of handed-on work proposes whatever any agent above it would propose ([[#rights]]). A [[agent-autonomy#standing|standing rule]] a person gave an agent above it — for the work it does for that person, or for everybody — takes that agent's "propose" out of the way for what it hands on, as it does for what it does itself, and the rule counts each use. It lifts nothing else: not "never", not "propose every time", not another person's work, and not the specialist's own declaration, which needs a rule of its own. Such a rule can only be given for a tool the agent above holds itself.
Whose rights does handed-on work run with?#
A piece never holds more than the work that handed it on.
- It is done by the specialist's own account — the roles its class names ([Actor] — a principal that code may run as) — for the person the work at the top is for, and nobody else: that is read from the receipt above it, not from whoever happens to be signed in when it is opened. Its tools change only what both may ([[agent-ask#for]]).
- Its gate is the strictest of its own and of every piece of work above it. A specialist that marks things by itself in its own work proposes the same change to a person when the agent that handed it the work would have to; and what any piece of work above it never does is not offered to it, not run, and not approvable.
- It spends out of what the work above it has left ([[#money]]).
What it does not inherit is the other agent's roles: a specialist keeps its own authority — the collator may read what the planner may not — which is what makes it a specialist.
What does it cost, and who is held to what?#
A piece is opened with the least of: its share (budget:), what its own skill and agent say, and what the work
above it has left at that moment. With nothing left, nothing is handed on.
Every model call a piece makes is charged to its own receipt and to every piece of work above it — and to
whatever the top was charged to with [[agent-budgets#on|On]] — and room is reserved against each of those
limits before the call leaves. So a tree can never spend more than its top may, however many pieces run at once.
AgentTask.SpentMicros is what a piece spent itself; WorkTree.CostOf(task) and WorkNode.TreeMicros are a
piece with everything under it.
What when a piece must ask a person?#
It asks whoever the work at the top would ask: the person that work is for; the sponsor of the agent at the top
when it is for nobody ([[agent-people#who]]). The proposal or question says through whom it comes —
AskedThrough: "Collator, working for Planner" — so the person sees who is asking and for what.
The piece waits; so does the piece that handed it on, and the one above that: each receipt says what it waits for ("Collator, which waits for a decision from Mia"). Nothing runs and nothing is spent. When the person has decided, each goes on from where it stood, bottom up — as the app, in runs of their own. A yes runs the proposed call as whoever said yes and as the piece that proposed it, for exactly what was shown ([[agent-autonomy#proposal]]).
What when a piece fails, is stopped or runs out?#
- It fails, or is stopped: the model above is told how it ended, why, and what its tools had found — and can go
on without it, hand the work to somebody else, or hand back what it has and say what is missing. A piece whose
skill says
Retryis tried again first ([[agent-guards#retry]]). The pieces beside it are not touched. - Its money runs out: the same. When the limit that ran out is a piece of work's above it, everything under that piece is called off with it — a piece waiting for a person is not left waiting in vain.
- The work above it ends, or is called off (
StopAgentTask): every piece under it is called off — each at its own next step, and a waiting one at once; a proposal nobody decided is withdrawn, an open question closed.
Whatever the ending, nothing of a tree is left going once its top has ended.
What when the process doing the work dies?#
Nothing runs to say so: the receipts go on saying Working. Every model answer and every tool is a line on the
record of the whole, so a whole with a piece under way and no new line for AgentsSetup.AbandonedAfter (30
minutes; longer than the slowest tool an agent runs; zero: never) was abandoned — WasAbandoned(task).
- The kit closes it by itself. Every
AgentsSetup.AbandonedCheckEvery(ten minutes; zero: never) the kit's own clock looks, and closes every piece that was abandoned:Failed, "It stopped without reporting back" — orCancelledwhen somebody had called it off. What it had handed on goes with it: a piece that waited for a person is called off and its proposal withdrawn; a piece above that waited for it is told, and goes on. Only the top is said to have given up. The clock starts with the first piece of work; the app schedules nothing. CloseAbandonedAgentWork()is the same look, at once, for whoever supervises the agents — from a page, or from the app's own schedule when it setAbandonedCheckEveryto zero. It answers how many it found.StopAgentTaskon abandoned work closes it, and what is under way beneath it, at once — it has no next step to stop at.- A process that was only slow, and comes back after its work was closed, reads that on its next turn and does nothing more in the work's name: no model is asked, no tool run, nothing handed on.
- Work that waits for a person is never abandoned by waiting, however long that takes.
app.Agents = new AgentsSetup {
Agents = [new Planner()],
AbandonedAfter = TimeSpan.FromMinutes(30), // quiet this long, and nobody is doing it
AbandonedCheckEvery = TimeSpan.FromMinutes(10), // how often the kit looks; TimeSpan.Zero: the app closes it itself
};The last example on this page stages a process dying and lets the clock close what it left.
How is a row handed on?#
What a model writes is JSON. A skill that takes a class of plain values, or text, needs to say nothing. A skill that takes one of the app's rows says how the row is found — the model names it by its id:
public class CountLeaves : Skill<Volume, LeafCount> {
public override string HandedSchema() => HandOn.ById("The volume to count.");
public override Volume Handed(string inputJson) {
var id = HandOn.IdIn(inputJson);
return Volume.Single(v => v.Id == id);
}
}Handed runs as the specialist, for the person the work is for: a row they may not read is not found, and the work
is not handed on.
How do I read the tree?#
WorkTree.Of(task) answers every piece of the work task is part of — the top first, each piece followed by what
it handed on — with who it is, where it stands, what it waits for and what it cost. The record
of a tree is one chain: TraceOf and VerifyTrace answer the same lines from any piece of it, with a
HandedOn line where a piece was handed on and a CameBack line where it came back. Who may read a piece is who
may read the top: the person it is for, whoever gave it, and whoever supervises or audits the agents.
How do I test it?#
AgentCast gives each agent its own script ([[agent-testing#cast]]); Says.HandTo<T>(skill, inputJson) hands a
piece on, Says.Together(…) puts several in one turn, and Trajectory.Tree(task) reads the tree back.
What it does not do yet#
Handing on is the model's to do, inside a piece of work. Your own code can start two pieces of work side by
side — var a = x.Do(…); var b = y.Do(…); — and each then runs with its tools ([[agent-ask#awaitable]]). But they
are two separate pieces of work: neither is under the other, they share no limit, and no receipt gathers them. A
coordinator directed step by step from code, with its pieces under its own receipt, is not there; for that, give the
work to an agent that hands it on.
Examples#
app Bindery {
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;
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 Planner(), new Collator(), new Buyer()], TaskBudget = 0.05m };
[Role] enum BinderyRole { Authenticator, Binder, Master, Helper }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsBinder => RoleGrant.Any(g => g.Member == user && g.Role == BinderyRole.Binder);
policy IsMaster => RoleGrant.Any(g => g.Member == user && g.Role == BinderyRole.Master);
policy IsHelper => RoleGrant.Any(g => g.Member == user && g.Role == BinderyRole.Helper);
policy UsesAgents => IsBinder || IsMaster;
policy SupervisesAgents => IsMaster;
policy AuditsAgents => IsMaster;
policy ReadsModelUsage => IsMaster;
policy ManagesModelSpend => IsMaster;
entity Person : Principal {
security { allow read when IsAuthenticated || IsAuthenticator; allow create when IsAuthenticator; }
}
/// One book in for rebinding.
entity Volume {
[Required, Unique, MaxLength(40)] string Shelfmark;
int Leaves = 0;
[MaxLength(400)] string Note = "";
security { allow read, create, update when IsBinder || IsMaster || IsHelper; }
}
/// What the planner hands back.
public class Plan {
public int Leaves;
/// What the cloth will cost, in euros — 0 when it could not be priced.
public decimal Cloth;
public string Summary = "";
}
/// What the collator hands back, and what the buyer is given and hands back.
public class LeafCount { public int Leaves; public string Finding = ""; }
public class ClothAsk { public string Colour = ""; public decimal Metres; }
public class ClothPrice { public decimal Euros; }
[Action("Write a note on the volume")]
public string NoteOnVolume(Volume volume, string note) { volume.Note = note; return $"Noted on {volume.Shelfmark}."; }
[Action("Look up the price of cloth", Query)]
public decimal PricePerMetre(string colour) { return colour == "oxblood" ? 14.5m : 11m; }
/// Counts a volume's leaves. It takes a ROW: handed on by another agent, the row is named by its id.
public class CountLeaves : Skill<Volume, LeafCount> {
public override string Description => "Count the leaves of one volume, and note anything missing.";
public override List<ActionInfo> Tools => [Actions.Of(NoteOnVolume)];
public override string Brief(Volume v) => $"Volume {v.Shelfmark}. Its id is {v.Id}.";
public override string HandedSchema() => HandOn.ById("The volume to count.");
public override Volume Handed(string inputJson) {
var id = HandOn.IdIn(inputJson);
return Volume.Single(v => v.Id == id);
}
}
/// Prices the cloth. It takes a class of plain values: nothing more to say.
public class PriceCloth : Skill<ClothAsk, ClothPrice> {
public override string Description => "Price the cloth for one binding.";
public override List<ActionInfo> Tools => [Actions.Of(PricePerMetre)];
}
public class PlanRebinding : Skill<Volume, Plan> {
public override string Description => "Plan the rebinding of one volume: have its leaves counted and its cloth priced.";
public override string Brief(Volume v) => $"Volume {v.Shelfmark}. Its id is {v.Id}.";
}
/// Says nothing of what it may do alone: it proposes every change.
[Actor(Roles = [BinderyRole.Helper])]
public class Collator : Agent {
public static Person Principal => new Person { Email = "[email protected]", DisplayName = "Collator" };
public override string Purpose => "You count and check the leaves of books.";
public override List<SkillBase> Skills => [new CountLeaves()];
}
[Actor(Roles = [BinderyRole.Helper])]
public class Buyer : Agent {
public static Person Principal => new Person { Email = "[email protected]", DisplayName = "Buyer" };
public override string Purpose => "You price materials.";
public override List<SkillBase> Skills => [new PriceCloth()];
}
/// ⭐ The planner hands two pieces of its work on: the count to the collator, the price to the buyer — and gives
/// the pricing a share of its own.
[Actor(Roles = [BinderyRole.Helper])]
public class Planner : Agent {
public static Person Principal => new Person { Email = "[email protected]", DisplayName = "Planner" };
public override string Purpose => "You plan the rebinding of books.";
public override List<SkillBase> Skills => [new PlanRebinding()];
public override Permits May => Permits.ChangesAlone();
public override Delegation Delegates => new Delegation {
To = [Specialist.Of<Collator>(new CountLeaves()), Specialist.Of<Buyer>(new PriceCloth(), budget: 0.006m)],
};
}
[TestFixture]
void Shop() {
var mia = new Person { Email = "[email protected]", DisplayName = "Mia" };
var noor = new Person { Email = "[email protected]", DisplayName = "Noor" };
new RoleGrant { Member = mia, Role = BinderyRole.Binder };
new RoleGrant { Member = noor, Role = BinderyRole.Master };
new Volume { Shelfmark = "MS-12", Leaves = 0 };
new AgentSeat { Agent = "Planner", Sponsor = noor };
new AgentSeat { Agent = "Collator", Sponsor = noor };
new AgentSeat { Agent = "Buyer", Sponsor = noor };
}
principal Mia => Person.Single(p => p.Email == "[email protected]");
principal Noor => Person.Single(p => p.Email == "[email protected]");
/// What the planner's model says: both pieces in ONE turn — so they are done side by side — then the plan.
AgentScript Plans(Volume v, Plan plan) {
return AgentScript.Of(
Says.Together(
Says.HandTo<Collator>(new CountLeaves(), Says.Row(v.Id)),
Says.HandTo<Buyer>(new PriceCloth(), JsonSerializer.Serialize(new ClothAsk { Colour = "oxblood", Metres = 2 }))),
Says.HandBack(plan));
}
/// Let whatever waits look again: a wait whose person has decided goes on, and the work above it follows.
void LetItGoOn() {
for (var i = 0; i < 4; i++) {
Background.Settle();
foreach (var wait in AgentWait.Where(w => w.Status == AgentWaitStatus.Waiting).ToList()) { Workflow.Settle(wait); }
}
}
[Test(Shop)]
[runas(Mia)]
void It_hands_two_pieces_on_at_once_and_gathers_what_comes_back() {
var volume = Volume.Single(v => v.Shelfmark == "MS-12");
var cast = AgentCast.Of()
.Plays<Planner>(Plans(volume, new Plan { Leaves = 212, Cloth = 29, Summary = "212 leaves; two metres of oxblood." }))
.Plays<Collator>(AgentScript.Of(Says.HandBack(new LeafCount { Leaves = 212, Finding = "Complete." })))
.Plays<Buyer>(AgentScript.Of(
Says.Tool(Actions.Of(PricePerMetre), "{\"colour\":\"oxblood\"}"),
Says.HandBack(new ClothPrice { Euros = 29 })));
ScriptedAnswer.Stub((model, request) => cast.Answer(model, request));
var outcome = await Agents.Of<Planner>().Do(new PlanRebinding(), volume, about: "Rebinding MS-12");
// Typed out of each piece, typed out of the whole.
Assert.True(outcome.Done);
Assert.Equal(212, outcome.Result.Leaves);
Assert.Equal(29m, outcome.Result.Cloth);
// One receipt for each piece, under the planner's — for the same person, about the same thing.
Assert.Equal(["Planner · Plan rebinding: Done", " Collator · Count leaves: Done", " Buyer · Price cloth: Done"], Trajectory.Tree(outcome.Task));
var pieces = WorkTree.HandedOnBy(AgentTask.Single(t => t.Id == outcome.Task));
Assert.Equal(Mia.Id, pieces[0].Requester!.Id);
Assert.Equal("Rebinding MS-12", pieces[1].About);
// The buyer's piece was opened with its share; and the whole costs what its pieces cost.
Assert.Equal(6000, pieces[1].LimitMicros);
Assert.Equal(400 + 200 + 400, Trajectory.TreeCost(outcome.Task));
Assert.Equal(0.001m, SpentAgainst("task:" + outcome.Task.ToString()));
// One record for the tree, and it checks.
Assert.True(VerifyTrace(pieces[0]).Intact);
}
[Test(Shop)]
void A_piece_that_must_ask_a_person_makes_the_whole_chain_wait() {
TestClock.Set(new DateTime(2031, 3, 3, 9, 0, 0, DateTimeKind.Utc));
Guid task;
runas (Mia) {
var volume = Volume.Single(v => v.Shelfmark == "MS-12");
var cast = AgentCast.Of()
.Plays<Planner>(Plans(volume, new Plan { Leaves = 210, Cloth = 29, Summary = "210 leaves, two missing; noted." }))
// The collator may change nothing alone: its note is proposed.
.Plays<Collator>(AgentScript.Of(
Says.Tool("Leaves 40 and 41 are missing.", Actions.Of(NoteOnVolume), "{\"volume\":\"" + volume.Id.ToString() + "\",\"note\":\"Leaves 40-41 missing\"}"),
Says.HandBack(new LeafCount { Leaves = 210, Finding = "Two missing." })))
.Plays<Buyer>(AgentScript.Of(Says.HandBack(new ClothPrice { Euros = 29 })));
ScriptedAnswer.Stub((model, request) => cast.Answer(model, request));
var outcome = await Agents.Of<Planner>().Do(new PlanRebinding(), volume, about: "Rebinding MS-12");
task = outcome.Task;
// The buyer finished. The collator waits for Mia — and the planner for the collator.
Assert.True(outcome.Waiting);
Assert.Equal(["Planner · Plan rebinding: Waiting", " Collator · Count leaves: Waiting", " Buyer · Price cloth: Done"], Trajectory.Tree(task));
Assert.Equal("Collator, which waits for a decision from Mia", AgentTask.Single(t => t.Id == task).WaitingOn);
// It is put to the person the work at the top is for, and says through whom it comes.
var proposal = ProposalsForMe().Single();
Assert.Equal("Collator, working for Planner", proposal.AskedThrough);
Assert.Equal("", Volume.Single().Note);
}
// A week. Nothing runs, and no model is asked.
TestClock.Advance(TimeSpan.FromDays(7));
LetItGoOn();
runas (Mia) {
Assert.Equal(3, AgentTask.ToList().Sum(t => t.ModelCalls));
Assert.Equal(ProposalStatus.Done, ApproveAgentProposal(ProposalsForMe().Single()).Status);
}
LetItGoOn();
runas (Mia) {
// The note was written as Mia and as the collator; each piece went on from where it stood.
Assert.Equal("Leaves 40-41 missing", Volume.Single().Note);
Assert.Equal(["Planner · Plan rebinding: Done", " Collator · Count leaves: Done", " Buyer · Price cloth: Done"], Trajectory.Tree(task));
Assert.Contains("210 leaves, two missing; noted.", AgentTask.Single(t => t.Id == task).Result!);
Assert.Equal(0, Trajectory.StillGoing(task).Count);
}
}
[Test(Shop)]
[runas(Mia)]
void A_piece_that_runs_out_of_money_is_told_to_the_work_above_which_carries_on() {
var volume = Volume.Single(v => v.Shelfmark == "MS-12");
var cast = AgentCast.Of()
.Plays<Planner>(Plans(volume, new Plan { Leaves = 212, Cloth = 0, Summary = "212 leaves. The cloth could not be priced: the pricing ran out of money." }))
.Plays<Collator>(AgentScript.Of(Says.HandBack(new LeafCount { Leaves = 212 })))
// The buyer's first answer is a long one — 1000 tokens in, 600 out: 4000 of its 6000. A second call does not fit.
.Plays<Buyer>(AgentScript.Of(
Says.Tool(Actions.Of(PricePerMetre), "{\"colour\":\"oxblood\"}", 1000, 600),
Says.HandBack(new ClothPrice { Euros = 29 })));
ScriptedAnswer.Stub((model, request) => cast.Answer(model, request));
var outcome = await Agents.Of<Planner>().Do(new PlanRebinding(), volume);
// The piece was stopped at its own share; the piece beside it was not touched; the planner was told how it ended
// and what it had found, and handed back what it had.
Assert.Equal(["Planner · Plan rebinding: Done", " Collator · Count leaves: Done", " Buyer · Price cloth: Stopped"], Trajectory.Tree(outcome.Task));
Assert.True(Trajectory.Said(outcome.Task).Any(s => s.StartsWith("Buyer was stopped: Its money ran out:")));
var told = TraceEvent.Single(e => e.Task.Id == outcome.Task && e.Kind == TraceKind.CameBack && e.Summary.StartsWith("Buyer was stopped"));
Assert.Contains("What it had found: PricePerMetre answered 14.5", told.Payload!);
Assert.True(outcome.Done);
Assert.Equal(0m, outcome.Result.Cloth);
}
/// ⭐ The process doing the work dies — here the buyer's second model call takes the process with it, so nothing
/// writes down how the work ended. Nobody has to notice: the kit's own clock finds the work quiet for half an hour
/// and closes it, with everything it had handed on.
[Test(Shop)]
void Work_whose_process_died_is_closed_by_the_kit_itself() {
TestClock.Set(new DateTime(2031, 3, 3, 9, 0, 0, DateTimeKind.Utc));
Guid task = Guid.Empty;
runas (Mia) {
var volume = Volume.Single(v => v.Shelfmark == "MS-12");
var cast = AgentCast.Of()
.Plays<Planner>(Plans(volume, new Plan { Leaves = 212 }))
.Plays<Collator>(AgentScript.Of(Says.HandBack(new LeafCount { Leaves = 212 })))
.Plays<Buyer>(AgentScript.Of(
Says.Tool(Actions.Of(PricePerMetre), "{\"colour\":\"oxblood\"}"),
Says.HandBack(new ClothPrice { Euros = 29 })));
ScriptedAnswer.Stub((model, request) => {
if (AgentCast.AskedAgent(request) == "Buyer" && request.Messages.Count(m => m.Role == "assistant") >= 1) {
throw new InvalidOperationException("The process died.");
}
return cast.Answer(model, request);
});
Assert.Throws<InvalidOperationException>(() => await Agents.Of<Planner>().Do(new PlanRebinding(), volume));
}
runas (Mia) {
task = AgentTask.Single(t => t.Parent == null).Id;
// What a crash leaves: receipts that go on saying the work is under way.
Assert.Equal(["Planner · Plan rebinding: Working", " Collator · Count leaves: Done", " Buyer · Price cloth: Working"],
Trajectory.Tree(task));
}
// Forty minutes with nothing written on its record: the kit's own clock has closed it. Nobody asked.
TestClock.Advance(TimeSpan.FromMinutes(40));
LetItGoOn();
runas (Mia) {
Assert.Equal(["Planner · Plan rebinding: Failed", " Collator · Count leaves: Done", " Buyer · Price cloth: Failed"],
Trajectory.Tree(task));
Assert.Equal("It stopped without reporting back: whatever was doing it is gone.", AgentTask.Single(t => t.Id == task).Outcome);
Assert.Equal(0, Trajectory.StillGoing(task).Count);
}
}See also#
- Writing an agent — a class with a purpose, skills and an account of its own (Agents kit) — an agent, whole
- What an agent does alone, what it proposes, what it never does — the gate and proposals (Agents kit) — the gate, and proposals
- An agent asks a person, and waits — the person rung of the ladder of help (Agents kit) — asking a person, and waiting
- What an agent may spend — a hard limit on every piece of work (Agents kit) — limits, and what work is charged to
- The receipt and the record — what every piece of an agent's work leaves (Agents kit) — the receipt and the record
- Testing an agent without a model — Says, AgentScript and Trajectory (Agents.Testing kit) — scripting the models