<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Token Usage Meter for AI Agents on RockB</title><link>https://baeseokjae.github.io/tags/token-usage-meter-for-ai-agents/</link><description>Recent content in Token Usage Meter for AI Agents on RockB</description><image><title>RockB</title><url>https://baeseokjae.github.io/images/og-default.png</url><link>https://baeseokjae.github.io/images/og-default.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Thu, 01 Oct 2026 09:12:23 +0000</lastBuildDate><atom:link href="https://baeseokjae.github.io/tags/token-usage-meter-for-ai-agents/index.xml" rel="self" type="application/rss+xml"/><item><title>Agent Token Usage Meter: How to Measure and Control AI Agent Costs in 2026</title><link>https://baeseokjae.github.io/posts/token-usage-meters-for-agent-cost-tracking/</link><pubDate>Thu, 01 Oct 2026 09:12:23 +0000</pubDate><guid>https://baeseokjae.github.io/posts/token-usage-meters-for-agent-cost-tracking/</guid><description>An agent token usage meter logs the provider&amp;#39;s per-call usage object, splits input, output and cache-read tokens, then converts each to dollars.</description><content:encoded><![CDATA[<p>An agent token usage meter is the component that reads the provider&rsquo;s <code>usage</code> object on every model response, keeps input, output, and cache-read tokens separate, and converts those counts into dollars at log time using a versioned price table. It meters a run, not a request, because one agent task fans out into dozens of model calls.</p>
<p>That is the short answer. The rest of this guide separates the four quantities people keep collapsing into one number, shows where the ground truth actually lives, explains the OpenTelemetry contract the industry is converging on, and states plainly where the current tooling still fails.</p>
<h2 id="what-an-agent-token-usage-meter-actually-has-to-measure">What an Agent Token Usage Meter Actually Has to Measure</h2>
<p>The single most common metering bug is reporting &ldquo;tokens used&rdquo; as one figure. Four distinct quantities flow through every agent, and they are not priced remotely the same way.</p>
<table>
	<thead>
			<tr>
					<th>Quantity</th>
					<th>Provider field (Anthropic)</th>
					<th>Provider field (OpenAI)</th>
					<th>How it is priced</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Fresh input tokens</td>
					<td><code>input_tokens</code></td>
					<td><code>prompt_tokens</code></td>
					<td>Full input rate per MTok</td>
			</tr>
			<tr>
					<td>Cache-read tokens</td>
					<td><code>cache_read_input_tokens</code></td>
					<td><code>input_tokens_details.cached_tokens</code></td>
					<td>Roughly 10% of the input rate on standard models</td>
			</tr>
			<tr>
					<td>Cache-write tokens</td>
					<td><code>cache_creation_input_tokens</code></td>
					<td><code>input_tokens_details.cache_write_tokens</code></td>
					<td>Above the input rate: 1.25x for a 5-minute write, 2x for 1 hour</td>
			</tr>
			<tr>
					<td>Output tokens</td>
					<td><code>output_tokens</code></td>
					<td><code>completion_tokens</code></td>
					<td>Full output rate per MTok, typically 3-5x input</td>
			</tr>
			<tr>
					<td>Reasoning tokens</td>
					<td>included in output</td>
					<td><code>completion_tokens_details.reasoning_tokens</code></td>
					<td>Billed as output</td>
			</tr>
			<tr>
					<td>Dollars</td>
					<td>not reported</td>
					<td>not reported</td>
					<td>Computed by you</td>
			</tr>
	</tbody>
