My first month running agents at any real volume, I burned $1,400 and most of it was waste. Not the model thinking hard about hard problems — that's money well spent. It was re-sending the same 40K-token system prompt uncached on every single request, running everything at max effort, and using Opus to grep files. Pure leakage.
The goal isn't to make your agent cheaper by making it dumber. Anyone can drop to a smaller model and call it cost control. The goal is to cut the waste and keep the intelligence where it matters. Here's where the money actually goes and how I plug each leak.
Prompt caching is the single biggest lever
If you're running an agent with a large stable system prompt and tool set, and your cache_read_input_tokens is zero, you are setting money on fire. Caching is a prefix match — the API caches the rendered prompt up to a breakpoint, and reads cost roughly a tenth of full input price.
The catch: any byte change anywhere in the prefix invalidates everything after it. The classic killer is a timestamp in the system prompt.
system = f"You are an agent. Current time: {datetime.now()}" # cache dead on arrival
That datetime.now() makes every request a unique prefix. Nothing ever caches. Move volatile stuff — timestamps, request IDs, the actual question — to the end, after your last cache breakpoint. Keep the system prompt and tool list frozen and byte-identical.
How to know it's working: check usage.cache_read_input_tokens across repeated requests. If it's a fat number, you're caching. If it's zero, diff two rendered prompts and find the byte that's changing. It's always something dumb — an unsorted JSON dump, a UUID, a date. On a long agent session this is the difference between a $4 run and a $0.40 run, and it changes nothing about output quality. Free money.
Tune effort per route, don't max everything
The effort parameter controls how hard the model thinks. The instinct is to crank it to max everywhere so the agent is as smart as possible. Wrong instinct. On the current Opus models, high is the sweet spot for most work, and max often just burns extra tokens on overthinking with diminishing returns.
More to the point: not every step in an agent loop needs the same brainpower. A classification step, a "which file should I look at" decision, a yes/no gate — those run fine at low or medium. Reserve high and xhigh for the actual hard reasoning and the long-horizon planning.
# cheap gate
client.messages.create(model="claude-opus-4-8", output_config={"effort": "low"}, ...)
# the real work
client.messages.create(model="claude-opus-4-8", output_config={"effort": "high"}, ...)
I ran an effort sweep on my own eval set — medium, high, xhigh — and found high matched xhigh on quality for most of my routes at noticeably lower token spend. You won't know your numbers until you measure. Don't default to max out of fear.
Subagents for the cheap work
Here's a trick that's both a cost play and a caching play. When you need a cheap sub-task done — explore a directory, summarize a file, grep for something — don't do it inline on your main expensive loop. Spawn a subagent on a cheaper model.
Why it's also a caching win: switching models mid-conversation invalidates your cache, because caches are model-scoped. If you drop your main loop from Opus to Haiku for a grep and back, you've nuked your prefix cache twice. A subagent keeps the main loop on one model and one cache, while the cheap work happens off to the side on Haiku. Claude Code does exactly this — its Explore subagents run on Haiku for precisely this reason.
Context editing and compaction for long runs
Long agent sessions accumulate stale tool results and old thinking blocks. Every turn you re-send all of it at full input price. Two tools fix this.
Context editing clears old tool results from the transcript before the model sees them. Compaction summarizes the history server-side when you near the window. They're different — editing prunes, compaction condenses — and long agents often use both. The savings compound: a 40-turn session that would re-send 200K tokens of dead tool output every turn instead sends a tight summary.
One gotcha that bites everyone with compaction: append the full response.content back each turn, not just the extracted text. The compaction state lives in those content blocks. Strip to text and you silently lose it, and your costs creep right back up while you wonder why.
Watch the right number
Last thing. When you debug cost, don't stare at input_tokens alone. The real total is input_tokens + cache_creation_input_tokens + cache_read_input_tokens. I've seen people panic that a long-running agent shows only 4K input_tokens and assume something's broken — no, the rest was served from cache. That 4K is the uncached remainder. Sum all three or you're reading the gauge wrong.
None of this makes your agent dumber. Caching is free. Effort tuning keeps brainpower where it counts. Subagents put cheap work on cheap models. The dumb move is the smart-sounding one: drop to a weaker model everywhere and watch quality crater to save a few bucks. Cut the waste first. The waste is enormous, and it's hiding in a timestamp you forgot about.
