You are here: Building with agents › Deciding what to build › The six decisions
Summary#
Read this page once before you write an agent, and again when you review one. Each decision is a question, the answer you get if you say nothing, and the line you write to say otherwise.
| # | The question | If you say nothing | You say otherwise with |
|---|---|---|---|
| 1 | Who does it act as, and for whom? | its own account, for whoever is signed in | [Actor(Roles = […])], .For(person) |
| 2 | What may it do alone? | only what reads; everything else is proposed | May, a skill's DecidedBy |
| 3 | What may it spend? | fifty cents for one piece of work | TaskBudget, Limits, Budget, SetBudget |
| 4 | Who answers its questions? | it asks nobody | People, Consults |
| 5 | Who is in charge of it? | nobody: it does not run until somebody answers for it | the policies, AgentSeat |
| 6 | What is kept, and for how long? | what it said for a month, that it happened for seven | the Privacy kits |
Description#
1. Who does it act as, and for whom?#
[Actor(Roles = [OfficeRole.Vetting])] // the account it acts as holds exactly these roles
public class Vetter : Agent { … }
await Agents.Of<Vetter>().Do(skill, input); // for whoever is signed in
await Agents.Of<Vetter>().For(buyer).Do(skill, input); // for somebody else — a process step, a batch- The agent is an account of the app's, with the roles its class names and no others.
- Working for a person, it reads what both may read and changes what both may change. It can never do for somebody what that person may not do themselves.
- In a process, where nobody is signed in, it works with its own roles alone, unless the step says
.For(person). - Giving work for somebody else while signed in yourself needs the policy
DirectsAgentsForOthers.
More: [[agent-agents#rights]], [[agent-ask#for]], [Actor] — a principal that code may run as.
2. What may it do alone, what does it propose, what does it never do?#
public override Permits May => new Permits {
Alone = [Actions.Of(NoteRisk)], // by itself
ProposeEveryTime = [Actions.Of(ApproveSupplier)], // a person, every time: no standing rule
Never = [Actions.Of(DeleteSupplier)], // not offered to the model at all
};- Said nothing, a tool that only reads runs alone, and every tool that changes, sends or spends is proposed.
- What a tool does is what the compiler worked out from its body, so nothing is declared safer than it is. See [Action] — what an app offers to be done, by name.
Permits.ChangesAlone()is the common middle: the app's own data alone; anything that leaves the app proposed.- A proposal goes to the person the work is for. A skill's
DecidedBysends it to a queue or to one person instead, and then the person the work is for cannot approve it. - "From now on, do this alone" is a [[agent-guide-glossary#standing-rule|standing rule]]. Only whoever
WidensAgentAutonomyadmits may give one, and it never covers a tool the agent never runs.
More: What an agent does alone, what it proposes, what it never does — the gate and proposals (Agents kit), Guards and trying again — the app's own checks on an agent's work, and what happens when work fails (Agents kit) for the app's own checks. Choosing between the three: [[agent-guide-choosing#alone-or-propose]].
3. What does it cost, and who pays?#
app.Agents = new AgentsSetup { Agents = […], TaskBudget = 0.25m }; // every piece of work, at most
public override AgentLimits Limits => new AgentLimits { PerTask = 0.10m, Daily = 5 }; // this agent
public override decimal? Budget => 0.02m; // this skill
SetProcessBudget("Qualification", 2.00m); // each run of this process- Every piece of work has a hard limit: the skill's
Budget, else the agent'sLimits.PerTask, else the app'sTaskBudget. Room is reserved before each model call leaves. - Work whose money runs out is stopped, and keeps what it did.
- A day's limit for an agent, a limit for each run of a process, a limit for an assignment and a limit for anything
of your own (
.On("customer:acme", 10)) bind the same calls. - Nobody reads the meter or sets a budget until the app says who:
ReadsModelUsage,ManagesModelSpend.
More: What an agent may spend — a hard limit on every piece of work (Agents kit), Model spend — budgets, the meter and the price book (Llm kit), [[agent-processes#cost]].
4. Who answers its questions, and where?#
public override AsksPeople People => new AsksPeople { MayChoose = true, About = "A payment term over 30 days." };
public override string Consults => "Careful"; // a stronger model, asked first- An agent asks a person only when its class says it may.
- It asks the person the work is for; its [[agent-guide-glossary#sponsor|sponsor]] when the work is for nobody.
- The work stops where it stands and costs nothing while it waits. It goes on from the same point when the answer comes, a minute or a week later.
- With HumanRequests in the app, questions and proposals are requests in that person's inbox, with a deadline and reminders. Without it, your app shows them wherever it likes.
- Before a person, an agent can consult a more capable model, charged to the same piece of work.
More: An agent asks a person, and waits — the person rung of the ladder of help (Agents kit), Asking for help — an agent that consults a more capable model (Agents kit), An agent's proposals and questions in a person's inbox, and an inbox item in an agent's hands (Agents.HumanRequests kit).
5. Who supervises, who teaches, who audits?#
policy UsesAgents => IsOffice; // gives agents work, reads the work they asked for
policy SupervisesAgents => IsLead; // sponsors, pauses, calls work off, decides others' proposals
policy AuditsAgents => IsRiskOfficer; // reads everything, with what it said
policy TeachesAgents => IsRiskOfficer; // makes an instruction active, retires one, rolls back
new AgentSeat { Agent = "Vetter", Sponsor = lead }; // somebody answers for it- Each of these is nobody's until the app answers it. An agent with no sponsor does not run.
- Supervising brings four things with it — naming a sponsor, pausing, reading every receipt, deciding other people's proposals — and each can be handed to somebody else in one line.
- A supervisor reads that each step happened. Only an auditor, a teacher, and the people the work was for or given by read what it said.
- A person corrects a piece of work; an instruction drawn from corrections reaches the agent only after a person who teaches has made it active. The agent cannot activate its own.
More: Who is in charge of an app's agents — every policy in one table, pausing, sponsoring, and trying work again has every policy in one table. Correcting an agent, and what it is told from then on — corrections, instructions, instruction sets and rollback (Agents kit), The management screens — where a person runs an app's agents (Agents.Management kit).
6. What is kept, and what is erased?#
MarkAgentWorkAbout(outcome.Task, new DataSubject { Emails = [loan.BorrowerEmail] }); // whose data it is- Every piece of work leaves a receipt and a record. The record is chained, so a changed line is found.
- With Privacy in the app, what the work said is kept a month and that it happened seven months, by default. The tenant can change both.
- A person can ask for their copy, or to be erased. After an erasure the record still checks.
- Work done for a signed-in person is found by itself. Work about somebody needs the one line above.
More: The receipt and the record — what every piece of an agent's work leaves (Agents kit), Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request. When to add each Privacy kit: [[agent-guide-choosing#privacy]].
What does an agent do when its class says nothing?#
The careful thing, each time. This app decides as little as an app can: one agent with a purpose, a skill and two tools. The tests show what that gets you.
app Bureau {
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 Filer()] }; // no TaskBudget: fifty cents
[Role] enum BureauRole { Authenticator, Translator, Manager, Assistant }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsTranslator => RoleGrant.Any(g => g.Member == user && g.Role == BureauRole.Translator);
policy IsManager => RoleGrant.Any(g => g.Member == user && g.Role == BureauRole.Manager);
policy IsAssistant => RoleGrant.Any(g => g.Member == user && g.Role == BureauRole.Assistant);
// The two an app cannot leave out: who may give agents work, and who is in charge of them.
policy UsesAgents => IsTranslator || IsManager;
policy SupervisesAgents => IsManager;
entity Person : Principal {
security { allow read when IsAuthenticated || IsAuthenticator; allow create when IsAuthenticator; }
}
entity Commission {
[Required, Unique, MaxLength(40)] string Reference;
[MaxLength(400)] string Note = "";
security { allow read, create, update when IsTranslator || IsManager || IsAssistant; }
}
public class Filing { public bool Filed; }
[Action("Look up the house terms for a language", Query)]
public List<string> HouseTerms(string language) { return ["sopimus: agreement"]; }
[Action("Write a note on the commission")]
public string NoteCommission(Commission commission, string note) { commission.Note = note; return "Noted."; }
public class FileCommission : Skill<Commission, Filing> {
public override string Description => "File one commission.";
public override List<ActionInfo> Tools => [Actions.Of(HouseTerms), Actions.Of(NoteCommission)];
public override string Brief(Commission c) => $"Commission {c.Reference}. Its id is {c.Id}.";
}
/// It says who it is and what it can do, and nothing else.
[Actor(Roles = [BureauRole.Assistant])]
public class Filer : Agent {
public static Person Principal => new Person { Email = "[email protected]", DisplayName = "Filer" };
public override string Purpose => "You file the commissions of a small translation bureau.";
public override List<SkillBase> Skills => [new FileCommission()];
}
[TestFixture]
void Office() {
var ines = new Person { Email = "[email protected]", DisplayName = "Ines Varga" };
var otto = new Person { Email = "[email protected]", DisplayName = "Otto Reyes" };
new RoleGrant { Member = ines, Role = BureauRole.Translator };
new RoleGrant { Member = otto, Role = BureauRole.Manager };
new Commission { Reference = "C-31" };
}
principal Ines => Person.Single(p => p.Email == "[email protected]");
principal Otto => Person.Single(p => p.Email == "[email protected]");
[Test(Office)]
void Said_nothing_it_is_careful() {
// Decision 5: nobody answers for it yet, so it does not run.
runas (Ines) {
var commission = Commission.Single();
Assert.Throws<AgentNotSponsored>(() => await Agents.Of<Filer>().Do(new FileCommission(), commission));
}
runas (Otto) { SponsorAgent("Filer", Otto); UnitOfWork.Commit(); }
runas (Ines) {
var commission = Commission.Single();
var script = AgentScript.Of(
Says.Tool(Actions.Of(HouseTerms), "{\"language\":\"Finnish\"}"),
Says.Tool("One house term applies.", Actions.Of(NoteCommission), "{\"commission\":\"" + commission.Id.ToString() + "\",\"note\":\"sopimus: agreement\"}"),
Says.HandBack(new Filing { Filed = true }));
ScriptedAnswer.Stub((model, request) => script.Answer(model, request));
var outcome = await Agents.Of<Filer>().Do(new FileCommission(), commission);
// Decision 2: it looked the terms up alone. The note changes data, so it is proposed, to her, and the work waits.
Assert.True(outcome.Waiting);
Assert.Equal(["HouseTerms"], Trajectory.Of(outcome.Task));
Assert.Equal("", Commission.Single().Note);
Assert.Equal("Write a note on the commission", ProposalsForMe().Single().Label);
// Decision 3: fifty cents for one piece of work, in millionths of a dollar.
Assert.Equal(500000, AgentTask.Single().LimitMicros);
}
}What to read next#
- Shape 1 — an assistant for one person — the smallest app, where five of the six are one line each
- Choosing — I want X, so use Y, not Z, because — decision tables for the choices that come up most
- What stays under people's control — ten promises, and where each is shown — the same six, as a person who is not a developer would ask them
Go deeper#
- Who is in charge of an app's agents — every policy in one table, pausing, sponsoring, and trying work again — every policy, what it admits, and its default
- What an agent does alone, what it proposes, what it never does — the gate and proposals (Agents kit) — the gate and proposals
- What an agent may spend — a hard limit on every piece of work (Agents kit) — limits on every piece of work
- Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request — what is held, for how long, and what an erasure leaves