Skip to content
Redgold.AI Blog

· 8 min read · LLM gateway, cost, observability, coding agents

Tracking Down Agent Spending Anomalies With One Usage Ledger

Every model call our agents make is written to one ledger row with who made it and why. Here are four anomalies from the last month, what each looked like at first, and the query that explained it.

All of our model traffic goes through one gateway: coding agents, chat in the UI, background summarizers, app features. For every call, the gateway writes one row to a usage_event table in Postgres. Over the last 30 days that table gained about 1.16 million rows, 114 billion tokens, and roughly $3,080 of estimated spend.

A provider dashboard can tell you that spend went up. It cannot tell you which agent, task or feature caused it, because it only sees an API key. The ledger exists to answer that second question. Each row carries the tokens, plus the keys we found we needed when something looked wrong:

  • Who: user, API key, tenant and the agent role (a coding agent, the chat UI, a summarizer).
  • What work: session, conversation, ticket, workstream and pull request.
  • Where: model, backend, machine, pod and source address.
  • Tokens, split the way the provider reported them: uncached input, cache reads, cache writes, output and reasoning.

A command-line tool ranks spend by any of these keys over any window (top --by role, --by day, --by session and so on). It reads Postgres directly, so there is no separate analytics pipeline that can fall behind. The four cases below are all from the last month.

1. Spend more than doubled in two days

On 3 and 4 October, estimated spend jumped to $221 and $245 a day, against a 28-day average of about $100. The first instinct was a runaway agent stuck in a loop. Ranking those two days by session showed no single outlier: the top sessions each used about 3% to 7% of the total, and their average prompt size matched every other day.

Estimated spend per day0250500Sep 25: 40 USDSep 26: 59 USDSep 27: 63 USDSep 28: 71 USDSep 29: 63 USDSep 30: 73 USDOct 1: 83 USDOct 2: 106 USDOct 3: 221 USDOct 4: 245 USDOct 5: 95 USDOct 6: 101 USDOct 7: 159 USDOct 8: 106 USDOct 9: 141 USDOct 10: 74 USDOct 11: 177 USDOct 12: 96 USDOct 13: 146 USDOct 14: 88 USDOct 15: 121 USDOct 16: 81 USDOct 17: 57 USDOct 18: 127 USDOct 19: 98 USDOct 20: 94 USDOct 21: 87 USDOct 22: 93 USDSep 25Sep 29Oct 3Oct 7Oct 11Oct 15Oct 19
Estimated spend per day. All roles and both gateways, 25 September to 22 October 2026.
Show the data
USD
Sep 2540
Sep 2659
Sep 2763
Sep 2871
Sep 2963
Sep 3073
Oct 183
Oct 2106
Oct 3221
Oct 4245
Oct 595
Oct 6101
Oct 7159
Oct 8106
Oct 9141
Oct 1074
Oct 11177
Oct 1296
Oct 13146
Oct 1488
Oct 15121
Oct 1681
Oct 1757
Oct 18127
Oct 1998
Oct 2094
Oct 2187
Oct 2293

Counting distinct agent sessions per day explained it. The fleet grew from 27 coding-agent sessions on 2 October to 45 and then 51, and each session made somewhat more calls. Spend per active session moved much less than total spend did, and those were also the two busiest merge days of the month, with 197 and 182 pull requests merged. The fleet was doing more work, not repeating itself.

Coding-agent sessions active per day050100Sep 25: 19 sessionsSep 26: 23 sessionsSep 27: 19 sessionsSep 28: 20 sessionsSep 29: 19 sessionsSep 30: 22 sessionsOct 1: 28 sessionsOct 2: 27 sessionsOct 3: 45 sessionsOct 4: 51 sessionsOct 5: 47 sessionsOct 6: 43 sessionsOct 7: 48 sessionsOct 8: 49 sessionsOct 9: 38 sessionsOct 10: 29 sessionsOct 11: 37 sessionsOct 12: 38 sessionsOct 13: 36 sessionsOct 14: 36 sessionsOct 15: 19 sessionsOct 16: 15 sessionsOct 17: 15 sessionsOct 18: 13 sessionsOct 19: 17 sessionsOct 20: 29 sessionsOct 21: 24 sessionsOct 22: 20 sessionsSep 25Sep 29Oct 3Oct 7Oct 11Oct 15Oct 19
Coding-agent sessions active per day. Distinct coding-agent sessions with at least one model call, same window.
Show the data
sessions
Sep 2519
Sep 2623
Sep 2719
Sep 2820
Sep 2919
Sep 3022
Oct 128
Oct 227
Oct 345
Oct 451
Oct 547
Oct 643
Oct 748
Oct 849
Oct 938
Oct 1029
Oct 1137
Oct 1238
Oct 1336
Oct 1436
Oct 1519
Oct 1615
Oct 1715
Oct 1813
Oct 1917
Oct 2029
Oct 2124
Oct 2220

Lesson: before looking for a bug, divide by the number of things running. A spike that disappears per session is a capacity decision, not an incident. It may still be the wrong decision, but it needs a different conversation.

2. Prompts appeared to shrink by 75%

In mid-October, average input per request fell from about 125,000 tokens to as low as 20,000 on some days. That looked like good news: perhaps context trimming had started working. It was wrong.

The two providers report input differently. OpenAI counts cached tokens inside its input total and reports the cached part as a subset. Anthropic reports uncached input, cache reads and cache writes as three separate numbers. A Claude call with a 140,000-token conversation typically records a few hundred tokens of input and the rest as cache reads. When agents shifted part of their work to Claude in mid-October, the naive average fell, even though prompts had not changed.

