All articlesAI Engineering

You know what your agent spent. You don't know what it bought.

Three numbers you can measure: cost per token, cost per request, cost per outcome. Only the last one tells you anything a business can act on - and the attempts that failed belong in the denominator.

Oct 14, 202611 min read

You know what your agent spent. You don't know what it bought.

Every model provider will tell you what you spent, to the penny, broken down by model and by day. Not one of them can tell you what the money accomplished, because that isn't something they can see from where they sit.

That gap is why so many teams can recite their monthly AI bill to the penny and still have no idea whether the thing is worth running.

Three numbers, only one of which is useful

Cost per token is the number the invoice gives you. It tells you about the provider's pricing, not about your product. Tokens are an input you don't control and a unit your business has never once cared about.

Cost per request is better, and it's where most teams stop. It at least maps to something that happens in your system. But a request is not a result — and the relationship between the two is where all the interesting problems hide.

Cost per outcome is the business number. What did it cost to resolve one support ticket, draft one document, reconcile one statement? That's the figure you can put next to what the work used to cost, or what you charge for it.

We've gestured at this before — in cost, speed, quality and in how we evaluate models, both of which say measure dollars per completed task rather than per call. This post is about the part we skipped: actually doing it is harder than it sounds, and the difficulty is where the insight lives.

First, somebody has to say what an outcome is

You cannot infer this. No amount of instrumentation discovers that a sequence of calls constitutes "a resolved ticket," because that's a statement about your business, not about your traffic.

So it has to be declared. The product says: this is a unit of work, it started here, it ended there, and this is how it ended.

The useful test is whether somebody outside engineering would recognise the thing as having happened. "Forty-one model calls" is not an outcome. "The client's quarterly pack went out" is. If a unit of work can't be described to the person paying for it, it's probably a step rather than an outcome.

gometering/unit.go
// A unit of work is declared when it starts and closed when it ends, however
// it ends. Every model call made on its behalf carries its id, so the cost is
// summed rather than estimated.
type WorkUnit struct {
    ID         string   // threaded through every call made on its behalf
    Kind       string   // "ticket.resolve", "document.draft", "statement.reconcile"
    Customer   string   // who it was done for
    User       string   // an identifier, never a name or an email
    StartedAt  time.Time
    EndedAt    time.Time
    Outcome    Outcome  // how it actually ended
    CostMicros int64    // millionths of a dollar, summed from every call
}

type Outcome string

const (
    Succeeded Outcome = "succeeded" // the thing the user wanted happened
    Abandoned Outcome = "abandoned" // they gave up, or the session died
    Escalated Outcome = "escalated" // a human had to take over
    Failed    Outcome = "failed"    // we could not complete it
)

Two fields in there do most of the work, and both are easy to leave out.

Customer and user turn a cost into a business fact. Total spend tells you to worry; spend per customer tells you which account is unprofitable, and spend per user tells you whether three people are driving half the bill. Record the user as an opaque identifier — the reasons not to put names and emails into telemetry apply here as much as anywhere.

Outcome has to include the unhappy endings. Which is the next problem.

The denominator has to include the failures

Here's the mistake that makes this metric lie.

If you only count work that succeeded, you get a flattering number that falls as your system gets worse. Imagine an agent that resolves half the tickets it attempts. Measure cost per successful resolution and you've silently excluded every abandoned attempt — all of which spent real money.

So record every attempt, and track two figures:

  • Cost per attempt — what you actually spend, every time the agent tries.
  • Cost per success — total spend across all attempts, divided by the ones that worked.
TEN ATTEMPTS — ALL OF THEM PAID FOR✓—✓✓——✓—✓—total spend ÷ 10 attemptscost per attemptthe same total ÷ 5 that finishedcost per successthe distance between these two is the number nobody is watching
Count only the successes and you get a flattering figure that improves as your system gets worse.

The gap between those two is your efficiency, and it's the single most diagnostic number in the whole system. A narrow gap means most attempts finish. A wide one means you're paying repeatedly for work that goes nowhere — and no per-call metric will ever show you that, because each individual call looks fine.

An agent that is cheap per call and expensive per outcome is the normal failure. It's also invisible to every dashboard your provider gives you.