</table>
<p>The practical consequence: any meter that does not break out cached input cannot tell you where the money went. Cache-read tokens bill at roughly 10% of the normal input rate, so the cached slice of a long session costs about a tenth of what the same tokens would cost fresh. Collapse cache-read into &ldquo;input tokens&rdquo; and you overstate that slice by up to 10x on exactly the workloads — long agent runs — where spend matters most.</p>
<p>There is also a boundary question hiding in the table. An agent run is not one request. A single task can issue 30 model calls across planning, tool use, retries, and synthesis. If your meter increments once per request and never aggregates upward, you will never be able to answer the only question finance asks: what did this task cost?</p>
<h2 id="where-the-numbers-come-from-provider-payloads-agent-sdks-and-admin-apis">Where the Numbers Come From: Provider Payloads, Agent SDKs, and Admin APIs</h2>
<p>There are three places to read usage, in increasing order of usefulness.</p>
<p><strong>1. The provider&rsquo;s per-response <code>usage</code> object — the ground truth.</strong> Every response from Anthropic and OpenAI carries token counts. This is the only authoritative source and it is free. Never estimate tokens with a character heuristic or a locally loaded tokenizer; you will be wrong by a margin large enough to make the meter useless, and you will be wrong precisely on the inputs that dominate your bill (tool output, JSON, code).</p>
<p><strong>2. The agent framework&rsquo;s run-level aggregation.</strong> The OpenAI Agents SDK tracks usage automatically for every run and exposes it at <code>result.context_wrapper.usage</code>, with <code>requests</code>, <code>input_tokens</code>, <code>output_tokens</code>, <code>total_tokens</code>, a per-request <code>request_usage_entries</code> breakdown, and detail fields for <code>cached_tokens</code>, <code>cache_write_tokens</code>, and <code>reasoning_tokens</code>. Crucially, that aggregation covers every model call the run made, including calls that produced tool calls or handoffs — the run is the natural metering boundary. Two details trip people up. First, streaming Chat Completions providers return no usage unless you explicitly set <code>ModelSettings(include_usage=True)</code>, so a streaming-based meter silently logs zeros. Second, with a session, each <code>Runner.run(...)</code> reports usage for that run only, but prior messages are re-fed as input, so input token counts compound turn over turn even though the per-call view looks flat. <code>RunResult.to_state()</code> snapshots accumulated usage so a resumed run continues from those totals; nested <code>Agent.as_tool()</code> runs are the deliberate exception, rolling their post-resume usage up into the outer run.</p>
<p><strong>3. Provider administrative usage APIs — for reconciliation, not for metering.</strong> Anthropic&rsquo;s Usage &amp; Cost Admin API reports usage with <code>bucket_width</code> of <code>1d</code>, <code>1h</code>, or <code>1m</code> (default limits 7, 24, and 60 buckets; maximums 31, 168, and 1,440), grouped by model, <code>workspace_id</code>, <code>api_key_id</code>, <code>inference_geo</code>, and <code>speed</code> (the last requiring the fast-mode beta header), with a separate <code>/v1/organizations/cost_report</code> endpoint that returns USD cents grouped by workspace or description at daily granularity only. Data typically appears within about five minutes. This is the right tool for a monthly invoice reconciliation and the wrong tool for a per-agent cost dashboard: it is minutes-late, coarse, and has no idea which of your agents caused the spend.</p>
<p>The three-tier framing worth internalizing: the vendor dashboard is a daily aggregate with no attribution, the agent&rsquo;s own counter is real-time but unattributed, and logging the per-call <code>usage</code> field <strong>with a label</strong> is the actual meter. Only the third can answer &ldquo;which feature, which customer, which tool call caused this.&rdquo;</p>
<h2 id="the-opentelemetry-genai-contract-gen_ai-attributes-and-counters">The OpenTelemetry GenAI Contract: gen_ai.* Attributes and Counters</h2>
<p>You do not want dashboards built around each provider&rsquo;s raw field names, because <code>prompt_tokens</code> and <code>input_tokens</code> and whatever Gemini calls it next year will keep diverging. The vendor-neutral schema is the OpenTelemetry GenAI semantic conventions, and the honest framing is that the <em>shape</em> is right while the <em>status</em> is still provisional.</p>
<p>On 12 June 2026, in semantic-conventions v1.42.0, the GenAI conventions moved out of the core repository into the dedicated <code>open-telemetry/semantic-conventions-genai</code> repository so the area could iterate faster than the core stability bar allows. As of October 2026 every <code>gen_ai.*</code> definition is still Development status; only inherited attributes like <code>error.type</code>, <code>server.address</code>, and <code>server.port</code> are Stable. Attribute names can therefore be renamed without a deprecation window. Pin the conventions version your dashboards assume.</p>
<p>A reference request-level event looks like this:</p>
<table>
	<thead>
			<tr>
					<th>Attribute</th>
					<th>Purpose</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>gen_ai.operation.name</code></td>
					<td>Which operation: chat, embeddings, invoke_agent, execute_tool</td>
			</tr>
			<tr>
					<td><code>gen_ai.request.model</code></td>
					<td>Model requested</td>
			</tr>
			<tr>
					<td><code>gen_ai.response.model</code></td>
					<td>Model actually served (differs on routing/fallback)</td>
			</tr>
			<tr>
					<td><code>gen_ai.usage.input_tokens</code></td>
					<td>Fresh input tokens</td>
			</tr>
			<tr>
					<td><code>gen_ai.usage.cache_read_input_tokens</code></td>
					<td>Cache-read tokens</td>
			</tr>
			<tr>
					<td><code>gen_ai.usage.output_tokens</code></td>
					<td>Output tokens</td>
			</tr>
			<tr>
					<td><code>gen_ai.response.finish_reasons</code></td>
					<td>Why generation stopped (truncation shows up here)</td>
			</tr>
			<tr>
					<td><code>llm.request_cost_usd</code></td>
					<td>Custom: dollars computed at log time</td>
			</tr>
			<tr>
					<td><code>llm.pricing_version</code></td>
					<td>Custom: which price table produced that number</td>
			</tr>
	</tbody>
