Tracking tokens is not the same as tracking cost
Cost is derived from token counts, a price table and a rollup across a trace. Recent pull requests in two projects show each of those layers failing alone.
Tracking tokens is not the same as tracking cost. A token count is measured; a dollar figure is computed from it, using a price table for that model and a rollup that decides which calls belong to the thing you are asking about. Each of those three layers can be wrong on its own, and none of them fails loudly — a wrong cost number looks exactly like a right one. Three items from the past week, in two open-source projects, are each a different one of them.
A cache token is not an input token
A CrewAI pull request titled "fix(anthropic): include cache tokens in totals" describes what it changed:
include Anthropic cache reads and cache writes in normalized input and total token counts
Before that change, a workload using prompt caching under-reported its own token totals. The second line of the same summary matters more:
preserve cache read and cache creation counters as separate usage dimensions
Separate is the whole requirement. On Anthropic's pricing, cache reads cost about a tenth of base input and writes about 1.25×, so three different rates apply to three kinds of input token. Once they are added into one field, no later step can price them correctly — not the dashboard, not the invoice reconciliation, not the model-comparison chart. The information needed to divide them again is gone, and the resulting number is wrong in whichever direction the mix happened to lean.
This is also why cache tokens are the first place to look when your own ledger disagrees with a provider invoice: a blended input rate applied to a token count that silently includes cache reads produces a plausible figure that is too high, on a workload where caching was working. If caching is not working, the cause is usually upstream of the accounting — see why caching silently stops working.
A price table has a date
A feature request in Langfuse's discussions asks for something that sounds administrative and is not. The conditions it sets out (discussion #15786):
A new model releases that has updated costs, or an older model's pricing was changed
and the ask is to pick up the new prices "Without upgrading the entire langfuse instance — which might not be feasible immediately or might take a week or so".
Credit where it is due: Langfuse is free and open source, calculates cost from both ingested and inferred usage, and supports tiered pricing and custom model definitions — its cost maths is one of the better-specified parts of that ecosystem. The gap here is operational rather than a defect. If the rates live with the deployment, a provider's price change starts a window in which everything ingested is priced at the old numbers, and nothing in the resulting rows says so.
That gives a rule worth applying to whatever you use: a cost row should record which price table produced it. Re-pricing history is then a separate, ordinary operation. Without a version stamp it is not an operation at all, because you cannot tell which rows are stale.
The cost of a run is a join, not a row
The third one is structural. A Langfuse fix titled "restore 7-day evaluator costs" explains why a cost column was rendering a placeholder:
Rebuild costs per trace because evaluator metadata lives on the parent span while cost lives on child events
Generalise that and it is the central accounting problem in agent systems. The thing you want the cost of — a run, an agent, a customer, a resolved ticket — is never the thing that carries the cost. Cost lands on the leaf call; identity lives on an ancestor. Every figure you read is therefore a join, and a join does not raise an error when it drops rows. It returns a smaller number.
A placeholder in a table is the visible version of that failure and the lucky one. The unlucky version is a cost per run that is quietly light because one agent's calls never carried the run identifier, and nothing on the page distinguishes "this run cost that much" from "that much of this run was joinable".
The read-time-versus-write-time choice is the same problem seen from the other end. A LiteLLM pull request adding a session-level rollup argues that the facts its dashboard needs "are sequential (which tier served the previous turn, how long a tier had been idle), and the request that produces a turn already knows all of them, so re-deriving them at read time redoes work". Anything the call knows about itself — which agent, which run, which rule fired, which cache dimension the tokens landed in — is cheapest and most reliable to record at the call, and progressively harder to reconstruct afterwards.
What to check on your own numbers
- Are cache reads and cache writes separate fields end to end? Collapse them at any hop — SDK, framework usage metrics, your own events table — and every price downstream of that hop is a guess.
- Does a cost row say which rates produced it? If not, a mid-month price change is unrecoverable rather than merely annoying.
- Does a run total say how much of the run it covers? A rollup that joins identity to cost should also report the remainder it could not join. An unattributed bucket you can see is worth more than a total you cannot check.
- Reconcile one window against the invoice. Same billing window, the provider's timezone, exact per-model rates with cache lines kept apart, including calls that failed after being billed. Do it once and the size of the gap tells you which of the three layers to distrust.
The same three layers, in our stack
Capsera derives cost the same way everyone does: token counts times per-model rates. There is no version of this where the number is measured rather than computed, and claiming otherwise would be the wrong lesson to take from the three items above.
What is under our control is the shape of the record. Cache reads are kept distinct from base input on the event, which is what makes cache-read share per model a question you can ask at all. Agent identity and the run identifier are attached at the moment of the call, inside your process, so a run total is a group-by over rows that already agree rather than a reconstruction from a parent span — and because identity is inherited through the call context, a sub-agent spawned at runtime carries the chain without anything being threaded through it. Costs are derived from stored token counts rather than stored prompts, so the inputs to the arithmetic survive independently of the rates applied to them.
None of that makes the figure the invoice. It is still a derivation with the same three layers inside it, and the only way to learn how close it lands on your traffic is to reconcile one window and look at the gap.
Give every agent an identity, a budget, and hard limits.
One line of code. Anthropic, OpenAI, and Google Gemini.
See pricing