Then you have to connect spend to the thing it was spent on

One outcome is rarely one call. A single resolved ticket might involve a planning step, four retrieval calls, two tool calls, a retry after a timeout, a summarisation pass, and a sub-agent that made three calls of its own. Some of those are cheap. The retry wasn't.

UNIT OF WORKticket.resolvewu_4f2a91planning step×1wu_4f2a91retrieval calls×4wu_4f2a91tool calls×2wu_4f2a91retry after a timeout×1wu_4f2a91summarisation pass×1wu_4f2a91sub-agent, calling on its own×3wu_4f2a91ONETOTALtwelve calls, one identifier — any call missing it becomes unattributed spend
Attribution is bookkeeping, not cleverness: the identifier opens once and rides along.

The mechanism is simple and the discipline isn't: open an identifier when the unit of work starts, thread it through every call made on its behalf, and attribute each cost to it as it's incurred.

gometering/record.go
// Called from the one place every model call passes through, so a new agent
// or a new tool is counted the day it ships rather than the day somebody
// remembers to instrument it.
func (m *Meter) RecordCall(ctx context.Context, u Usage) {
    unit := workUnitFrom(ctx) // the id that was opened when this job started
    if unit == "" {
        // No unit means we cannot attribute this spend to anything. Count it
        // loudly rather than discarding it: unattributed cost is the metric
        // that tells you your instrumentation has a hole in it.
        m.unattributed.Add(u.CostMicros)
        return
    }
    m.ledger.Append(ctx, Debit{
        Unit:       unit,
        Model:      u.Model,
        CostMicros: u.CostMicros,
        At:         now(ctx),
    })
}

The branch that matters is the empty one. Unattributed spend is a metric, not an error to swallow. It is the number that tells you your instrumentation has holes — a new agent somebody shipped without wiring it up, a background job nobody counted, a retry path that loses the context. If that figure is a meaningful share of your bill, every other number on this page is wrong, and you'd never know.

One architectural note: this belongs at the one point every model call already passes through, not sprinkled across each agent. Teams that instrument agent-by-agent end up with drift — several copies of nearly the same recording code, each slightly different, each missing something the others caught. We know, because that's exactly what we had.

What changes once you can see it

Four things, roughly in order of how quickly they pay for themselves.

You find the expensive workflow, not the expensive model. The instinct when the bill rises is to move to a cheaper model. Cost per outcome usually points somewhere else entirely: one workflow retrying, one agent re-reading the same documents, one path that falls back to the expensive model more often than anyone realised.

You can tell whether a change helped. A prompt rewrite that reduces cost per call by a fifth and raises cost per outcome by a third is a loss, and it will read as a win on every conventional measure. This is the same trap as a quality check that reports success: the number improved, the system got worse.

You can decide where the expensive model is worth it. The trade between cost, speed and quality is unresolvable in the abstract and obvious with this number in hand. A stronger model that costs more per call but succeeds far more often is frequently cheaper per outcome — and you can only defend that choice with the figure.

You can price it. If you sell software that does work on a customer's behalf, cost per outcome is the floor under your pricing. Without it you're guessing, and the guess is wrong in the direction of your largest customer.

Where this doesn't work

Not everything has a clean outcome, and forcing one produces a number that looks rigorous and means nothing.

Open-ended assistants are the obvious case. If someone is exploring, there may be no moment where the thing is done. You can use a proxy — a session that ended without escalation, a result the user kept — but be honest that it's a proxy, and don't let it harden into a target.

The rule we use: if you can't describe the outcome to the person paying the bill, don't invent one. Measure cost per attempt, say plainly that's what it is, and spend the effort on the workflows where a real outcome exists.

The number to ask for

If you take one thing from this, make it a question to ask at your next review.

Not "what are we spending on AI this month." That number is easy, available, and tells you almost nothing. Ask instead: what does it cost us to complete one of the things this agent exists to do — and how much of that is spent on the attempts that don't finish?

Most teams can't answer. The ones that can tend to make very different decisions about which agents to keep.

Thinking about an agent like this for your team?

Describe the job you want automated and our Automation Architect blueprints it in seconds — or talk it through with the engineers who ship them.