</table>
<p>Two conventions are easy to get wrong. First, the spec deliberately does not define dollar cost. Compute it yourself from token counts and a price table you version alongside the standard usage fields — otherwise a provider price change silently rewrites the meaning of your historical data. Second, give every agent a unique <code>gen_ai.agent.name</code>; omit it and the span shows as <code>Unknown</code>, which destroys attribution. Sub-agents need their own distinct names rather than inheriting the parent&rsquo;s, and the <em>caller</em> should emit the <code>invoke_agent</code> span rather than the agent being called, so each agent&rsquo;s work and token spend stay separated in the trace.</p>
<p>The token counters themselves carry a required <code>gen_ai.token.modality</code> attribute (<code>text</code>, <code>image</code>, <code>audio</code>, <code>unknown</code>), so summing across modality equals the total even for providers that report no breakdown. The spec is also opinionated about truthfulness in a way most homegrown meters are not: when a system reports both used and billable tokens, instrumentation <strong>must</strong> report billable tokens, and instrumentation that cannot obtain token counts efficiently <strong>must not</strong> report usage metrics at all. That second rule exists because a fabricated number is worse than a missing one.</p>
<h2 id="why-two-instrument-families-exist--and-the-p95-trap">Why Two Instrument Families Exist — and the p95 Trap</h2>
<p>OpenTelemetry splits token metering into two instrument families, and conflating them produces quietly wrong dashboards.</p>
<p><strong>Usage counters</strong> — <code>gen_ai.client.inference.usage.input_tokens</code> and <code>.output_tokens</code>, plus cache-read, cache-write, and reasoning subsets — are cumulative and are the only correct source for totals and cost approximation.</p>
<p><strong>Per-operation histograms</strong> — <code>gen_ai.client.inference.operation.input_tokens</code> and <code>.output_tokens</code> — describe the distribution of a <em>single operation</em>. They must never be summed or averaged to produce a total.</p>
<p>Here is the trap, stated concretely. Run 100 operations that each consume 100 text tokens plus 200 image tokens. Add <code>gen_ai.token.modality</code> as a dimension on the histogram and your p95 reads <strong>200</strong>, because each modality series only sees its own slice. The true p95 of total tokens per operation is <strong>300</strong>. Nothing errors. The dashboard simply lies, and you make a capacity or caching decision on a number that is off by a third.</p>
<p>Recommended histogram boundaries are powers of four: per-operation token histograms use <code>[1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864]</code>; <code>gen_ai.invoke_agent.duration</code> uses boundaries from 0.1 to 409.6 seconds; <code>gen_ai.client.operation.duration</code> uses 0.01 to 81.92 seconds.</p>
<p>The agent-level instruments sit alongside the client-level ones: <code>gen_ai.invoke_agent.duration</code> (Histogram, unit <code>s</code>), <code>gen_ai.invoke_agent.inference_calls</code> (unit <code>{inference_call}</code>), <code>gen_ai.invoke_agent.tool_calls</code> (unit <code>{tool_call}</code>, explicitly including failed calls), <code>gen_ai.invoke_workflow.duration</code>, and <code>gen_ai.execute_tool.duration</code>. The failed-calls detail matters: retry storms are a top cause of runaway spend, and a tool-call counter that only counts successes hides them.</p>
<h2 id="from-tokens-to-dollars-the-cost-formula-and-the-cache-multiplier">From Tokens to Dollars: The Cost Formula and the Cache Multiplier</h2>
<p>The formula is boring, which is the point:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>cost = (fresh_input_tokens  x input_price / 1e6)
</span></span><span style="display:flex;"><span>     + (cache_read_tokens   x input_price x 0.10 / 1e6)
</span></span><span style="display:flex;"><span>     + (cache_write_tokens  x input_price x cache_write_multiplier / 1e6)
</span></span><span style="display:flex;"><span>     + (output_tokens       x output_price / 1e6)
</span></span></code></pre></div><p>The only non-obvious multiplier is the cache-read discount: 0.10 of the input rate for most models, and lower still on the premium ones (0.025 on Fable 5.1 and Mythos 5.1, 0.05 on Opus 5.5 and Sonnet 5.5). A worked example at Sonnet 5&rsquo;s published rates — $2 per MTok input, $0.20 per MTok cache read, $10 per MTok output — makes the stakes clear: 18 turns, 2.4M input tokens of which 1.6M were cache reads, plus 40K output tokens. That is 0.8M fresh input × $2 + 1.6M cache reads × $0.20 + 40K output × $10, which lands at about <strong>$2.32</strong> for the task. Charge those same 1.6M tokens fresh instead and the session lands near $5.20. Nothing about the session changed except the cache-hit rate, and it is the difference between a cheap task and a mid-priced one.</p>
<p>Three implementation rules follow.</p>
<p><strong>Compute cost at log time, not in a monthly reporting job.</strong> Pricing tables, model names, cache rules, and tool charges change quickly. A cost computed weeks later uses whatever price table exists then, not the one that applied when the tokens were spent. Store <code>llm.request_cost_usd</code> next to the raw token counts and stamp it with <code>llm.pricing_version</code>.</p>
<p><strong>Version the price table separately from the conventions.</strong> These drift on different schedules. A spec bump that renames an attribute must not silently rewrite historical cost, and a price change must not invalidate your dashboards&rsquo; schema assumptions.</p>
<p><strong>Handle the models that cannot be inferred.</strong> Cost inference works by matching the model name to a definition holding a price per usage type and multiplying by the observation&rsquo;s usage — this is how Langfuse&rsquo;s infer-then-ingest path works, preferring an ingested cost from the response when one is present. But reasoning models such as the OpenAI o1 family cannot have cost inferred at all without token counts, so ingested usage is required. In Langfuse, only generation and embedding observations carry usage or cost; other observation types do not, and usage keys must match the model definition&rsquo;s keys exactly or the multiplication silently no-ops.</p>
<h2 id="choosing-your-metering-boundary-call-run-session-or-customer">Choosing Your Metering Boundary: Call, Run, Session, or Customer</h2>
<p>The metering boundary determines what you can actually bill or optimize, and it is a design decision you make <em>before</em> picking a tool.</p>
<table>
	<thead>
			<tr>
					<th>Boundary</th>
					<th>Question it answers</th>
					<th>Typical use</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Per model call</td>
					<td>Is this prompt/tool call expensive?</td>
					<td>Prompt and context optimization</td>
			</tr>
			<tr>
					<td>Per agent run</td>
					<td>What did this task cost?</td>
					<td>Feature and workflow costing</td>
			</tr>
			<tr>
					<td>Per session</td>
					<td>What did this conversation cost?</td>
					<td>Support economics, session caps</td>
			</tr>
			<tr>
					<td>Per customer or key</td>
					<td>What do I invoice?</td>
					<td>Usage-based billing, entitlements</td>
			</tr>
			<tr>
					<td>Per agent identity</td>
					<td>Which agent is burning budget?</td>
					<td>Multi-agent fleet governance</td>
			</tr>
	</tbody>
