# Shape 1 — an assistant for one person

> The smallest app with an agent in it: one agent, one typed job, two tools, working for whoever is signed in. It takes two kits, `Osysharp.Llm` and `Osysharp.Agents`, and five decisions. The example on this page is a whole app and its tests, compiled and run.

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

> **You are here:** [Building with agents](https://osysharp.com/reference/agent/guide/) › [Deciding what to build](https://osysharp.com/reference/agent/guide-deciding/) › Shape 1

## Summary        {#summary}
A translator at a small translation bureau opens a commission and asks for it to be prepared. An agent looks up the
bureau's house terms for the language, writes them on the commission as a note, and hands back how many it found.
It works for her: it can read and change only what she may, and it leaves a receipt saying what it did and what it
cost.

That is the first shape: **an assistant for one person**. Nothing waits, nobody approves, no other agent is
involved.

## Description    {#description}

### What does it take?   {#takes}

| You add | It gives you |
|---|---|
| [[agent-kit-llm\|Osysharp.Llm]] | every call to a model: which model, what it may cost, what it did cost |
| one model provider — [[agent-kit-anthropic\|Anthropic]], [[agent-kit-openai\|OpenAI]] or [[agent-kit-gemini\|Gemini]] | the class that talks to that provider |
| [[agent-kit-agents\|Osysharp.Agents]] | the agent, its job, its tools, the receipt and the record |
| [[agent-kit-agents-testing\|Osysharp.Agents.Testing]], in tests | a script in place of the model, so tests cost nothing |

### What do you decide, in order?   {#decisions}

1. **Who the agent acts as.** `[Actor(Roles = [BureauRole.Assistant])]` gives it an account of its own with exactly
   that role. See [[Actor] — a principal that code may run as](https://osysharp.com/reference/security/declared-actor/).
2. **Who it works for.** Whoever is signed in when the work is given. It then does only what *both* its account and
   that person may do. See [[agent-ask#for]].
3. **What it may do alone.** Here `Permits.ChangesAlone()`: it may change the bureau's own data without asking.
   Leave that line out and it only looks things up. See [[agent-autonomy#gate]].
4. **What it may spend.** `TaskBudget = 0.05m` holds each piece of work to five cents. See [What an agent may spend — a hard limit on every piece of work (Agents kit)](https://osysharp.com/reference/agent/budgets/).
5. **Who answers for it, and who may use it.** A person is named as its [[agent-guide-glossary#sponsor|sponsor]],
   and the app answers three [[agent-guide-glossary#policy|policies]]: `UsesAgents`, `SupervisesAgents`,
   `AuditsAgents`. See [Who is in charge of an app's agents — every policy in one table, pausing, sponsoring, and trying work again](https://osysharp.com/reference/agent/roles/).

### What do you get without writing it?   {#free}
- A [[agent-guide-glossary#receipt|receipt]] for every piece of work: who asked, as which account it ran, what it
  cost, how it ended. See [[agent-task-log#receipt]].
- A [[agent-guide-glossary#record|record]] of every step, chained so that a changed line is found.
- A hard stop when the money for a piece of work runs out.
- A result that is typed and checked before your code sees it. See [[agent-skills#check]].

### What does the whole app look like?   {#app}
The model here is a [scripted model](https://osysharp.com/reference/agent/llm-scripted-model/), so the example runs anywhere and costs nothing. In
a real app that one line names a provider instead:
`Client = new AnthropicClient { ApiKey = Secret.Anthropic }`.

```osy title="one agent, one typed job, two tools — and what it leaves behind" run app=agent-guide-assistant
app Bureau {
  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;

// The models this app has, and the agents it has.
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 Preparer()], TaskBudget = 0.05m };      // decision 4

[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);

// Decision 5: who gives agents work, who is in charge of them, who reads everything they did.
policy UsesAgents       => IsTranslator || IsManager;
policy SupervisesAgents => IsManager;
policy AuditsAgents     => IsManager;
policy ReadsModelUsage   => IsManager;
policy ManagesModelSpend => IsManager;

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

/// One text a customer has sent in to be translated.
entity Commission {
  [Required, Unique, MaxLength(40)] string Reference;
  [Required, MaxLength(200)] string Title;
  [MaxLength(40)] string Into = "";
  [MaxLength(400)] string Note = "";
  security {
    allow read, update when IsTranslator || IsManager || IsAssistant;
    allow create when IsTranslator || IsManager;
  }
}

/// What preparing a commission hands back.
public class Preparation {
  public int Terms;
  public string Summary = "";
}

// The agent's tools are the app's own functions. `Query` says this one only reads.
[Action("Look up the house terms for a language", Query)]
public List<string> HouseTerms(string language) {
  return language == "Finnish" ? ["sopimus: agreement", "toimitus: delivery"] : [];
}

[Action("Write a note on the commission")]
public string NoteCommission(Commission commission, string note) { commission.Note = note; return $"Noted on {commission.Reference}."; }

/// The job: a commission in, a preparation out.
public class PrepareCommission : Skill<Commission, Preparation> {
  public override string Description => "Prepare one commission for its translator.";
  public override string Instructions => "Look up the house terms for the language. Write them on the commission as a note.";
  public override List<ActionInfo> Tools => [Actions.Of(HouseTerms), Actions.Of(NoteCommission)];
  public override string Brief(Commission c) => $"Commission {c.Reference}: {c.Title}, into {c.Into}. Its id is {c.Id}.";
  public override string? Check(Preparation p) => p.Terms < 0 ? "A count cannot be negative." : null;
}

/// The agent. Decision 1: the account it acts as. Decision 3: what it may do alone.
[Actor(Roles = [BureauRole.Assistant])]
public class Preparer : Agent {
  public static Person Principal => new Person { Email = "preparer@bureau.example", DisplayName = "Preparer" };
  public override string Purpose => "You prepare commissions for the translators of a small bureau.";
  public override List<SkillBase> Skills => [new PrepareCommission()];
  public override Permits May => Permits.ChangesAlone();
}

[TestFixture]
void Office() {
  var ines = new Person { Email = "ines@bureau.example", DisplayName = "Ines Varga" };
  var otto = new Person { Email = "otto@bureau.example", DisplayName = "Otto Reyes" };
  new RoleGrant { Member = ines, Role = BureauRole.Translator };
  new RoleGrant { Member = otto, Role = BureauRole.Manager };
  new Commission { Reference = "C-31", Title = "Supply agreement", Into = "Finnish" };
  new AgentSeat { Agent = "Preparer", Sponsor = otto };              // decision 5: the manager answers for it
}
principal Ines => Person.Single(p => p.Email == "ines@bureau.example");
principal Otto => Person.Single(p => p.Email == "otto@bureau.example");

[Test(Office)]
[runas(Ines)]
void The_assistant_prepares_a_commission_for_the_person_who_asked() {
  var commission = Commission.Single(c => c.Reference == "C-31");
  // What the model answers, turn by turn. Everything else is real.
  var script = AgentScript.Of(
    Says.Tool(Actions.Of(HouseTerms), "{\"language\":\"Finnish\"}"),
    Says.Tool(Actions.Of(NoteCommission), "{\"commission\":\"" + commission.Id.ToString() + "\",\"note\":\"sopimus: agreement; toimitus: delivery\"}"),
    Says.HandBack(new Preparation { Terms = 2, Summary = "Two house terms noted." }));
  ScriptedAnswer.Stub((model, request) => script.Answer(model, request));

  // Decision 2: it works for whoever is signed in — here, Ines.
  var outcome = await Agents.Of<Preparer>().Do(new PrepareCommission(), commission, about: "Commission C-31");

  Assert.True(outcome.Done);
  Assert.Equal(2, outcome.Result.Terms);                                             // typed, and checked
  Assert.Equal("sopimus: agreement; toimitus: delivery", Commission.Single().Note);
  Assert.Equal(["HouseTerms", "NoteCommission"], Trajectory.Of(outcome.Task));       // what it did, in order

  // The receipt: who asked, how it ended, what it cost against what it may.
  var receipt = AgentTask.Single();
  Assert.Equal(Ines.Id, receipt.Requester!.Id);
  Assert.Equal(AgentTaskStatus.Done, receipt.Status);
  Assert.True(receipt.SpentMicros > 0);
  Assert.Equal(50000, receipt.LimitMicros);                                          // five cents, in millionths
  Assert.True(VerifyTrace(receipt).Intact);                                          // the record is as it was written
}

[Test(Office)]
void An_agent_nobody_answers_for_does_not_run() {
  runas (Otto) { PauseAgent("Preparer", "The glossary is being rewritten."); }
  runas (Ines) {
    var commission = Commission.Single(c => c.Reference == "C-31");
    Assert.Throws<AgentPaused>(() => await Agents.Of<Preparer>().Do(new PrepareCommission(), commission));
  }
}
```

### What would make this the wrong shape?   {#not-this}
- The agent must do something a person has to agree to first. Go to [shape 2](https://osysharp.com/reference/agent/guide-approvals/).
- The work is one step of a longer business process. Go to [shape 3](https://osysharp.com/reference/agent/guide-process/).
- One job is really several jobs for different specialists. Go to [shape 4](https://osysharp.com/reference/agent/guide-team/).
- You only need one answer from a model, with no tools. You do not need an agent: see
  [[agent-guide-choosing#agent-or-call]].

## What to read next   {#read-next}
- [Shape 2 — work done for people, with approvals](https://osysharp.com/reference/agent/guide-approvals/) — the same app, grown: the agent proposes a fee and a person approves it in their inbox
- [One piece of work, followed end to end](https://osysharp.com/reference/agent/guide-work-end-to-end/) — one piece of work followed through every kit
- [The map of the kits — which sits on which, and what each one is for](https://osysharp.com/reference/agent/guide-kits/) — the map of the kits

## Go deeper   {#see-also}
- [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, who is in charge
- [A skill — a typed job an agent can be given, its check and its tools (Agents kit)](https://osysharp.com/reference/agent/skills/) — a typed job, its check and its tools
- [Giving an agent work — Do, Ask, Stream, For and Decide (Agents kit)](https://osysharp.com/reference/agent/ask/) — `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
- [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