Average input per coding-agent call099.6199.1Sep 25Sep 29Oct 3Oct 7Oct 11Oct 15Oct 19Total, week of Sep 25: 128k tokensTotal, week of Sep 26: 134k tokensTotal, week of Sep 27: 124k tokensTotal, week of Sep 28: 127k tokensTotal, week of Sep 29: 116k tokensTotal, week of Sep 30: 133k tokensTotal, week of Oct 1: 132k tokensTotal, week of Oct 2: 123k tokensTotal, week of Oct 3: 125k tokensTotal, week of Oct 4: 129k tokensTotal, week of Oct 5: 130k tokensTotal, week of Oct 6: 136k tokensTotal, week of Oct 7: 135k tokensTotal, week of Oct 8: 120k tokensTotal, week of Oct 9: 130k tokensTotal, week of Oct 10: 141k tokensTotal, week of Oct 11: 161k tokensTotal, week of Oct 12: 144k tokensTotal, week of Oct 13: 154k tokensTotal, week of Oct 14: 134k tokensTotal, week of Oct 15: 149k tokensTotal, week of Oct 16: 160k tokensTotal, week of Oct 17: 136k tokensTotal, week of Oct 18: 181k tokensTotal, week of Oct 19: 142k tokensTotal, week of Oct 20: 133k tokensTotal, week of Oct 21: 136k tokensTotal, week of Oct 22: 145k tokensTotalRaw, week of Sep 25: 115k tokensRaw, week of Sep 26: 124k tokensRaw, week of Sep 27: 124k tokensRaw, week of Sep 28: 126k tokensRaw, week of Sep 29: 116k tokensRaw, week of Sep 30: 89k tokensRaw, week of Oct 1: 123k tokensRaw, week of Oct 2: 123k tokensRaw, week of Oct 3: 125k tokensRaw, week of Oct 4: 129k tokensRaw, week of Oct 5: 130k tokensRaw, week of Oct 6: 136k tokensRaw, week of Oct 7: 134k tokensRaw, week of Oct 8: 110k tokensRaw, week of Oct 9: 125k tokensRaw, week of Oct 10: 90k tokensRaw, week of Oct 11: 55k tokensRaw, week of Oct 12: 74k tokensRaw, week of Oct 13: 72k tokensRaw, week of Oct 14: 96k tokensRaw, week of Oct 15: 101k tokensRaw, week of Oct 16: 117k tokensRaw, week of Oct 17: 20k tokensRaw, week of Oct 18: 30k tokensRaw, week of Oct 19: 48k tokensRaw, week of Oct 20: 124k tokensRaw, week of Oct 21: 121k tokensRaw, week of Oct 22: 63k tokensRawTotalRaw
Average input per coding-agent call. Thousands of tokens. “Raw” averages the provider’s input field as reported. “Total” adds cache reads and writes for Claude, whose input field excludes them.
Show the data
Week ofTotal (k tokens)Raw (k tokens)
Sep 25128115
Sep 26134124
Sep 27124124
Sep 28127126
Sep 29116116
Sep 3013389
Oct 1132123
Oct 2123123
Oct 3125125
Oct 4129129
Oct 5130130
Oct 6136136
Oct 7135134
Oct 8120110
Oct 9130125
Oct 1014190
Oct 1116155
Oct 1214474
Oct 1315472
Oct 1413496
Oct 15149101
Oct 16160117
Oct 1713620
Oct 1818130
Oct 1914248
Oct 20133124
Oct 21136121
Oct 2214563

With input counted once, the average rose slightly over the month, from about 125,000 to about 140,000 tokens. Our reporting now stores a normalized total next to the raw provider fields, and every cache hit rate is computed against that total.

Lesson: never average a token field across providers without checking what it includes. Keep the raw fields, because they map to how each provider bills, and add one normalized total for comparisons.

3. Calls nobody owned

About 4.4% of calls and $157 of the month’s spend had no agent role. The ledger has a view for exactly this slice, broken down by model and session. Broken down by model, half of it was calls to our main agent model that arrived without a role header, and another 30% was two smaller models that are not used through the normal agent launcher. The breakdown by session points to the specific clients to fix.

Unowned spend matters more than its share suggests. A budget that is enforced per role cannot stop a client that has no role. A growing unattributed slice is best treated as a bug in whichever client stopped sending headers.

Lesson: attribution has to be stamped by the client making the call, or by the gateway from the client’s credential. Anything that relies on someone joining logs later will drift.

4. The busiest session was not the expensive one

Ranking session-hours by call count, the top entry was one agent making 894 calls in a single hour, about one every four seconds. It looked like a loop. Its uncached input for that hour was close to zero, and its output was small: it was reading and editing many small files, with nearly all of its context served from cache.

The sessions that did cost money looked ordinary by call count. Their cost came from cache misses on large contexts: a session that switches model, or has its history rewritten, pays full price for 100,000 or more tokens on the next call. One older model on our gateway ran at a 27% cache hit rate for the month, against 91% to 97% for the models agents use day to day on the few clients still using it, so most of its input was billed at full price.

Lesson: rank by estimated cost, not by request count. Then check the cache hit rate of the most expensive sessions before anything else.

The order we check things in now

  1. Spend per day, then per active session. If the per-session line is flat, the cause is concurrency.
  2. Normalized input per call and cache hit rate, by model. A falling hit rate on the same work means something changed the prompt prefix.
  3. The unattributed slice. If it grew, find the client that stopped sending headers.
  4. Top sessions by estimated cost for the window, and what ticket or pull request each was working on.

None of this needs special infrastructure: one wide table, written once per call by the component that already sees every request. The hard part was deciding up front which keys to stamp, and adding the ones we missed each time an anomaly could not be explained.