</table>
<p>A meter built only at the call level answers the first question and structurally cannot answer the others. A meter built only at the customer level tells you a number moved without telling you which agent moved it. The OpenAI Agents SDK&rsquo;s choice to aggregate at the run boundary is a good default because a run maps to a unit of work a human can reason about — but you still need a stable identifier threaded through the run (task id, ticket id, feature flag) to roll runs up into a customer view. LiteLLM takes the complementary approach: <code>spend_logs_metadata</code> can be attached per key, team, or request tag, so attribution travels with the request rather than being reconstructed afterward.</p>
<h2 id="attributing-spend-to-what-caused-it">Attributing Spend to What Caused It</h2>
<p>A total is alarming; a breakdown is actionable. Four cost drivers account for most agent spend, and each needs its own label.</p>
<p><strong>File reads.</strong> Re-reading the same large file across turns is pure replay cost. Label these calls and a single bad tool-loop pattern becomes visible.</p>
<p><strong>Unfiltered tool output.</strong> A shell command or API response that dumps 100K tokens into context is paid for on every subsequent turn of the session, not once. This is the single largest source of accidental spend in coding agents.</p>
<p><strong>The MCP manifest.</strong> Every connected MCP server contributes tool definitions to the context on every turn — a constant per-turn tax independent of the task. Fifteen servers with verbose schemas can add thousands of input tokens per turn whether or not a single one of those tools is called. This is the most under-instrumented line item in agent cost tracking, because it never appears as a dramatic spike; it appears as a slightly-too-high baseline forever.</p>
<p><strong>Transcript replay.</strong> In a sessioned agent, prior messages are re-fed as input on each run, so input tokens compound. The per-call view hides this; a session-level view exposes it immediately.</p>
<p>Tagging each call with a category converts &ldquo;our agent costs $4,000 a month&rdquo; into &ldquo;40% of spend is transcript replay, 25% is one unfiltered log dump.&rdquo; Only the second sentence produces an engineering ticket.</p>
<h2 id="turning-the-meter-into-a-control-budgets-resets-and-kill-switches">Turning the Meter into a Control: Budgets, Resets, and Kill Switches</h2>
<p>Cost tracking without an enforcement edge is observability theatre. The same meter that produces the dashboard should feed a limit.</p>
<p>LiteLLM is the clearest reference implementation: <code>max_budget</code> and <code>budget_duration</code> are set per key, user, or team, with <code>budget_reset_at</code> defining the replenishment cycle, so enforcement lives next to the meter rather than in a separate reporting job. Provider-specific pricing is applied automatically when the response carries tier metadata (Vertex AI PayGo and priority pricing, Bedrock service tiers, Azure base-model mapping). The maintenance requirement is explicit: model pricing data has to be kept in sync from the upstream repository or reported costs diverge from the provider bill.</p>
<p>Three enforcement patterns are worth having, in escalating severity:</p>
<ol>
<li><strong>Soft alert</strong> — a threshold on cost per feature or cost per workspace that pages a human. Cheap, catches drift.</li>
<li><strong>Hard cap</strong> — a budget keyed to a customer, team, or agent identity that refuses new requests when exceeded. This is what converts a runaway retry loop into an error message instead of an invoice.</li>
<li><strong>Circuit breaker</strong> — a per-run ceiling that aborts the run itself. An agent stuck in a tool loop is metered and stopped before turn 40 rather than after.</li>
</ol>
<p>OpenMeter approaches the same problem from the billing side: it meters LLM tokens with a CloudEvents event (type <code>prompt</code>, with token counts in <code>data.tokens</code> and <code>subject</code> set to the customer), aggregated <code>SUM</code> with <code>groupBy</code> over provider, model, and type. It is Apache-2.0, and its production users include Trigger.dev and Requestly — but self-hosting means running PostgreSQL, ClickHouse, Kafka, and the API server as four separate components, which is a real operational commitment for a company that has not already standardized on event-driven metering.</p>
<h2 id="the-tool-landscape-what-each-layer-actually-does">The Tool Landscape: What Each Layer Actually Does</h2>
<table>
	<thead>
			<tr>
					<th>Tool</th>
					<th>Layer</th>
					<th>What it gives you</th>
					<th>Licensing / scale (Oct 2026)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>LiteLLM</td>
					<td>Proxy / gateway</td>
					<td>Spend tracking for 100+ LLMs, per key/user/team budgets, daily spend breakdown API</td>
					<td>59,970 GitHub stars, actively pushed</td>
			</tr>
			<tr>
					<td>Langfuse</td>
					<td>Observability</td>
					<td>Usage details and cost details per generation, infer-then-ingest pricing, metrics API</td>
					<td>35,256 stars</td>
			</tr>
			<tr>
					<td>MLflow</td>
					<td>Experiment + tracking</td>
					<td>Run-level metrics including token counts and cost</td>
					<td>28,207 stars, Apache-2.0</td>
			</tr>
			<tr>
					<td>OpenLLMetry (Traceloop)</td>
					<td>Instrumentation</td>
					<td>OTel-native auto-instrumentation that emits GenAI spans</td>
					<td>7,463 stars, Apache-2.0</td>
			</tr>
			<tr>
					<td>Helicone</td>
					<td>Gateway / observability</td>
					<td>Request-level cost and usage with caching</td>
					<td>6,190 stars, Apache-2.0</td>
			</tr>
			<tr>
					<td>OpenLIT</td>
					<td>Instrumentation</td>
					<td>OTel-native LLM instrumentation</td>
					<td>2,809 stars, Apache-2.0</td>
			</tr>
			<tr>
					<td>OpenMeter</td>
					<td>Metering / billing</td>
					<td>Token metering as billable events, entitlements, credit balances</td>
					<td>2,356 stars, Apache-2.0</td>
			</tr>
			<tr>
					<td>OTel GenAI semconv</td>
					<td>Standard</td>
					<td>The <code>gen_ai.*</code> schema itself</td>
					<td>401 stars, Development status</td>
			</tr>
	</tbody>
