# Shape 4 — a team of agents with a coordinator

> One agent is in charge of a job and hands typed pieces of it to specialists, several at once. Each piece is a piece of work of its own, with its own receipt under the coordinator's. A specialist never holds more rights than the work above it, never spends more than is left above it, and never does alone what the work above it must propose. It takes no new kit: delegation is part of `Osysharp.Agents`.

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

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

## Summary        {#summary}
A large commission arrives at the translation bureau. Planning it is two different jobs: somebody has to check its
terms against the house glossary, and somebody has to price it. The bureau has an agent for each. A third agent, the
coordinator, is given the commission, hands both jobs on **in one go**, and writes the plan from what comes back.

This is the fourth shape: **a team of agents with a coordinator.**

![A piece of work with everything it handed on, and what each piece waits for](https://osysharp.com/media/ref/364e22-agents-delegation-work-tree-waiting-wide-light.png)

*The tree of a piece of work, from a purchasing office's app: the agent in charge, its three specialists, where each
one stands and what each one cost.*

## Description    {#description}

### What does it add?   {#takes}
Nothing new to install. [Osysharp.Agents](https://osysharp.com/reference/agent/kit-agents/) does it:

| You write | It gives you |
|---|---|
| `Delegates` on the coordinator, naming each specialist and the skill it is handed | the coordinator's model is offered each as a tool; pieces asked for in one turn run side by side |
| a skill on each specialist | typed in, typed out, checked — exactly as in [[agent-guide-assistant\|shape 1]] |
| optionally `budget:` on a specialist | that piece may spend at most this share of what the coordinator has left |

### What do you decide now?   {#decisions}

1. **Who is in charge, and of what.** The coordinator holds the skill people ask for. It needs no tools of its
   own.
2. **What each specialist is handed.** `Specialist.Of<Pricer>(new PriceWork())`: an agent and one of its skills. A
   coordinator hands on only what its class declares. See [[agent-delegation#declared]].
3. **What each piece may spend.** A piece spends out of what the work above it has left, and `budget:` caps it
   further. A piece that runs out is stopped; the piece beside it is not touched. See [[agent-delegation#money]].
4. **How deep it goes.** `MaxDepth` says whether a specialist may hand work on in turn.
5. **What a specialist may do alone.** Its own class says, as in [shape 2](https://osysharp.com/reference/agent/guide-approvals/) — and it is never
   more than the work above it may do alone. See [[agent-delegation#people]].

### What holds, whatever you decide?   {#holds}
- **Rights never grow down the tree.** A specialist acts with its own agent's roles and, like the coordinator, never
  past the person the whole job is for. See [[agent-delegation#rights]].
- **Money never grows down the tree.** Every piece is charged to its own receipt and to every piece above it. The
  whole costs what its pieces cost.
- **An approval from deep in the tree reaches the right person.** When a specialist must propose something, the
  proposal goes to the person the work at the top is for, and says through whom it comes. The whole chain waits.
- **One record for the whole tree**, and it can be checked for changes. See [[agent-task-log#chain]].

### What does the whole app look like?   {#app}
What is new since [shape 1](https://osysharp.com/reference/agent/guide-assistant/) is marked `NEW`.

```osy title="a coordinator hands two pieces on at once; the tree, the money, and a piece that runs out" run app=agent-guide-team
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 Coordinator(), new Terminologist(), new Pricer()], TaskBudget = 0.05m };

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

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; }
}

entity Commission {
  [Required, Unique, MaxLength(40)] string Reference;
  [Required, MaxLength(200)] string Title;
  [MaxLength(40)] string Into = "";
  int Words = 0;
  [MaxLength(400)] string Note = "";
  security { allow read, create, update when IsTranslator || IsManager || IsAssistant; }
}

/// What the coordinator hands back.
public class Plan {
  public int Terms;
  /// The fee, in euros — 0 when it could not be priced.
  public decimal Fee;
  public string Summary = "";
}
/// NEW: what each specialist hands back, and what the pricer is given.
public class TermReport { public int Terms; public string Finding = ""; }
public class PriceAsk { public int Words; public string Into = ""; }
public class Price { public decimal Euros; }

[Action("Look up the house terms for a language", Query)]
public List<string> HouseTerms(string language) {
  return language == "Finnish" ? ["sopimus: agreement", "toimitus: delivery"] : [];
}

[Action("Look up the rate per word", Query)]
public decimal RatePerWord(string language) { return language == "Finnish" ? 0.13m : 0.10m; }

/// NEW: a specialist's skill that takes a ROW. Handed on by another agent, the row is named by its id.
public class CheckTerms : Skill<Commission, TermReport> {
  public override string Description => "Check one commission against the house terms for its language.";
  public override List<ActionInfo> Tools => [Actions.Of(HouseTerms)];
  public override string Brief(Commission c) => $"Commission {c.Reference}, into {c.Into}. Its id is {c.Id}.";
  public override string HandedSchema() => HandOn.ById("The commission to check.");
  public override Commission Handed(string inputJson) {
    var id = HandOn.IdIn(inputJson);
    return Commission.Single(c => c.Id == id);
  }
}

/// NEW: a specialist's skill that takes plain values. Nothing more to say.
public class PriceWork : Skill<PriceAsk, Price> {
  public override string Description => "Price one piece of translation work.";
  public override List<ActionInfo> Tools => [Actions.Of(RatePerWord)];
}

public class PlanCommission : Skill<Commission, Plan> {
  public override string Description => "Plan one commission: have its terms checked and its work priced.";
  public override string Brief(Commission c) => $"Commission {c.Reference}: {c.Title}, {c.Words} words into {c.Into}. Its id is {c.Id}.";
}

[Actor(Roles = [BureauRole.Assistant])]
public class Terminologist : Agent {
  public static Person Principal => new Person { Email = "terminologist@bureau.example", DisplayName = "Terminologist" };
  public override string Purpose => "You check texts against the bureau's house terms.";
  public override List<SkillBase> Skills => [new CheckTerms()];
}

[Actor(Roles = [BureauRole.Assistant])]
public class Pricer : Agent {
  public static Person Principal => new Person { Email = "pricer@bureau.example", DisplayName = "Pricer" };
  public override string Purpose => "You price translation work.";
  public override List<SkillBase> Skills => [new PriceWork()];
}

/// NEW — the coordinator. Decision 2: what it hands on, and to whom. Decision 3: the pricing gets a share.
[Actor(Roles = [BureauRole.Assistant])]
public class Coordinator : Agent {
  public static Person Principal => new Person { Email = "coordinator@bureau.example", DisplayName = "Coordinator" };
  public override string Purpose => "You plan the commissions of a small translation bureau.";
  public override List<SkillBase> Skills => [new PlanCommission()];
  public override Delegation Delegates => new Delegation {
    To = [Specialist.Of<Terminologist>(new CheckTerms()), Specialist.Of<Pricer>(new PriceWork(), budget: 0.006m)],
    MaxDepth = 1,                                                          // decision 4
  };
}

[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", Words = 2400 };
  new AgentSeat { Agent = "Coordinator", Sponsor = otto };
  new AgentSeat { Agent = "Terminologist", Sponsor = otto };
  new AgentSeat { Agent = "Pricer", Sponsor = otto };
}
principal Ines => Person.Single(p => p.Email == "ines@bureau.example");

/// What the coordinator's model says: both pieces in ONE turn — so they are done side by side — then the plan.
AgentScript Plans(Commission c, Plan plan) {
  return AgentScript.Of(
    Says.Together(
      Says.HandTo<Terminologist>(new CheckTerms(), Says.Row(c.Id)),
      Says.HandTo<Pricer>(new PriceWork(), JsonSerializer.Serialize(new PriceAsk { Words = 2400, Into = "Finnish" }))),
    Says.HandBack(plan));
}

[Test(Office)]
[runas(Ines)]
void The_coordinator_hands_two_pieces_on_at_once_and_plans_from_what_comes_back() {
  var commission = Commission.Single();
  var cast = AgentCast.Of()
    .Plays<Coordinator>(Plans(commission, new Plan { Terms = 2, Fee = 312, Summary = "Two house terms; 312 euros." }))
    .Plays<Terminologist>(AgentScript.Of(
      Says.Tool(Actions.Of(HouseTerms), "{\"language\":\"Finnish\"}"),
      Says.HandBack(new TermReport { Terms = 2, Finding = "Both apply." })))
    .Plays<Pricer>(AgentScript.Of(
      Says.Tool(Actions.Of(RatePerWord), "{\"language\":\"Finnish\"}"),
      Says.HandBack(new Price { Euros = 312 })));
  ScriptedAnswer.Stub((model, request) => cast.Answer(model, request));

  var outcome = await Agents.Of<Coordinator>().Do(new PlanCommission(), commission, about: "Planning C-31");

  // Typed out of each piece, typed out of the whole.
  Assert.True(outcome.Done);
  Assert.Equal(312m, outcome.Result.Fee);
  // One receipt for each piece, under the coordinator's — for the same person, about the same thing.
  Assert.Equal(["Coordinator · Plan commission: Done", "  Terminologist · Check terms: Done", "  Pricer · Price work: Done"], Trajectory.Tree(outcome.Task));
  var pieces = WorkTree.HandedOnBy(AgentTask.Single(t => t.Id == outcome.Task));
  Assert.Equal(Ines.Id, pieces[0].Requester!.Id);
  Assert.Equal("Planning C-31", pieces[1].About);
  // The pricer's piece was opened with its share; and the whole costs what its pieces cost.
  Assert.Equal(6000, pieces[1].LimitMicros);
  Assert.Equal(AgentTask.ToList().Sum(t => t.SpentMicros), Trajectory.TreeCost(outcome.Task));
  // One record for the tree, and it checks.
  Assert.True(VerifyTrace(pieces[0]).Intact);
}

[Test(Office)]
[runas(Ines)]
void A_piece_that_runs_out_of_money_is_stopped_and_the_coordinator_carries_on() {
  var commission = Commission.Single();
  var cast = AgentCast.Of()
    .Plays<Coordinator>(Plans(commission, new Plan { Terms = 2, Fee = 0, Summary = "Two house terms. Not priced: the pricing ran out of money." }))
    .Plays<Terminologist>(AgentScript.Of(Says.HandBack(new TermReport { Terms = 2 })))
    // The pricer's first answer is a long one — 1000 tokens in, 600 out: 4000 of its 6000. A second call does not fit.
    .Plays<Pricer>(AgentScript.Of(
      Says.Tool(Actions.Of(RatePerWord), "{\"language\":\"Finnish\"}", 1000, 600),
      Says.HandBack(new Price { Euros = 312 })));
  ScriptedAnswer.Stub((model, request) => cast.Answer(model, request));

  var outcome = await Agents.Of<Coordinator>().Do(new PlanCommission(), commission);

  // The piece was stopped at its own share; the piece beside it was not touched; the coordinator was told, and
  // handed back what it had.
  Assert.Equal(["Coordinator · Plan commission: Done", "  Terminologist · Check terms: Done", "  Pricer · Price work: Stopped"], Trajectory.Tree(outcome.Task));
  Assert.True(Trajectory.Said(outcome.Task).Any(s => s.StartsWith("Pricer was stopped: Its money ran out:")));
  Assert.True(outcome.Done);
  Assert.Equal(0m, outcome.Result.Fee);
}
```

### What would make this the wrong shape?   {#not-this}
- The pieces must happen in a fixed order, with people in between. That is a process: see
  [shape 3](https://osysharp.com/reference/agent/guide-process/). A process step can itself be a coordinator.
- One agent with two more tools would do. Then do that: see [[agent-guide-choosing#tools-or-delegation]].

## What to read next   {#read-next}
- [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, delegation included
- [Choosing — I want X, so use Y, not Z, because](https://osysharp.com/reference/agent/guide-choosing/) — one agent with more tools, or delegation?
- [A supplier is qualified — five days, four agents, one that failed, and a three-day wait](https://osysharp.com/reference/agent/story-supplier/) — a coordinator and three specialists qualifying a supplier

## Go deeper   {#see-also}
- [An agent hands work on to other agents — at once, typed, and never with more than it holds (Agents kit)](https://osysharp.com/reference/agent/delegation/) — handing work on: rights, money, waiting, failing
- [What an agent may spend — a hard limit on every piece of work (Agents kit)](https://osysharp.com/reference/agent/budgets/) — what a piece of work may spend
- [[agent-requests#tree]] — the tree of a piece of work on a page
- [[agent-testing#cast]] — scripting several agents at once
