---
title: "TokenOps: why a 40-turn agent costs 16×, not 4×"
description: An agent loop re-reads its whole context on every turn, so uncached input cost grows with the square of the turns. Gareth Bland's TokenOps talk at Microsoft explains the price list behind that, a three-ratio score for whether tokens bought anything, and the S-curve that tells you when to stop spending.
date: 2026-10-08
tags: AI agents, LLM costs, observability, agentic engineering
---

A task your agent finishes in 10 turns costs you 1×. Let it run 40 turns and you'd expect 4×. It's **16×**. Every turn re-sends the context that the earlier turns built up, so uncached input cost grows with the square of the turns, not in a straight line. The fix is to engineer the context so most of it is read from the cache, at a tenth of the price.

That's the opening of [TokenOps](https://www.youtube.com/watch?v=B4yovi_znxE), a four-minute talk by Gareth Bland, chief data scientist at Microsoft Frontier Company. He defines TokenOps as the data science that lets a company keep deciding where its tokens should go: where they move the needle most. Short talk, three ideas worth keeping.

## The price list

Model providers don't charge one price per token. Taking your input messages as 1×, this is the scale he showed:

| Token | Cost | What it is |
|---|---|---|
| Output | 5× | Where reasoning happens, the most useful thing an agent does |
| Base input | 1× | The messages you send |
| Cache write | extra | Paid as the loop writes context to the cache |
| Cached input | 0.1× | Context read back from the cache |

So the expensive part is also the valuable part: reasoning is output. The part you control is how much of the input is cached.

<figure class="fd-fig to-price-fig" aria-labelledby="to1-cap" data-desc="The token price scale from the talk, as bars, with your input messages as 1×. Output costs 5×: that's where reasoning happens, the most useful thing an agent does. Base input, the messages you send, costs 1×. Cache writes cost extra, paid as the loop writes context to the cache; the talk gave no multiplier for them. Cached input, context read back from the cache, costs 0.1×. The cached share of the input is the part you control.">
<div class="to-in">
<ul class="to-bars">
<li class="to-bar to-bar-out"><p class="to-name">Output<span>where reasoning happens</span></p><p class="to-track"><i class="to-w5"></i></p><p class="to-x">5×</p></li>
<li class="to-bar"><p class="to-name">Base input<span>the messages you send</span></p><p class="to-track"><i class="to-w1"></i></p><p class="to-x">1×</p></li>
<li class="to-bar to-bar-write"><p class="to-name">Cache write<span>paid as the loop writes to the cache</span></p><p class="to-track"><i class="to-w1"></i></p><p class="to-x">extra</p></li>
<li class="to-bar to-bar-cached"><p class="to-name">Cached input<span>read back from the cache</span></p><p class="to-track"><i class="to-w01"></i></p><p class="to-x">0.1×</p></li>
</ul>
</div>
<p class="mp-strip"><b>Yours to control</b><span>How much of the input comes from the cache.</span></p>
<figcaption id="to1-cap">The price scale he showed, with your input messages as 1×. Output, base input and cached input are drawn to scale. The talk gave no multiplier for cache writes, so that bar is only marked, not measured.</figcaption>
</figure>

## Loops make it quadratic

Agents don't answer in one shot anymore. They take turns: act, look at the result, reason, act again. Each turn carries everything that came before.

| Turns | You'd expect | Uncached input actually costs |
|---|---|---|
| 10 | 1× | 1× |
| 40 | 4× | 16× |

Four times the turns, sixteen times the cost. Context engineering is how you bring that curve down: keep the stable part of the context in front and unchanged, so the provider can serve it from the cache at 0.1×.