</table>
<p>Read that last row carefully. The most important artifact in this table — the thing everything else is converging on — is the smallest repository in it. The metering primitives (proxies, SDKs, instrumentation libraries) are mature and commercially backed; the <em>standard</em> is young and explicitly provisional. That asymmetry is the defining risk of building a cost meter in 2026: your tooling will outlive the attribute names it currently emits.</p>
<p>The practical hedge is to keep raw provider token counts and a price table in your own store, in addition to whatever the vendor records. If <code>gen_ai.*</code> names change — and they can, without a deprecation window — you can re-derive your history from first principles instead of losing it.</p>
<h2 id="building-a-minimal-meter-today-log-tag-roll-up">Building a Minimal Meter Today: Log, Tag, Roll Up</h2>
<p>You do not need to adopt a platform to start metering. The minimum viable meter is roughly forty lines of code.</p>
<p><strong>Step 1 — Log the usage object on every response.</strong> Capture the raw provider payload, unmodified, before any normalization. Include the model actually served, not just the model requested.</p>
<p><strong>Step 2 — Attach business metadata at the same moment.</strong> Task or ticket id, feature name, customer or workspace id, agent name, sub-agent name if any, environment. This is the step teams skip and then regret, because metadata cannot be retroactively recovered.</p>
<p><strong>Step 3 — Compute cost immediately</strong> using a price table stored in the repository under version control, and write <code>cost_usd</code> and <code>pricing_version</code> into the same record. Never recompute later.</p>
<p><strong>Step 4 — Roll up on read, at whatever boundary the question needs.</strong> Request-level records are the atomic fact; run, session, feature, and customer views are aggregations over them. Doing it this way means adding a new dashboard dimension later requires no new instrumentation.</p>
<p><strong>Step 5 — Reconcile monthly.</strong> Compare your rolled-up totals against the provider&rsquo;s admin usage and cost endpoints, or the invoice itself. A meter that has never been reconciled is a meter that might be wrong in a way nobody has noticed. Discrepancies usually trace to one of four things: a model routing fallback you did not log, cache-write tokens omitted from the formula, a price table that was not updated, or tokens served by a provider path your instrumentation does not wrap.</p>
<h2 id="dashboards-and-alerts-that-earn-their-keep">Dashboards and Alerts That Earn Their Keep</h2>
<p>Most token dashboards get abandoned because they show a total that goes up. Five panels actually drive decisions.</p>
<p><strong>Cost per feature or per workflow.</strong> The only number product owners can act on. If &ldquo;document summarization&rdquo; costs 12x &ldquo;chat,&rdquo; that is a roadmap input.</p>
<p><strong>Tokens per request and per session, as distributions, not averages.</strong> The most useful panel is outlier requests. A single runaway request, a huge context window, or a retry loop distorts daily spend, and an average hides exactly that. Alert on tokens per request, tokens per session, cost per feature, and cost per workspace.</p>
<p><strong>Cache-hit rate, next to spend.</strong> This is the highest-leverage dial most teams never touch. Since cache reads bill around 10% of the input rate, a cache-hit-rate improvement of 30 points is worth more than most prompt-engineering projects — and it is measurable.</p>
<p><strong>MCP manifest overhead per turn.</strong> A flat line that you can actually shrink by disconnecting unused servers. It is the rare cost driver that is both boring and easy to fix.</p>
<p><strong>Reconciliation delta.</strong> Your computed total minus the provider&rsquo;s. Trend it monthly; a growing delta means your price table or your instrumentation has drifted.</p>
<p>For engineering, request-level outliers and latency correlation come from the same events. For finance, an immutable per-period aggregate that reconciles to the invoice comes from the same events. Emit request-level telemetry once and roll it up for both audiences rather than maintaining a monthly spreadsheet.</p>
<h2 id="common-mistakes">Common Mistakes</h2>
<p><strong>Counting output tokens and ignoring input.</strong> Output is the smaller number in almost every agent workload. Input, inflated by transcript replay, typically dominates.</p>
<p><strong>Ignoring cache entirely.</strong> Either direction is wrong: treating cache reads as full-price input overstates the cached tokens by up to 10x, and treating cached tokens as free understates it.</p>
<p><strong>Estimating with a local tokenizer or a character heuristic.</strong> The count will be wrong, and wrong most severely on exactly the dense content (tool output, code, JSON) that drives agent cost. The provider already gives you the number for free.</p>
<p><strong>Measuring one call instead of the whole run.</strong> A single prompt looks cheap; the 34-call run around it is the actual cost unit.</p>
<p><strong>Assuming streaming responses report usage.</strong> They do not by default on Chat Completions providers; you need an explicit usage flag, and without it your meter logs zeros with no error.</p>
<p><strong>Summing a histogram.</strong> Per-operation token histograms with a breakdown dimension produce a p95 that is lower than reality — 200 instead of 300 in the worked example — with no warning.</p>
<p><strong>Putting every sub-agent under the parent&rsquo;s name.</strong> Attribution collapses and you cannot tell which agent in a fleet is the expensive one.</p>
<p><strong>Never reconciling against the invoice.</strong> Silent drift in a price table or a routing fallback is invisible until someone compares totals. Do it monthly.</p>
<h2 id="what-changes-next">What Changes Next</h2>
<p>Three things are moving, and each has a defensive posture.</p>
<p><strong>The conventions will be renamed.</strong> <code>gen_ai.*</code> moved to its own repository in June 2026 and every GenAI attribute remains Development status, so renames can happen without a deprecation window. Keep raw token counts and your own price table so a spec change cannot orphan your history.</p>
<p><strong>Agent-level instruments are filling in.</strong> The instrumentation is shifting from &ldquo;client call&rdquo; to &ldquo;agent invocation,&rdquo; with <code>invoke_agent</code>, <code>invoke_workflow</code>, and <code>execute_tool</code> instruments joining the client-level ones. Cost dashboards built purely on client-side request counts will need to be re-scoped as this lands.</p>
<p><strong>Pricing drift is permanent.</strong> Cache multipliers, tiered pricing, and reasoning-token accounting all change faster than quarterly. Version your price table, stamp every cost record with that version, and reconcile against the invoice. Everything else in a cost meter can be approximately right; the price table cannot.</p>
<p>The teams that get this right in 2026 are not the ones with the most sophisticated dashboards. They are the ones who logged the usage object with a label on day one, kept four numbers separate, computed cost at write time, and pointed a budget limit at the same meter. None of those steps require buying anything.</p>
<h2 id="faq">FAQ</h2>
<p><strong>What is an agent token usage meter?</strong></p>
<p>It is the component that captures the provider&rsquo;s <code>usage</code> object on every model response made by an agent, keeps input, output, cache-read, and cache-write tokens as separate fields, attaches business labels (task, feature, customer, agent name), and converts the counts into dollars at log time using a versioned price table. The distinguishing feature versus generic token counting is that it meters an agent <em>run</em> — dozens of model calls — rather than a single request.</p>
<p><strong>Why can&rsquo;t I just read my provider dashboard for agent costs?</strong></p>
<p>Provider dashboards report daily aggregates with no attribution to which agent or feature caused the spend, and administrative usage APIs are minutes-late and coarse (Anthropic&rsquo;s, for example, defaults to 7 daily buckets and offers daily granularity on the cost report). They are excellent for reconciling a monthly invoice and structurally incapable of telling you that one sub-agent&rsquo;s retry loop cost $300 yesterday. Only per-call logging with labels answers that.</p>
<p><strong>How much does prompt caching actually save on agent workloads?</strong></p>
<p>Cache-read tokens bill at roughly 10% of the normal input rate, so the tokens that hit cache cost about a tenth of what they would fresh. Worked at Sonnet 5&rsquo;s published rates, 18 turns with 2.4M input tokens (1.6M of them cache reads) plus 40K output tokens comes to about $2.32 for the task, against roughly $5.20 if that same input were charged fresh. This is why cache-hit rate belongs on the cost dashboard next to spend, not in a separate caching doc.</p>
<p><em><em>Should I use OpenTelemetry gen_ai.</em> attributes or my provider&rsquo;s raw field names?</em>*</p>
<p>Use <code>gen_ai.*</code> as your dashboard schema so you are not rewriting queries when you add a provider, and persist the raw provider payload alongside it so you can re-derive history if attribute names change. Be aware that every <code>gen_ai.*</code> attribute is still Development status as of October 2026 and can be renamed without a deprecation window. Also note the spec intentionally does not define dollar cost — you compute that yourself and version the price table that produced it.</p>
<p><strong>Where should I put the enforcement limit?</strong></p>
<p>At the same layer as the meter, and at the boundary that matters to you. Per-key or per-team <code>max_budget</code> with a <code>budget_duration</code> and <code>budget_reset_at</code> (as LiteLLM implements it) converts a runaway loop into a refused request; a per-run ceiling aborts the run itself. A meter with no enforcement edge only tells you, after the fact, how expensive the outage was.</p>
<h2 id="sources">Sources</h2>
<ul>
<li>Anthropic, Usage and Cost Admin API (bucket widths, default and maximum limits, group-by dimensions, cost report granularity, ~5 minute freshness) — <a href="https://docs.claude.com/en/api/usage-cost-api">https://docs.claude.com/en/api/usage-cost-api</a></li>
<li>Anthropic, Prompt caching (cache pricing table, 0.10x cache-read multiplier and per-model exceptions, 1.25x 5-minute and 2x 1-hour write premiums, <code>cache_read_input_tokens</code> / <code>cache_creation_input_tokens</code>) — <a href="https://docs.claude.com/en/docs/build-with-claude/prompt-caching">https://docs.claude.com/en/docs/build-with-claude/prompt-caching</a></li>
<li>OpenAI, Prompt caching (cache-read discount, <code>input_tokens_details.cached_tokens</code> and <code>input_tokens_details.cache_write_tokens</code>) — <a href="https://platform.openai.com/docs/guides/prompt-caching">https://platform.openai.com/docs/guides/prompt-caching</a></li>
<li>OpenAI Agents SDK, Usage (<code>result.context_wrapper.usage</code>, <code>request_usage_entries</code>, <code>ModelSettings(include_usage=True)</code>, <code>RunResult.to_state()</code>, nested <code>Agent.as_tool()</code> roll-up) — <a href="https://openai.github.io/openai-agents-python/usage/">https://openai.github.io/openai-agents-python/usage/</a></li>
<li>OpenTelemetry GenAI semantic conventions, inference token metrics (<code>gen_ai.client.inference.usage.*</code> counters vs per-operation histograms, <code>gen_ai.token.modality</code>, billable-token rule, powers-of-four boundaries) — <a href="https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-token-metrics.md">https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-token-metrics.md</a></li>
<li>OpenTelemetry GenAI semantic conventions, agent and tool metrics (<code>gen_ai.invoke_agent.*</code>, <code>gen_ai.invoke_workflow.duration</code>, <code>gen_ai.execute_tool.duration</code>, boundaries) — <a href="https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-metrics.md">https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-metrics.md</a></li>
<li>OpenTelemetry semantic-conventions CHANGELOG v1.42.0 (12 June 2026: GenAI conventions moved to the dedicated repository) — <a href="https://github.com/open-telemetry/semantic-conventions/blob/main/CHANGELOG.md">https://github.com/open-telemetry/semantic-conventions/blob/main/CHANGELOG.md</a></li>
<li>LiteLLM, Cost tracking (<code>max_budget</code> / <code>budget_duration</code>, provider tier pricing applied automatically, model cost map kept in sync from GitHub) — <a href="https://docs.litellm.ai/docs/proxy/cost_tracking">https://docs.litellm.ai/docs/proxy/cost_tracking</a></li>
<li>LiteLLM, Virtual keys and budgets (<code>budget_reset_at</code> per key, user or team) — <a href="https://docs.litellm.ai/docs/proxy/users">https://docs.litellm.ai/docs/proxy/users</a></li>
<li>Langfuse, Token and cost tracking (infer-then-ingest order, price per usage type, o1 reasoning models needing ingested tokens, generation/embedding-only cost) — <a href="https://langfuse.com/docs/observability/features/token-and-cost-tracking">https://langfuse.com/docs/observability/features/token-and-cost-tracking</a></li>
<li>OpenMeter, documentation (CloudEvents usage events, <code>SUM</code> aggregation, groupBy, Apache-2.0, four-component self-host) — <a href="https://openmeter.io/docs">https://openmeter.io/docs</a></li>
<li>Repository and licence checks by the reviewer on 2026-10-07 — <a href="https://github.com/BerriAI/litellm">https://github.com/BerriAI/litellm</a> ; <a href="https://github.com/langfuse/langfuse">https://github.com/langfuse/langfuse</a> ; <a href="https://github.com/mlflow/mlflow">https://github.com/mlflow/mlflow</a> ; <a href="https://github.com/traceloop/openllmetry">https://github.com/traceloop/openllmetry</a> ; <a href="https://github.com/Helicone/helicone">https://github.com/Helicone/helicone</a> ; <a href="https://github.com/openlit/openlit">https://github.com/openlit/openlit</a> ; <a href="https://github.com/openmeterio/openmeter">https://github.com/openmeterio/openmeter</a> ; <a href="https://github.com/open-telemetry/semantic-conventions-genai">https://github.com/open-telemetry/semantic-conventions-genai</a></li>
</ul>
]]></content:encoded></item></channel></rss>