Chapter 05
Find costly AI users
Tokenmaxxing is so in right now. But tokenmaxxing and value-minning? Less cool.
A token leaderboard shows you your heaviest users – it doesn't show you your most expensive ones. A costly user is the one that should worry you: same work, same impact, more money.
What makes a user expensive
An expensive user isn't one who uses your product often – it's one who costs more without generating more usage or value. What triggers the cost increase is how you tell the difference.
Something you deployed triggered the cost change. A model swap or a broken cache prefix triggers this. This is something you can spot on your AI bill because everyone's cost moves on the same day. Tell an AI cost spike from real growth is the chapter that covers this.
The user's input caused it. A 40-page PDF or six-hour conversation. Nothing changed in your code, but what it costs to serve that user did. This is the tokenmaxxing, value-minning user – and where you're actually losing money.
Divide cost by use
Your top spender is at $400 this month. Is that good or bad?
You probably have no idea because spend is two numbers multiplied together: how much someone used your product, and what each use cost you.
To figure it out, you should divide cost by use. PostHog already has the data you need to do this calculation – every AI observability event carries $ai_trace_id, and one trace is one full interaction:
- The model call
- Retrieval
- Tool calls
So you already have a denominator to divide by, and that $400 splits into two very different stories. In Fig. 1, two users have the same spend, but User A is the one using your AI product efficiently.
Monthly spend = Traces×Cost per trace
| Monthly spend | Traces | Cost per trace | ||
|---|---|---|---|---|
| User A | $400 | 10,000 | $0.04 | Growth |
| User B | $400 | 3,000 | $0.13 | Regression |
This approach works well for traces. If you're building a multi-turn experience like a chatbot, use $ai_session_id instead. It groups traces into a whole conversation, and cost per session is the number that matters.
Averages hide expensive users
Watching cost per trace across your whole product works well for regressions that hit everyone, like a bad deploy or a model swap.
This doesn't reveal edge cases and outliers. A retry loop that only fires on documents over 8k tokens hits the handful of accounts that send long documents. Average that in with thousands of healthy traces and it disappears.
So one comparison isn't enough. You need two: how a user's cost per trace compares to everyone else's, and how it compares to their own last week. A user who doubled against themselves is worth a look even if they're nowhere near the top of your bill.
Anatomy of a trace
Back to User B in Fig. 1: cost per trace up 3x, routed through the same workflow. You know where the spend is going. You just don't know why yet.
So you open User B's trace, and Fig. 2 is what you find.
- $ai_tracedocument_qa200 OK · 14.2s · $0.131
- $ai_spanload_document9,200 tokens in
- $ai_generationextract_fieldsstop: max_tokens · $0.024
- $ai_generationextract_fields · retry 1stop: max_tokens · $0.024
- $ai_generationextract_fields · retry 2stop: max_tokens · $0.025
- $ai_generationextract_fields · retry 3stop: max_tokens · $0.024
- $ai_generationanswer_questionstop: end_turn · $0.034
There's 5 generations in a request that should take one, against a population median of 1.3. There's your retry loop.
Except a retry loop isn't automatically expensive. A 429 rejection bills nothing, because the provider never started generating. A generation that ran, produced tokens, and hit max_tokens bills in full. So check the stop reason before you blame the retries.
Every attempt here stopped on max_tokens, and every one carries cost. That's the finding: the provider billed four times to produce nothing, because the extraction step retries on malformed output and never gives up loudly.
Now you know what to fix.
You are not going to do this every morning
No one has time to rank users, work out the population baseline, compare every candidate against it, split by model and workflow, and examine their traces? And you need to do this every day.
So hand it to a scout. A scout runs on a schedule and you only hear about it when something needs your attention.
When it finds something, you get a report with the comparison window, the numbers you need to judge it, the cause it found in the traces, and what action to take. Fig. 3
Inbox
1 unreadOne customer's cost per trace tripled after switching to the document workflow
Four accounts moved from $0.04 to $0.13 cost per trace over two days while their trace volume held steady. All four route through the document-qa workflow, which retries the extraction step on malformed output without a cap.
Traces show a median of 5 generations per request against a population median of 1.3, and the retried generations carry non-zero cost with a max_tokens stop reason. The provider billed every retry.
4 accounts, ~$180/day above their own baseline
Cap the retry count on the document-qa extraction step and fail loudly instead of retrying on malformed output.
An agent opens the pull request. You review and merge.
Add the scout
The scout itself is just a file an agent follows on a schedule, and Fig. 4 is the one this guide is about.
The custom scout for this already lives in PostHog. You just have to enable it, and it's set to run daily at 9:00 AM.
You'll need these to run this scout.
- RequiredAI Observability instrumented, so $ai_generation events are arriving
- RequiredGenerations attributed to a user, so spend can be grouped by person rather than by trace
- RecommendedA property identifying the feature or workflow, so a cause can be located
Not set up yet? One command installs PostHog, connects GitHub, and turns on a default troop of scouts. That troop doesn't include this one – add it above once you're set up.
npx @posthog/wizard self-driving










