TL;DR
I run a Claude Code agent that grinds on my side projects around the clock. For months I ignored the token bill — until it stopped being ignorable. Here are the 5 changes that cut my monthly token spend by ~60% without making the agent dumber: order your context for cache hits, stop stuffing the window, cap tool output, route each task to the right model, and batch independent work. All of these are things you can do this afternoon.
The Problem
I have a long-running autonomous coding agent — a Claude Code process that picks up small tasks on my repos, fixes flaky tests, updates docs, and refactors the boring stuff while I sleep. It's genuinely useful. It's also a machine that turns tokens into work, and for a long time I treated tokens as free.
They are not free.
The wake-up call was boring: I finally looked at a month of usage and my daily spend had crept up to roughly $40/day. Nothing was broken. The agent was just doing a lot of work in the least efficient way possible — re-reading the same files every turn, dumping entire directories into context "just in case," and running everything on the single most expensive model because that was the default I'd set on day one.
The interesting part isn't that it was expensive. It's that most of the cost was pure waste — tokens spent re-sending context the model had already seen, or reasoning power spent on tasks that didn't need it. Once I saw it that way, the fixes were obvious. Here's what actually moved the number.
How I Solved It
Lesson 1: Order your context so the cache actually hits
This one alone was worth more than the other four combined.
Anthropic's prompt caching lets you mark a stable prefix of your prompt as cacheable. On a cache hit, those tokens cost roughly 10% of a normal input token. A cache write costs about 25% more than a normal input token, and the default cache lives for about 5 minutes, refreshed every time you hit it.
The trap: the cache only helps if the prefix is byte-for-byte identical between calls. The moment something near the front of your prompt changes, everything after it is a cache miss and gets re-billed at full price.
So the rule is: stable stuff at the front, volatile stuff at the back.
flowchart TD
A[System prompt] --> B[Tool definitions]
B --> C[Large reference docs / project rules]
C --> D[Conversation history]
D --> E[Latest user turn]
subgraph cached [Cacheable prefix - cheap on repeat]
A
B
C
end
subgraph volatile [Changes every call - full price]
D
E
end
I had this exactly backwards. I was injecting a freshly-generated timestamp and a "current task" summary near the top of the system context, which invalidated the cache on every single turn. Moving those to the end — after the big, stable block of project rules and tool definitions — turned a near-0% cache hit rate into something like 80%.
If you use Claude Code directly, a lot of this is handled for you, but the lesson still applies to anything you build with the API: don't let a volatile token sneak into your stable prefix.
Lesson 2: Stop stuffing the context window
My agent had a "read everything relevant" step that, in practice, meant reading half the repo into context before doing a two-line fix. More context feels safer. It is not safer — it's slower, more expensive, and it actively hurts quality because the signal gets buried.
The fix was to make retrieval lazy and narrow:
Before: read src/**/*.ts -> 40k tokens -> make a 3-line change
After: grep for the symbol -> read the 2 files that matched -> change
I replaced "read the directory" with "search, then read only what matched." Same task, a fraction of the tokens, and — this surprised me — better results, because the model wasn't distracted by 38k tokens of unrelated code.
Rule of thumb I now follow: if I can't explain why a file needs to be in context, it doesn't go in.
Lesson 3: Cap what your tools hand back
Tool output is input tokens you pay for on the next turn. A single npm test or a chatty log command can dump tens of thousands of tokens of stack traces straight into the context window, and then you re-send that garbage on every subsequent turn until it scrolls off.
I wrapped my noisy commands to truncate hard:
# Instead of piping raw output back to the agent:
npm test 2>&1 | tail -n 40
# And for anything that can explode:
some-verbose-command 2>&1 | head -c 4000
For a coding agent, the last 40 lines of a failing test run is almost always enough to act on. The other 4,000 lines are pure cost. Capping tool output was one of the least glamorous changes and one of the most effective.
Lesson 4: Route tasks to the right model
I was running everything on the biggest model because I set it once and forgot. But most of what an autonomous coding agent does is not hard reasoning — it's mechanical: renaming things, formatting, writing a commit message, summarizing a diff.
Anthropic ships a tiered lineup for exactly this reason: Opus for the genuinely hard reasoning, Sonnet as the balanced workhorse, Haiku for fast, cheap, high-volume work. The prices differ by more than an order of magnitude across the range, so routing matters a lot.
My routing now looks roughly like this:
Haiku -> format, lint-fix, commit messages, "summarize this diff"
Sonnet -> most day-to-day edits, refactors, writing tests
Opus -> architecture decisions, gnarly bugs, anything I'd escalate to a senior
The trick is to make the cheap model the default and escalate on demand, instead of defaulting to the expensive model and never de-escalating. If a Sonnet attempt fails a check twice, the wrapper bumps that one task up to Opus. Most tasks never need it.
Lesson 5: Batch independent work into one turn
Every model turn re-sends the whole context. So if you have five independent little edits and you do them as five separate turns, you pay the context tax five times.
Where the tasks are truly independent, I fan them out and collect them together instead of round-tripping one at a time:
Bad: turn -> edit A -> turn -> edit B -> turn -> edit C (context re-sent 3x)
Good: one turn -> "here are 3 independent edits, do all three"
This is the same idea as issuing parallel, independent tool calls in a single step rather than serializing them. Fewer turns means the expensive, re-sent context gets amortized across more actual work. It also finishes faster, which is a nice bonus when you're watching it run.
Lessons Learned
Zooming out, here's what I'd tell past-me:
- Waste hides in repetition, not in big one-off calls. The scary line item wasn't one giant request — it was the same context re-sent thousands of times. Optimize the loop, not the outlier.
- The cache is a data-layout problem. Caching isn't a flag you flip; it's a discipline of keeping your stable prefix stable. One stray timestamp near the top can quietly cost you 80% of your potential savings.
- More context is not more intelligence. Trimming what the model sees made it both cheaper and sharper. This was the most counterintuitive result of the whole exercise.
- Default cheap, escalate on demand. Reaching for the biggest model "to be safe" is the single most common way to overspend. Flip the default and let failures pull you up a tier.
- Measure before you optimize. I burned money for months because I never looked. Ten minutes with the usage dashboard paid for itself more than any clever trick.
Net result: my daily spend dropped from about $40/day to roughly $16/day — a ~60% cut — and the agent's output quality went up, mostly thanks to Lesson 2. Your mileage will vary, but the ordering of impact (cache → context hygiene → tool caps → routing → batching) has held up across every project I've applied it to.
What's Next
I'm building lightweight per-task cost tracking so the agent knows what each task actually cost and can flag its own expensive habits — closing the loop so it optimizes itself instead of waiting for me to notice a bill. I also want to A/B the 5-minute vs. longer cache windows for my specific workload, since my agent's idle gaps sometimes fall right outside the 5-minute TTL and eat a fresh cache write.
If there's interest, I'll write that follow-up as a build log too.
Wrap-up
Token cost is one of those things that stays invisible until it isn't, and then it's the only thing you can see. The good news is that the fixes are cheap, boring, and mostly one-time. Start with cache-friendly ordering, trim your context, and cap your tool output — that's 80% of the win for 20% of the effort.
Tested on Claude Code (2026), across the Claude Opus / Sonnet / Haiku lineup, Node.js 22.x.
💬 If you're running an always-on agent, I'd love to hear what your biggest token sink turned out to be — drop it in the comments.
🚀 Follow me here on Dev.to for more build-in-public write-ups on autonomous coding agents, and if you haven't tried driving one yourself yet, Claude Code is a great place to start. What would you cut first?
Top comments (1)
There is a sixth lesson hiding in front of your five: the pre-session constant. Every installed MCP server and skill contributes its tool schemas and descriptions to the prompt before the agent does anything, and for a round-the-clock agent that overhead is billed on every single session start. Worse, it interacts with your Lesson 1: an MCP server that updates or reorders its tool descriptions mid-week silently invalidates your carefully ordered cache prefix, and everything behind it re-bills at full price. So my biggest token sink turned out to be extensions I had stopped using, and auditing that list improved my cache hit rate as much as my context size. Anthropic clearly sees the same pattern, since the /checkup command that shipped this morning has removing unused skills, MCPs and plugins as its literal first feature.