Context economics
Where the tokens go when agents run as workers.
This page records what we measured on one real project, what we learned from it, and what we changed in this practice because of it. The rules themselves live in rules/working-rules.md and skills/herdr/SKILL.md. This page explains why they say what they say.
The project
A desktop and web app, built over six days by a lead agent session that wrote briefs and ran hc workers in Herdr panes. The work had milestones (a layout engine, a file round trip, a document model), then a cloud service, a live co-editing core and a browser head. Opus did the algorithms, parsers, security and fix work. Sonnet did the feature and UI slices. Every worker ran with --effort high.
We measured every session from its Claude Code transcript (~/.claude/projects/<project>*/*.jsonl): each assistant message carries a usage block with input_tokens, output_tokens, cache_read_input_tokens and cache_creation_input_tokens.
What we measured
| Who | Model | Turns | Cache reads | Output | Average context per turn |
|---|---|---|---|---|---|
| Workers | Opus | 17,907 | 3,510 M | 19.6 M | 196k |
| Workers | Sonnet | 5,195 | 786 M | 4.6 M | 151k |
| Lead | Opus | 4,590 | 1,299 M | 3.3 M | 283k |
- 155 sessions. 42 of them ended above 280k context.
- About 5.6 billion tokens were read again from the cache. About 28 million tokens were output. Output is under 1% of the tokens processed.
What we learned
- The cost is the context that each turn reads again, not the output. Every turn sends the whole conversation again. A turn at 280k costs about as much to read as a turn at 30k costs fourteen times over. So the size of the context that a session carries decides its cost far more than how much it writes.
- Effort is not the lever. Effort changes how much a model thinks and writes. That is the small part of the bill. A higher effort also fills the context faster, which makes the expensive part bigger and makes a worker reach its cap sooner. We kept
high. - Big briefs cost twice. A brief with four or more deliverables drove a worker to the 300k cap. It stopped with
PARTIAL, and a second worker started by reading the first worker’s report and the code again. A brief sized to finish under about 200k avoids both the high context and the second start. - The lead was the outlier. A worker has a brief and a cap, so it ends. The lead had neither. It ran for many hours, so its average context per turn (283k) was the highest of all, and it was about a quarter of all cache reads.
- Waiting in the foreground is expensive. The lead waited for workers with
hc wait --children any --timeout 580in a loop, and for CI with check-ins.hc waitreturns on every timeout and on every status change (a worker going idle while its tests run counts). Each return was a lead turn, and each turn read about 280k again, to learn that nothing had happened. - The model split was right. The slices that went to Opus (a network-facing parser, a convergence algorithm, auth, a live server room) found and fixed real high-severity defects before anything went live. The Sonnet slices passed in one part or stopped only at the cap. Keep strong models where a mistake is expensive, and use the cheaper model for features.
What we changed
| Change | Where | How it works |
|---|---|---|
| Size briefs to finish under about 200k: about two deliverables, or three only when each one is small; split bigger jobs into parts on one branch | rules/working-rules.md, skills/herdr/SKILL.md “Brief” |
The lead writes part a, merges or stacks it, then writes part b from part a’s report. 300k stays as the hard cap. |
| A budget for the lead: write a handoff at about 250k | rules/working-rules.md; skills/herdr/scripts/budget-watch |
budget-watch now also reads the lead’s own ctx from its screen (hc read --lines 8 "$HC_TERMINAL_ID") and sends the lead one message when it passes the lead cap (third argument, default 250). The lead writes a handoff at the next quiet point; a fresh lead starts near 30k. |
| Wait out of context | skills/herdr/scripts/wait-event; skills/herdr/SKILL.md “Wait” |
The lead runs wait-event as a background command. It loops hc wait itself and ends with one line only when a worker sends a message, exits, errors or is blocked on a prompt. Timeouts and plain status changes do not end it. --until '<command>' --every <s> also ends it when a command exits 0, for example a CI check. The lead wakes once per real event. |
wait-event reads the JSON that hc wait prints: a top-level from is a message; a child with an exit_code, a blocked or error status, or an exit reason is an event. With no live child, hc wait returns at once, so the script sleeps for --every seconds before it asks again. Its tests (skills/herdr/scripts/tests/test_wait_event.py) play a sequence of hc wait answers through a stub hc and check where it stops.
How we judge the two-deliverable size
The first version of the rule said two or three deliverables. On the day it went in, the lead saw briefs with three full deliverables reach the 300k cap, and it moved to about two in its next briefs. A worker costs about 100k before it builds anything: 57k at its first turn and about 100k by its tenth. So each split also has a cost, and too small a brief wastes context too.
The baseline is the 28 worker sessions that started after the lead moved to about two. It is not a clean test of two deliverables: some of those briefs still held three to six work items.
| Measure | Baseline (28 sessions) |
|---|---|
| Median peak context | 272k |
| Sessions that peaked at 280k or more | 9 (32%) |
Sessions that reported PARTIAL at the context cap |
7 (25%) |
| Brief under 4 KB: sessions at 280k or more | 0 of 12 |
| Brief of 4 KB or more: sessions at 280k or more | 9 of 16 |
Judge the rule after the next 20 worker sessions on a project that starts with it. Count the work items in each brief first, and leave out a brief that broke the rule.
- Keep it when 2 or fewer of the 20 report
PARTIALat the context cap, and 3 or fewer peak at 280k or more. - Go back to two or three when the median peak is under 150k and one job needs more workers than the same size of job did before. The briefs are then too small, and the extra start-up cost is the waste.
- Replace it when the briefs keep to the rule and the cap rate stays near 25%. The count is then the wrong measure. Look next at brief size and at the start-up context.
How to measure your own project
Sum the usage blocks of your sessions. A short Python script is enough:
import glob, json
reads = out = turns = 0
for f in glob.glob("<home>/.claude/projects/<project-folder>*/*.jsonl"):
for line in open(f, errors="ignore"):
try:
m = json.loads(line).get("message") or {}
except ValueError:
continue
u = m.get("usage") if isinstance(m, dict) else None
if u:
turns += 1
reads += u.get("cache_read_input_tokens", 0)
out += u.get("output_tokens", 0)
print(turns, reads / turns, out) # turns, average context per turn, output
Split it by directory (the lead’s project folder against the worktrees’ folders) and by message.model to see who carries the context. The number to watch is the average context per turn.