TL;DR
I run a fully autonomous implementation system on Claude around the clock, and for months my token bill quietly climbed until it hit a number I wasn't willing to say out loud in a standup. πΈ By measuring per-task cost, routing work across model tiers, fixing my prompt caching, and killing retry storms, I cut spend by roughly 70% β without any measurable drop in output quality. Here's exactly what I did, with the numbers.
The Problem
Some background: for over a year I've been running an autonomous coding setup built on Claude Code β an orchestrator that picks tasks, spawns worker agents, verifies their diffs, and commits. It runs whether or not I'm at my desk. It's genuinely useful: it has refactored god classes, backfilled tests, and deleted dead code while I slept.
But autonomous means always on, and always on means the meter never stops. β οΈ
When I finally sat down and audited a month of API usage, three things jumped out:
- I had no idea what a single task cost. I knew the monthly total. I could not tell you whether "backfill tests for module X" cost $0.40 or $14. That's like running a factory and only reading the electricity bill once a year.
- Everything ran on the biggest model. Every subtask β including "rename this variable in 12 files" β went to the top-tier model, because that's what I had configured on day one and never revisited.
- Failures were the most expensive tasks. A task that fails and retries three times with a full context each time costs 4x a successful one, and produces nothing.
The bill wasn't a pricing problem. It was an engineering problem I had been ignoring because the agent "worked."
How I Solved It
Step 1: Measure cost per task, not per month
You can't optimize what you can't see. My agent already wrote structured JSONL logs for every run, so the first fix was embarrassingly small: log token usage per task, tagged with the task type.
# after each API call, accumulate into the task record
usage = response.usage
task_log.update({
"task_type": task.category, # "refactor", "test-gen", "review", ...
"input_tokens": task_log.get("input_tokens", 0) + usage.input_tokens,
"output_tokens": task_log.get("output_tokens", 0) + usage.output_tokens,
"cache_read_tokens": task_log.get("cache_read_tokens", 0)
+ getattr(usage, "cache_read_input_tokens", 0),
})
Then a tiny report script aggregates by task_type and multiplies by the price sheet. Ten minutes of work, and suddenly I had a table like this:
| Task type | Avg cost | % of monthly spend |
|---|---|---|
| Code review passes | $2.10 | 34% |
| Test generation | $1.60 | 27% |
| Refactoring | $1.20 | 18% |
| Mechanical edits (renames, imports) | $0.90 | 14% |
| Everything else | β | 7% |
Two rows shocked me. Review passes were my biggest line item β the verification of work cost more than the work. And mechanical edits at $0.90 a pop were pure waste: no reasoning is required to reorder imports.
Step 2: Route by task tier, not by habit
The fix for the mechanical-edit waste was model routing. Claude comes in tiers β Haiku, Sonnet, Opus β with wildly different price points, and most agent subtasks do not need the top tier.
flowchart TD
T[Incoming subtask] --> C{Classify}
C -->|mechanical: renames, imports, formatting| H[Small model]
C -->|standard: implement, test-gen| S[Mid model]
C -->|hard: architecture, gnarly debugging, verification| O[Top model]
H --> V{Verifier check}
S --> V
O --> V
V -->|fail| E[Escalate one tier up, once]
The classifier is not fancy. It's a rule table keyed on task category plus a couple of signals (diff size estimate, number of files touched). I resisted the urge to make the classifier itself an LLM call β that would be spending tokens to decide how to spend tokens.
The one rule that made this safe: failed tasks escalate one tier up, exactly once. If the small model botches a rename, the mid model redoes it. In practice escalation fires on about 8% of routed tasks, which means 92% of mechanical work now runs at a fraction of the old cost.
Routing alone took ~30% off the bill.
Step 3: Actually use prompt caching (I was breaking it)
Anthropic's prompt caching discounts repeated prompt prefixes heavily β cached input tokens cost a small fraction of fresh ones. I "had caching on." I was also getting almost no benefit from it, and the per-task logs from Step 1 showed why: my cache read ratio was under 15%.
The culprit was my own system prompt assembly. I was interpolating volatile values near the top of the prompt β the current timestamp, the task ID, a random run identifier. Every run, the prefix changed, so every run was a cache miss from byte one. π€¦
The fix is a rule I now treat as law:
Stable content first, volatile content last. System rules, coding conventions, and project context go at the top and never change mid-session. Timestamps, task IDs, and dynamic state go at the very bottom.
After reordering, my cache read ratio jumped to ~80% on long sessions. This was the single highest ROI change relative to effort: it was a copy-paste order swap.
Step 4: Context hygiene β stop paying to re-read the repo
My workers used to start every task by re-reading large chunks of the codebase "to be safe." Safe, and expensive: input tokens dominated my bill at roughly 9:1 over output.
Two changes:
- Persistent code map. The orchestrator maintains a compact, structured summary of the repo (modules, key functions, ownership) that gets injected instead of raw file dumps. Workers read actual files only for what they're about to touch.
- Hard context budget per task. Each task type has a token budget; a worker that wants to exceed it must drop something first. It sounds draconian. In four months, the budget has been the cause of a task failure exactly twice β and both times the task was mis-scoped anyway.
Step 5: Kill retry storms
The nastiest pattern in the logs: a task hits a genuinely hard problem, fails verification, retries with the same context, fails the same way, retries again... Each loop carried the full context. One bad afternoon, a single stuck task burned more than my usual daily spend.
Now: two strikes, then stop and summarize. After a second failed verification, the task is parked with a short structured failure report instead of retried. A human (me) or a next-day run picks it up with fresh eyes. Failing loudly and cheaply beats failing silently and expensively.
Lessons Learned
- Per-task cost visibility changes your behavior more than any optimization. The report script took 10 minutes to write and drove every other decision in this post. Do this first.
- Your verification layer is probably your biggest line item. Everyone optimizes the "doing" and forgets the "checking." Mine cost more than the work it checked.
- Model routing is a rules problem, not an ML problem. A dumb rule table with one-shot escalation captured ~90% of the theoretical savings. Don't burn tokens deciding how to save tokens.
- Prompt caching fails silently, and it's usually your fault. Check your cache read ratio. If it's low, you almost certainly put something volatile at the top of your prompt.
- Retries without new information are gambling with your budget. If the context is identical, the failure will be too. Change something or stop.
One honest caveat: quality didn't drop, but I only know that because my verifier pass rate and my own spot-check rejection rate stayed flat across the change. If you don't measure quality before cutting costs, you won't know whether you traded it away.
What's Next
Two experiments on my list:
- Cost-aware task scheduling β letting the orchestrator batch cheap mechanical tasks into off-hours runs and reserve the top-tier model budget for the hard stuff.
- Cache-friendly session pooling β grouping tasks that share project context into the same session so the cached prefix gets reused across tasks, not just across turns.
I'll write both up once I have a month of real numbers, because vibes-based cost posts are exactly the thing I'm trying not to write.
Wrap-up
Cutting 70% off the bill required zero clever prompts and zero model downgrades on hard work β just measurement, routing, cache discipline, and a stop-loss rule. The meter is still running; it's just running on purpose now. π
If this was useful, follow me here on Dev.to β I write weekly about running autonomous coding agents in the real world: what breaks, what it costs, and what's actually worth automating. And if you haven't tried building on Claude Code yet (I'm on 2.x as of September 2026), start small: give it one well-scoped task and a way to verify itself. That loop is where everything in this post began.
What's your biggest AI cost sink? Drop it in the comments β I read all of them. π¬
Top comments (0)