<figure class="fd-fig to-quad-fig" aria-labelledby="to2-cap" data-desc="Why 40 turns cost 16 times 10 turns, drawn to scale. Turns run left to right, from 0 to 40. The height at each turn is the context that turn re-sends, so the area is what uncached input costs. The first 10 turns make one small triangle: 1×. On the left is the straight-line guess, four of those triangles side by side for 4×. On the right is what actually happens: the context keeps growing, each turn re-reads everything before it, and the area for 40 turns is a triangle four times as wide and four times as tall, sixteen small triangles: 16×. The fix is to keep the stable part of the context in front and unchanged, so the provider can serve it from the cache at 0.1×.">
<div class="mp-two-row">
<div class="mp-two-card">
<p class="mp-two-who">You'd expect</p>
<p class="mp-two-name">4×</p>
<svg class="to-tri" viewBox="0 0 220 78" aria-hidden="true">
<polygon class="to-tile to-one" points="20,58 65,58 65,13"/><polygon class="to-tile to-c1" points="65,58 110,58 110,13"/><polygon class="to-tile to-c2" points="110,58 155,58 155,13"/><polygon class="to-tile to-c3" points="155,58 200,58 200,13"/>
<path class="to-base" d="M20,58 H200"/>
<text class="to-tick" x="65" y="74" text-anchor="middle">10</text><text class="to-tick" x="110" y="74" text-anchor="middle">20</text><text class="to-tick" x="155" y="74" text-anchor="middle">30</text><text class="to-tick" x="200" y="74" text-anchor="middle">40</text>
</svg>
<p class="mp-two-line">Four times the turns</p>
<p class="mp-two-line">Every 10 turns cost the same</p>
</div>
<p class="mp-two-vs">vs</p>
<div class="mp-two-card mp-two-peter">
<p class="mp-two-who">Uncached input actually costs</p>
<p class="mp-two-name">16×</p>
<svg class="to-tri" viewBox="0 0 220 210" aria-hidden="true">
<polygon class="to-tile to-one" points="20,190 65,190 65,145"/><polygon class="to-tile to-c1" points="65,190 110,190 110,145"/><polygon class="to-tile to-c1" points="65,145 110,145 110,100"/><polygon class="to-tile to-c1" points="65,190 65,145 110,145"/><polygon class="to-tile to-c2" points="110,190 155,190 155,145"/><polygon class="to-tile to-c2" points="110,145 155,145 155,100"/><polygon class="to-tile to-c2" points="110,100 155,100 155,55"/><polygon class="to-tile to-c2" points="110,190 110,145 155,145"/><polygon class="to-tile to-c2" points="110,145 110,100 155,100"/><polygon class="to-tile to-c3" points="155,190 200,190 200,145"/><polygon class="to-tile to-c3" points="155,145 200,145 200,100"/><polygon class="to-tile to-c3" points="155,100 200,100 200,55"/><polygon class="to-tile to-c3" points="155,55 200,55 200,10"/><polygon class="to-tile to-c3" points="155,190 155,145 200,145"/><polygon class="to-tile to-c3" points="155,145 155,100 200,100"/><polygon class="to-tile to-c3" points="155,100 155,55 200,55"/>
<path class="to-out" d="M20,190 H200 V10 Z"/>
<text class="to-tick" x="65" y="206" text-anchor="middle">10</text><text class="to-tick" x="110" y="206" text-anchor="middle">20</text><text class="to-tick" x="155" y="206" text-anchor="middle">30</text><text class="to-tick" x="200" y="206" text-anchor="middle">40</text>
</svg>
<p class="mp-two-line">Each turn re-reads everything before it</p>
<p class="mp-two-line">Four times the turns, sixteen times the cost</p>
</div>
</div>
<p class="mp-strip"><b>The fix</b><span>Keep the stable part of the context in front and unchanged, so it's read from the cache at 0.1×.</span></p>
<figcaption id="to2-cap">Drawn to scale, turns 0 to 40 left to right. The height at each turn is the context it re-sends, so the area is what you pay for uncached input. The ink triangle is the first 10 turns, 1×. Forty turns is sixteen of them, not four.</figcaption>
</figure>

## Did the tokens buy anything?

Cost alone says nothing about whether the spend was worth it. Bland scores agent work with an **agentic efficiency score** made of three ratios, read as a funnel from all the work an agent starts down to what users feel:

<div class="levels" role="group" aria-label="The agentic efficiency score as a funnel of three ratios: autonomous completion, token efficiency and value generation, with merging into production and serving users as the steps between them">
  <div class="level">
    <p class="level-num">1</p>
    <h3>Autonomous completion</h3>
    <p>Work finished without failure or a human stepping in.</p>
  </div>
  <div class="level-step"><span>Merged to production</span></div>
  <div class="level">
    <p class="level-num">2</p>
    <h3>Token efficiency</h3>
    <p>Of the finished work, what actually shipped.</p>
  </div>
  <div class="level-step"><span>Serving users</span></div>
  <div class="level level--focus">
    <p class="level-num">3</p>
    <h3>Value generation</h3>
    <p>Of what shipped, what moved the needle.</p>
  </div>
</div>

The third ratio is the one a token dashboard can't give you. My own [Meterlex](/projects/meterlex.html) prices every model call from the session logs, which covers the cost side. It can't tell me whether a session's work got merged, let alone whether anyone used it. That needs data from the repo and the product, not the model provider.

## The S-curve: when to stop spending

The last idea is about where a product sits. Features pay off on an S-curve, and Bland names three zones:

