Cost Per Completed Task: Instrumenting Agent Spend Attribution
Every provider we integrate with has a cost dashboard, and every one of them produces the same artifact: daily spend, drawn as a bar chart, trending in some direction. When a bar jumps, the dashboard's story ends. Something spent more yesterday. Which role? Which task? A retry storm? A single run that spiraled? The chart has one answer for all of these: yesterday was expensive.
Fleet-level daily spend is a vanity metric. It is the number you screenshot; it is not the number you manage. The unit that carries information is cost per completed task, and computing it requires attribution — every unit of spend joined back to the task that caused it. You cannot manage what you cannot attribute.
This is the fourth cost lens we've written about, and it is deliberately not the other three. Containment is budgets, retry caps, and circuit breakers — stopping spend from going nonlinear. The harness floor is fixed overhead — what a session costs before it does anything. Spend authority is permission — what an agent may spend, with whose credential. This post is measurement: what was spent, attributed to the work that spent it. You can have all three of the others and still be unable to answer "why did Tuesday cost double."
Thread one identifier through everything
Attribution starts with a boring decision made early: every task gets an ID, and that ID rides along everywhere work happens on the task's behalf.
- Into the environment of every spawned process. Our orchestrator sets a task-ID environment variable when it spawns a worker. The worker inherits it, and so does every tool the worker shells out to. No tool needs to be told what task it is serving; it reads the variable.
- Into outbound API calls, wherever the provider allows tagging. Metadata fields, user identifiers, project groupings — whatever the surface offers. Provider-side grouping is what lets you reconcile their ledger against yours later.
- Into append-only logs. Every tool that does anything billable writes a JSONL line: timestamp, action, task ID. The write costs nothing. The absence of it costs you the join.
The property that makes this survivable is the same one that makes agent state survivable generally: files and rows, not ambient context. An agent "knowing" which task it is working on does not survive the process exiting. An environment variable stamped into every log line does. When you later ask what task 11562 cost, the answer is a grep and a join — not archaeology.
Provider cost APIs are hostile inputs
Here is the part nobody warns you about: once you go to reconcile your task-tagged logs against provider cost endpoints, the endpoints themselves will fight you. Not out of malice — cost reporting is a secondary product everywhere, built for dashboards rather than pipelines. Treat the responses like untrusted input. Four patterns we have hit across integrations, stated generically because they generalize:
Silent truncation defaults. Cost endpoints paginate, and default page sizes are small. Ask for thirty days of daily data, get the first handful of days back, no error, no warning — just a sum that is quietly a fraction of reality. Always set the page limit explicitly, or paginate until the API says there is nothing left, and assert that the date range you got back covers the date range you asked for.
String-typed amounts. A cost field that reads "0.249" is not the number 0.249. Sum it naively and you get a type error if you are lucky, or silent string concatenation if you are not. Cast at the boundary, immediately, before any arithmetic.
Grouping or collapse. Without an explicit grouping parameter, some endpoints collapse all spend into a single default bucket — every project, every workload, one number. The parameter is not a refinement; it is the difference between attribution and an aggregate. The default is always the aggregate.
Coverage gaps. Usage sub-endpoints lag the product lines they report on. A newer offering may not appear in the dedicated usage endpoint at all, only in the general cost endpoint under a line-item grouping. If a number looks too low, check whether the endpoint has heard of the thing you are buying.
One access note, and only one: some organization-level cost endpoints require admin-scoped credentials that ordinary project keys cannot satisfy. Plan that before building the pipeline, not after the first 403.
Retry amplification: charge failures to the task
A task that fails three times and succeeds on the fourth costs roughly four times what it appears to cost. In a daily aggregate, that multiplier is invisible — smeared across the bar chart like everything else. Attributed per task, it is glaring.
Our scar here is specific. Early on, our retry logic reset failed tasks to ready unconditionally. One task hit an upstream failure that no retry could ever fix and re-ran 319 times before anyone noticed. On the daily chart, that day looked moderately expensive — elevated, not alarming. Attributed by task ID, one task accounted for the bulk of the day's agent spend, and the query that showed it took seconds. The retry budget we run now — three attempts, then permanent failure — came directly from that incident. But the budget was the fix. The detection came from attribution. A retry storm is a per-task pathology, and only per-task accounting can see one.
The accounting rule that falls out: failed attempts do not get their own line. Their cost rolls into the task — either into the attempt that eventually completes, or into a permanent-failure bucket. That bucket is its own trend line, and it is worth watching, because a fleet that fails cheap and often looks efficient on any attempted-task metric.
"Completed" is a gate, not a claim
Which is why the denominator matters as much as the numerator. Cost per attempted task rewards a fleet that fails fast and constantly. Completed has to mean passed a verification gate — a separate reviewer checked the work — not "the agent printed done." We have written before about why success criteria need to be explicit; the cost metric inherits that definition wholesale. Loosen it and the metric inflates the same way the completions did.
With an honest denominator, plot cost per completed task per role, over time. This is where the metric earns its keep, because regressions localize. One role's line doubles week over week while its completion count holds flat: something changed in that role's instructions, tools, or inputs, and you investigate one role instead of a fleet. The monthly bill would have shown you the same regression four weeks later, denominated in nothing.
The honest seam
Attribution is not free. Tagging is a discipline tax on every new tool — every spawn path has to propagate the ID, every integration needs the metadata plumbing, and someone has to notice when a log line stops carrying the field. Cross-provider normalization is genuinely ugly: one provider groups by project, another by tag, a third not at all, and for that one you join on timestamps against your own logs and accept the fuzz. Amounts arrive in different types and units. You own the joins. No product currently does this for a fleet of heterogeneous agents, so expect glue code.
But the glue code is cheap next to the alternative, which is managing a fleet with a bar chart.
Once the per-role number exists, it stops being a report and becomes an input. If you know what a completed task costs by role, you can route work cheap-first and escalate only when the verification gate fails — which turns the metric into a scheduler. That's the next post.