# The Brief Is an API

> Running a swarm of AI agents, part 1: why the brief you hand each agent is an interface, and how a typed, validated contract stopped my fleet from researching itself to death.


I run a lot of work through small crowds of AI agents now. Not one agent grinding through a big task start to finish, but five or six of them working different angles of the same question at once, then all their answers pulled back together into one. When it works it feels like having a research team. When it does not, it fails in ways a single agent never would, and those failures do not announce themselves.

I have written a fair amount about the big picture of my setup: the research wing, why I built it, what it is for. This series zooms all the way in. These are the moves themselves, learned by getting them wrong a few times first. I am starting with the one I lean on most, running a swarm, because it is my bread and butter and because almost everything I know about it I learned by watching it break.

This is the first of three posts, and it starts where the work starts: the instruction you hand each agent. The single highest-leverage fix I have made to my entire setup was changing what that instruction is.

One distinction first, because it decides who is doing what. When I say I run a swarm, I am not the one writing each brief or pulling the reports together by hand. A model does that. It takes the question, builds the briefs, fans the work out to a crowd of subagents, and reconciles what they send back. That agent is the [orchestrator](/recall/orchestrator/), and the subagents are its workers. I sit above all of it as what I have come to call the [operator](/recall/operator/): I point the system at the right problem, channel what I know into how it runs, and judge what comes back. Operator, orchestrator, subagents. I will use those three words precisely across the series, because it is really about the two boundaries the operator owns. This post takes the first one: the contract going in.

## The brief is an API, not a prompt

Let me walk you through the time a swarm of mine came back with nothing.

One day, I dispatched a research [fleet](/recall/fleet/). A handful of agents, each with a slice of a larger question, each told to go research its slice and write its findings to its own file in a shared output folder. That folder is the deliverable: one findings file per agent, and when the run ends I expect to open it and count them all. I switched context to my other agents while it ran. I came back, opened the output folder, and several of the files were not there. Not empty. Not bad. Just absent. The agents had run, spent their entire budget of tool calls fetching and reading and thinking, and then hit their limit and stopped, having never written a single word to disk.

I went to the source to understand the cause. Every brief my orchestrator wrote included a line near the bottom that said, in effect, "save your last two turns for actually writing the file." That line was the only thing standing between a productive agent and one that researches itself to death. And it was just text. A convention I retyped each time, sitting in a paragraph of other text, with nothing checking that it was present or that it survived from one brief to the next. On one [dispatch](/recall/dispatch/) (one of these launches, where the orchestrator writes a brief per agent and sends the whole crowd out in one motion) it dropped out of the wording. The agents never knew it was supposed to be there. They did exactly what a hungry research agent does when nobody tells it to stop: they kept reading until the lights went out.

<!-- FIGURE 1
Type: timeline / two-track comparison diagram (render via diagram MCP, clean line style)
Title: "Two ways a research agent spends its budget"
Top track ("unpaced"): a turn-budget bar of ~10 cells, all filled with "fetch / read / think", final cell hits limit, label: "budget exhausted, 0 bytes written". Mark in red.
Bottom track ("paced"): same bar, ~8 cells "fetch / read / think", last 2 cells filled "WRITE OUTPUT" reserved, label: "findings on disk". Mark in green.
Caption: "The only difference between a useful agent and a wasted one was two reserved turns. Leaving that to prose meant it could vanish."
-->
![A two-track diagram comparing an agent that exhausts its turn budget on research and writes nothing, against an agent that reserves its final two turns for writing its output file.](./figures/fleet-pacing.svg)

The fix was not a better-worded line or instruction. The fix was to quit treating the brief as text and treat it as a typed contract instead.

So now every agent in a fleet is built by a short dispatch script, not a freeform prompt. For each agent, the script assembles a small structured object with named, required fields: who this agent is, the success criteria it grades itself against, how many tool calls it gets, how many turns it must reserve for writing, where its output goes. Before anything is dispatched, that object runs through a validator: a small piece of plain code that does one thing, confirm every required field is present and filled before a single agent launches. If a required field is missing or empty, the dispatch aborts then and there, with a list of exactly what is missing. Nothing spawns. No budget burns. The failure happens up front, in a tenth of a second, instead of an hour later in a folder of files that do not exist.

```python
brief = FleetBrief(
    thread_id="ag-ui",
    role="Agent A: Spec and mechanics",
    criteria=[...],            # the rubric this agent self-grades on
    tool_quota=40,             # hard ceiling on tool calls
    write_budget=2,            # turns reserved for writing, not negotiable
    pacing_instruction="At turn N-2, stop researching and start writing.",
    output_path="research/ag-ui/agent-a.md",
)

validate(brief)               # raises on any missing field, BEFORE dispatch
markdown = render_brief(brief) # the typed struct is the contract;
                              # the prompt text is just what it compiles to
```

That last comment is the real idea. The prompt the agent actually reads is an emission, the way assembly is an emission of your source code. You do not hand-write the assembly. You write the typed thing, you check the typed thing, and you let it compile down to the words the agent sees. The line about reserving two turns can never again drop out of a brief, because it is no longer a line. It is a field, and a brief without it does not exist.

The general version of this, if you take one thing from the post: the boundary between you and an autonomous worker is an interface, and the quality of that interface matters more than the raw smarts of the worker behind it. We spend enormous energy arguing about which model is best. I get more reliability out of a worse model with a validated brief than a better model with a sloppy one.

## The other boundary

That handles the input side. The output side is a stranger beast. A single agent that breaks will usually tell you so. A crowd hands you a tidy stack of summaries, and the problems arrive inside them, polished and easy to believe. The empty output folder was my introduction to a whole family of these, and the guards I keep against each one are the next post. The one after that closes the series with the question everything else exists to serve: how you come to trust what a swarm tells you at all.

---

*If you want to experiment with this yourself, the companion repo at [github.com/byrongeorge/fleetbrief](https://github.com/byrongeorge/fleetbrief) holds a tiny, dependency-free version of the typed brief, the validator, and an example dispatch, plus a demo that recreates the empty-folder failure so you can watch the validator catch it.*

