What agents are sent each turn¶
Every request a runtime makes to its model carries the whole conversation so far: the system prompt, every tool's schema and the history. Most of it is cached by the provider, and a cached token is billed at a tenth of the price or less. The token-compression research (#464) found that compression tools save little. This page measures what the app itself adds to each request, and records what was changed because of it.
Measured on 2026-10-08, from the agents on Alex's Mac and from the app's own code.
Where the tokens go¶
scripts/turn-usage.sh adds up every turn's usage, as each runtime reported it, for every
agent on this Mac. On 2026-10-08 it read:
| Runtime | Turns | Uncached input | Cache reads | Cache writes | Output |
|---|---|---|---|---|---|
| Claude | 1,278 | 51,876 | 4,033 M (98.5%) | 61 M (1.4%) | 12.5 M |
| Codex | 153 | 427,027 | 14.8 M (97.1%) | none reported | 13,704 |
| OpenCode | 29 | 9,095 | 1.66 M (99.4%) | none reported | 3,300 |
The percentages are shares of all input. For Claude, at its list prices (a cache read is 0.1× input, a cache write 1.25×, output 5×), cache reads are about three quarters of the cost, cache writes about a seventh and output about a ninth. Uncached input is almost nothing.
So the size of what is re-read on every request matters more than anything sent once. Each tool schema is re-read on every model call, and a turn makes many calls: Claude's turns read 3.2 M cached tokens each on average, against a median context of 128 K at the agent's last turn.
The app's own tools¶
These numbers were measured by ToolCostTests, a test of the Swift host that went with it
(#1314): what the app's MCP server lists, a tool at a time, as compact JSON. A token is taken
as about four bytes. To measure the Go host's list, call tools/list on a session's app
tools endpoint and count the bytes of the answer.
| Session | Bytes | About |
|---|---|---|
| An agent the person started (23 tools) | 42,623 | 10,650 tokens |
| An agent another agent started (19 tools) | 36,965 | 9,240 tokens |
| Grok's rules, which repeat the tools in its system prompt | 7,436 | 1,860 tokens |
| The briefing, once per conversation: Claude's and Grok's system prompt, the first prompt elsewhere (Claude) | 2,745 | 690 tokens |
Measured on 2026-10-10, after publish_event was removed (#639). manage_workflows is the largest at 19,910 bytes, then
start_agent (now start_session) at 3,398; every other tool is under 2,800. suggest_next_prompt adds
476 bytes (~119 tokens). finish_turn was 8,509 until it was split (below).
The briefing went with the Swift host (#1370) and came back in Go with three paragraphs, leases, events and how a turn ends (#1801), then the rest (#1806): who is who, live documents, asking and deciding, workflows, helpers and sessions, with worktrees, residue and a persona where they apply. That made archive_session 207 bytes longer (~50 tokens), and the briefing above is measured from brief.go on 2026-10-20, with nothing declared and no persona. Grok's rules came back with #1806 too: 9,007 bytes (~2,250 tokens) on 2026-10-20, from grokRules in brief.go.
What a runtime does with the list differs:
- Claude defers MCP tools behind its own tool search. A session sees each tool's name only, and pays for a schema once the agent loads it. The app's tools cost a Claude agent almost nothing until it uses them.
- Grok hides MCP tools behind
search_tooltoo, so the app writes the tools into its rules instead (see Why agents' own tools are taken away). That is about 2,200 tokens, fixed for the session. - Codex, OpenCode, Gemini, Copilot, Cursor and Antigravity send every listed schema with every request: about 10,650 tokens, against the 97 K a Codex turn reads on average.
A project's own connectors (MCP servers) are added on top, in the same way for each runtime. They are
the person's choice, and they are not measured here. To measure one, list its tools
(tools/list) and count the bytes of the answer.
ToolCostTests held the lead's list under 45,000 bytes. Since the Swift host went (#1314),
no test holds a ceiling; the tools' words are in Packages/Wire/app-tools.json, and a change
there is where to look.
What changed¶
Keys in one order. The app wrote its tool list, and every message it sends a runtime, with each object's keys in the order the process happened to hash them. That order is different every time the host starts. After a restart, an agent picked back up was sent the same tools with their fields reordered. Where a runtime passes a schema on as sent, that is a different prompt prefix, and the provider's cache misses on all of it. The app now writes keys sorted, so the same tools are the same bytes in every process.
Every turn's usage, kept per turn. Each turn's usage was already recorded in the
transcript. turns.jsonl now carries it beside each turn too, added up when a turn
reports more than once, so turns can be compared without reading whole transcripts.
scripts/turn-usage.sh --since DATE compares runtimes from a date on, and --turns prints
one line per turn.
finish_turn split into focused tools (#481). One tool carried every job at the end
of a turn: the outcome, the title, the next prompt, labels, parking, waiting on agents or a
time, and moves. Its description explained which arguments could not go together, and
agents still got that wrong. It now says only how the turn went. Waiting is on
wait_for_event (agents, wake_on, until_minutes), parking itself on park_agent
with no id (asking to be archived on request_archive since #584, archive_session since #638), moves on move_worktree, and labels on set_session_labels. The next prompt
was dropped. Old calls still work: finish_turn reads every argument it took before.
Measured by ToolCostTests (Swift, before #1314) |
Before | After |
|---|---|---|
finish_turn |
8,509 bytes (~2,130 tokens) | 1,154 bytes (~290 tokens) |
The tools it gave jobs to: wait_for_event, park_agent, archive_agent |
3,215 | 4,034 |
The new tools: move_worktree, set_session_labels |
none | 1,372 |
| An agent the person started | 36,386 bytes (~9,100 tokens), 21 tools | 31,222 bytes (~7,800 tokens), 23 tools |
finish_turn removed. After the split, all it still did was say how a turn went, and
the app already works that out (#479): Complete, or Waiting on your answer when a
question is open or the agent's last words ask one, under the first sentence of its last
message. The runtime names the conversation. The outcomes only an agent could give
(nothing to do, partly done, stuck) went with it: an agent that needs the person asks them.
That takes its ~1,100 bytes (~280 tokens) off every session's tool list. A conversation
that began before then can still call it, and the daemon still records what it says.
| An agent another agent started | 30,070 bytes (~7,500 tokens) | 25,577 bytes (~6,400 tokens) |
| Grok's rules | 8,992 bytes | 7,792 bytes |
That is about 1,300 tokens a request off every session on the runtimes that send every schema, two more tools to choose from notwithstanding.
What was left alone, and why¶
No tool was taken off an agent. Claude makes 88% of the turns measured and already
defers the app's tools. On the other runtimes, the largest cut that would not take away
something agents use, leaving manage_workflows off agents started by other agents,
saves about 2,150 tokens a request, about 2% of what a Codex turn reads, and it costs
an agent a tool it may be asked to use.
The start of each prompt was already stable. The briefing goes with the first prompt only, and stays in the history from then on. What the app adds to a later prompt (a moved folder, an edit to a live page, why a workflow started) comes in that prompt, after everything already cached. Tools are listed in a fixed order, and plugin folders and MCP servers are read in a fixed order. Nothing per turn goes into a system prompt. The one thing that moved was the key order above.
No compaction threshold was set. An earlier threshold makes later requests smaller.
But each compaction is a model call over the whole history, and its result has to be
cached again. It also loses detail the agent may still need. Whether that is a net saving
depends on how long agents run after compacting, and no measurement shows it yet. With
usage now kept per turn, scripts/turn-usage.sh --turns can compare an agent's turns
before and after a compaction. The levers each runtime offers are in
Compaction in each runtime.
Related¶
- Why agents' own tools are taken away, for what each runtime keeps.
- Tools the app gives agents, for the full list.
- Compaction in each runtime.