<div class="levels" role="group" aria-label="Three zones of the feature value S-curve: underinvestment, leverage and gold plating, with a threshold and saturation as the steps between them">
  <div class="level">
    <p class="level-num">Zone 1</p>
    <h3>Underinvestment</h3>
    <p>Not enough features yet for an MVP or a pilot.</p>
  </div>
  <div class="level-step"><span>Threshold</span></div>
  <div class="level level--focus">
    <p class="level-num">Zone 2</p>
    <h3>Leverage</h3>
    <p>Every new feature or improvement brings value back to users.</p>
  </div>
  <div class="level-step"><span>Saturation</span></div>
  <div class="level">
    <p class="level-num">Zone 3</p>
    <h3>Gold plating</h3>
    <p>Polishing what exists, adding what nobody asked for.</p>
  </div>
</div>

Agents make zone 3 cheap to enter. When a feature costs a prompt instead of a sprint, nothing stops you gold plating except knowing where you are on the curve. That's his closing point: in agentic engineering, knowing where your team or your next feature sits on the S-curve matters most.

## What I take from it

- **Measure the curve, not the bill.** A monthly total hides the quadratic part. Turns per task and the cached share of input say where the money goes.
- **Cost is the top of the funnel.** Token spend only means something next to how much of the work merged and how much of it users felt.
- **Spend where the curve is steep.** The cheapest token is the one you don't spend gold plating.

<figure class="fd-fig to-take-fig" aria-labelledby="to3-cap" data-desc="My three takeaways from the talk, side by side. One: measure the curve, not the bill. A monthly total hides the quadratic part; turns per task and the cached share of input say where the money goes. Two: cost is the top of the funnel. Token spend only means something next to how much of the work merged and how much of it users felt. Three: spend where the curve is steep. On the feature S-curve, the steep middle is where tokens bring value back; the flat top is gold plating, and the cheapest token is the one you don't spend there.">
<ol class="mp-trio">
<li class="mp-trio-step">
<svg class="mp-grow-art" viewBox="0 0 120 110" aria-hidden="true">
<rect class="to-bill" x="8" y="8" width="30" height="40"/>
<rect class="mp-ln" x="13" y="15" width="20" height="3"/><rect class="mp-ln" x="13" y="22" width="16" height="3"/><rect class="mp-ln" x="13" y="29" width="20" height="3"/>
<rect class="to-total" x="13" y="38" width="20" height="4"/>
<path class="hx-axis-l" d="M10,58 V100 H112"/>
<path id="to3-curve" class="to-curve" d="M10,99 Q78,99 112,20"/>
<g class="fd-token fd-token-ui"><rect x="-4" y="-4" width="8" height="8"><animateMotion dur="4s" repeatCount="indefinite"><mpath href="#to3-curve"/></animateMotion></rect></g>
</svg>
<p class="mp-trio-n"><b>1</b>Measure the curve</p>
<p class="mp-trio-what">Turns per task and the cached share of input, not the monthly bill</p>
</li>
<li class="mp-trio-step">
<svg class="mp-grow-art" viewBox="0 0 120 110" aria-hidden="true">
<rect class="to-f to-f-cost" x="8" y="14" width="104" height="20"/>
<rect class="to-f to-f-merged" x="27" y="45" width="66" height="20"/>
<rect class="to-f to-f-felt" x="45" y="76" width="30" height="20"/>
</svg>
<p class="mp-trio-n"><b>2</b>Cost is the top</p>
<p class="mp-trio-what">It means something next to what merged and what users felt</p>
</li>
<li class="mp-trio-step">
<svg class="mp-grow-art" viewBox="0 0 120 110" aria-hidden="true">
<path class="hx-axis" d="M42,8 V100 M82,8 V100"/>
<path class="hx-axis-l" d="M6,100 H114"/>
<path class="to-flat" d="M6,94 C26,94 34,92 42,86"/>
<path id="to3-steep" class="to-steep" d="M42,86 C56,74 66,34 82,24"/>
<path class="to-gold" d="M82,24 C94,17 104,16 114,16"/>
<g class="fd-token fd-token-ui"><rect x="-4" y="-4" width="8" height="8"><animateMotion dur="3s" repeatCount="indefinite"><mpath href="#to3-steep"/></animateMotion></rect></g>
</svg>
<p class="mp-trio-n"><b>3</b>Spend where it's steep</p>
<p class="mp-trio-what">The cheapest token is the one you don't spend gold plating</p>
</li>
</ol>
<figcaption id="to3-cap">What I'd watch after this talk: the shape of the cost, the funnel under it, and where a feature sits on the S-curve. The dashed top of the last curve is gold plating.</figcaption>
</figure>
