Chapter 5 of 7 · 8 min read
A brief carries conclusions, not the transcript that produced them
On this page
Here is what Vantage Retail sent the third time, in place of both the four-word task and the 47,000-token transcript.
TASK
Fix the flake in e2e/checkout.spec.ts :: "applies promo code before tax".
ESTABLISHED (do not re-derive)
- The flake is a race in fixture setup, not in the test body.
- seedCart() posts /cart/items, which returns 202. The cart row is
written by a background consumer. Observed latency: ~40 ms typical,
600 ms p99 under CI CPU contention.
- The page under test sometimes loads before the row exists; subtotal
renders $0.00 and the promo assertion fails.
- Ruled out: CSS animation timing, stale service worker, auth fixture.
CONSTRAINTS
- Do not modify assertions in the test body. They are correct.
- Do not add a fixed sleep. Fix the fixture's completion condition.
- The consumer's contract cannot change; other suites depend on 202.
POINTERS (read only if you need them)
- fixture: e2e/fixtures/cart.ts
- consumer: services/cart-consumer/src/index.ts
- a passing shape to copy: e2e/fixtures/orders.ts seedOrder()
RETURN
A unified diff, plus: { root_cause: <string>, files_touched: [<path>],
runs_passed: <int>/<int> }. Nothing else.That is 218 tokens. The child read cart.ts and orders.ts, noticed that seedOrder() already polls for its row, applied the same pattern, ran the file twenty times, and returned in three turns.
--- a/e2e/fixtures/cart.ts
+++ b/e2e/fixtures/cart.ts
@@ -12,7 +12,11 @@ export async function seedCart(api: ApiClient, items: Item[]) {
const res = await api.post('/cart/items', { items });
if (res.status !== 202) throw new Error(`seedCart: ${res.status}`);
- return res.data.cartId;
+ const cartId = res.data.cartId;
+ await waitFor(async () => {
+ const cart = await api.get(`/cart/${cartId}`);
+ return cart.data.items.length === items.length;
+ }, { timeout: 5_000, interval: 25 });
+ return cartId;
}The delegation cost 218 tokens of brief and returned 190 tokens of result, against twenty turns of parent work that it did not repeat and did not need to see.
The four sections are doing four different jobs
An on-call handover note has roughly this structure for the same reason. When you hand a live incident to the next shift you do not read them your terminal history; you tell them what you know, what you have ruled out, what they must not touch, and where the dashboards are. The person taking over is fully capable of running the queries you ran, and the handover exists so they do not have to.
The established-facts section carries the parent's conclusions, in declarative form, with the numbers attached. Those four lines are the compressed output of two forty-run test loops and a log correlation, and the compression ratio is roughly 400 to 1 against the transcript that produced them. The ruled-out line is worth its tokens on its own: it is small, and it stops the child from spending six minutes discovering that the service worker is fine.
The constraints section is the highest-value text per token in the whole brief. "Do not modify assertions in the test body. They are correct." is eleven words and it is the entire difference between this run and the one that deleted the assertion. Constraints are where the parent encodes what its verification signal cannot express, and the test for whether a constraint belongs is whether the child could satisfy its acceptance check while violating it. A green suite is compatible with a deleted assertion, so that constraint is load-bearing; a green suite is not compatible with a syntax error, so "write valid TypeScript" is noise.
The pointers section is the piece that most briefs omit and that makes the economics work. A pointer costs about eight tokens and expands to a file only in the runs that need it, which converts a fixed cost into a conditional one paid out of the child's window rather than the parent's. Vantage's child read cart.ts and orders.ts and never opened the consumer, so the consumer's 340 lines were never paid for by anyone. The general form is that anything on disk should be a path and anything discovered at runtime should be a stated fact, because the first is cheaply re-readable by the child and the second is not re-derivable at any price the delegation can afford.
The return section is the output contract, which the second chapter of this topic covered in the outbound direction. Putting it in the brief rather than only in the definition matters when the same subagent serves several call sites with different downstream needs.
A brief is not a summary of the conversation, and the difference is more than a matter of degree. A summariser compresses what happened, so it preserves the proportions of the discussion — twelve turns spent on the service worker become a sentence about the service worker, because twelve turns is a lot of the material. A brief is written backwards from what the child has to decide, so those twelve turns collapse into one word in the ruled-out line and the two-sentence finding from turn six gets three lines. Running a summarisation pass over the transcript and calling it a brief produces something that is smaller than the transcript and still wrong in the way the transcript was, which is why teams that automate this step by summarising rather than by selecting tend to report that briefs "keep growing" over time.
In practice the parent writes the brief, which means the delegation step is two model actions rather than one: compose the brief, then invoke. That is worth the extra turn for a reason beyond quality, which is that composing it surfaces the cases where there is nothing to compose. When the parent sits down to write the established-facts section and finds it empty, the task depends on nothing the parent discovered, and a task whose entire input is on disk is exactly the task that the first chapter of this topic said to test against a script before delegating at all. An empty constraints section is a similar signal in the other direction: it usually means the parent has not thought about what the child's acceptance check fails to capture.
Two layers, and what belongs in each
A subagent has a durable definition and a per-invocation brief, and confusing them produces either a definition that has to be edited for every task or a brief that repeats the same three paragraphs every time it is sent.
Definition (.claude/agents/*.md) | Brief (per invocation) | |
|---|---|---|
| Lifetime | committed, reviewed, changes rarely | built fresh for one call |
| Tool grant | here | never here |
| Role and standing method | here | never here |
| Default output shape | here | overridden here when needed |
| What the parent established | never here | here |
| Task-specific constraints | never here | here |
| Pointers into this repository's layout | stable ones here | task-specific ones here |
The rule that keeps the boundary clean is that the definition holds what is true of every invocation and the brief holds what is true of this one. A test-fixer definition can say that fixtures are the usual culprit and that assertions are presumed correct, because that is a standing policy; it cannot say that this particular flake is a 202 race.
Brief size is worth instrumenting for the same reason result size is. Vantage logged the token count of every brief and found the distribution bimodal within a month: most sat between 150 and 400 tokens, and a tail above 3,000 turned out to be four call sites where an engineer had reintroduced transcript pasting for a task that kept failing. A soft cap of 1,200 tokens, enforced the same way the result budget is, catches that regression before it becomes the house style, and the rare legitimate brief that exceeds it is a useful thing to be asked to justify. The number itself matters less than the fact that somebody is watching it, since brief inflation is gradual and each individual addition looks reasonable.
There is a practical benefit to that split beyond tidiness. The definition is a committed file, so it goes through review, and a change to a tool grant is a diff someone signs off on. Briefs are generated at runtime by the parent and nobody reviews them individually, which is an argument for keeping anything security-relevant in the layer that gets read by humans.
The same discipline going the other way
The infrastructure case from earlier in this topic ends up in the same place by a different route. Ardwick's child could not resolve nine module references because they arrived through a variable that only Terraform can evaluate, and the answer was to run the credentialed command in the parent and pass the values down. Those values enter the child's second invocation as established facts, in exactly the section above.
ESTABLISHED (do not re-derive; do not run terraform)
Resolved for prod-eu-west-1 from a plan taken at 14:20 UTC:
private_subnet_ids = {
subnet-0a91c3e2 = "10.12.0.0/20" # retiring
subnet-0b47f81d = "10.12.16.0/20" # retiring
subnet-0c22ae90 = "10.30.0.0/20" # already migrated CR-8841, ignore
}Three lines of JSON that cost a credentialed plan in the parent, plus one annotation the parent knew and no file records. The child keeps its read-only grant, the state file and the lock stay on the parent's side of the boundary, and the delegation that was blocked now completes. The brief is where the parent's privileges get converted into the child's knowledge, which is a good way to think about why the two halves of this topic — the tool grant and the brief — are the same design decision viewed from opposite ends.
The # already migrated CR-8841, ignore comment illustrates the other thing briefs carry that nothing else can. That fact exists only in the parent's session, forty turns back, in a change request the human approved verbally. No amount of repository access recovers it.
Where a brief goes stale
The timestamp in that block is not decoration. A brief is a snapshot of what the parent believed at the moment it was written, and everything downstream of it acts on that snapshot. When delegation is sequential and the parent is blocked, the window for divergence is the child's runtime, which is seconds. When several children run in parallel, or when the parent keeps working while a long child runs, the snapshot can be minutes old by the time the child acts on it, and if the child has write access the consequences are not confined to a wrong answer. Ardwick's plan was taken at 14:20; a child applying something at 14:38 on the basis of it is acting on state that another change request may have moved.
The mitigations are ordinary and worth naming, because they are the things nobody adds until after the first incident. Stamp the brief with the moment its facts were gathered, so a stale result is at least diagnosable. Keep write-capable children on facts the parent can cheaply re-confirm before applying anything, which usually means the write happens in the parent. And prefer briefs whose established facts are about code, which changes on a commit you control, over facts about live infrastructure, which changes because someone else pressed a button.
None of that tells you whether the delegation was worth doing. The brief is now good, the contract is tight, the grant is narrow, and the honest question — whether the child produced a better outcome than the parent would have inline — has not been asked yet, let alone measured.
Subagents — read the current version at https://daviddominguez.dev/learn/en/subagents/a-brief-carries-conclusions-not-the-transcript-that-produced-them