Prompt Caching and Context Strategy for Antigravity Agents — Cutting 60-80% Off Monthly API Costs in Long-Running Production
The longer you keep agents running, the more the monthly invoice quietly piles up. Running Antigravity agents alongside an AdMob-monetized indie app business (50M cumulative downloads), I managed to cut API costs by 60-80% by rebuilding prompt caching and context strategy. This article shares the three-layer cache, context compression, and TTL design I now run in production — with the code and numbers behind them.
Six months after I started running agents seriously, I sat down with my monthly API bill and realized it was time to redo the design. I've been shipping apps as an indie developer since 2014, and around the time cumulative downloads crossed 50 million, AdMob revenue started growing — and the agents I had built into my workflow were eating a noticeable share of that revenue as a fixed cost.
So I rebuilt the whole prompt caching and context strategy from scratch. The result: same functionality, but 60-80% lower monthly API spend. Specifically, average input tokens per agent request dropped from 12,400 to 4,800, and total monthly cost went from $480 to $112. This article walks through the design and the gotchas.
Why Prompt Caching Alone Doesn't Cut It
Prompt caching is the first thing the official docs recommend. I started there. But when I measured, my cache hit rate was only 30-40%, and the cost savings never matched expectations.
The reason was simple. I was mixing "cacheable" and "always-changing" pieces of context without separating them. System prompts and reference docs were static, but I was inserting dynamic user input and tool results between them. Prompt caching only works as a prefix match — the moment one byte shifts upstream, everything after it becomes a cache miss.
I needed to redesign the context itself as a cache layer.
A Three-Layer Cache Architecture
The structure I now run in production splits into three layers by TTL and expected hit rate characteristics.
Layer 1 — System prompt: ~24 hour TTL, ~99% expected hit rate
The key is keeping layer order strict. Layer 1 always comes first, then Layer 2, then Layer 3. Dynamic content (user input, tool results) goes inside Layer 3, ideally at the tail. Here's the TypeScript I run:
import { Anthropic } from "@anthropic-ai/sdk";interface CacheLayer { type: "system" | "reference" | "history"; content: string; cacheControl: { type: "ephemeral" } | undefined;}const buildContext = ( systemPrompt: string, references: string[], history: Array<{ role: string; content: string }>, userInput: string,): CacheLayer[] => { const layers: CacheLayer[] = []; // Layer 1: system (intended for ~24h caching) layers.push({ type: "system", content: systemPrompt, cacheControl: { type: "ephemeral" }, }); // Layer 2: reference docs (~6h caching) for (const ref of references) { layers.push({ type: "reference", content: ref, cacheControl: { type: "ephemeral" }, }); } // Layer 3: history + dynamic input (short cache + no cache) for (let i = 0; i < history.length; i++) { const isRecent = i >= history.length - 2; layers.push({ type: "history", content: `${history[i].role}: ${history[i].content}`, // do not cache the last 2 turns (they change fastest) cacheControl: isRecent ? undefined : { type: "ephemeral" }, }); } // final user input is never cached layers.push({ type: "history", content: `user: ${userInput}`, cacheControl: undefined, }); return layers;};
The moment I switched to this structure, cache hit rate jumped from 38% to 78%. The whole idea reduces to "stable stuff first, changing stuff last." That sounds obvious in hindsight, but I had been embedding timestamps in my system prompt, which alone was killing every layer.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦How to structure a three-layer cache (system / reference / history) with separate TTLs, and the concrete code that keeps cache hit rate above 75%
✦Context compression plus summary snapshots that cut input tokens by 60% — the actual numbers from my Antigravity production setup
✦The break-even point of cost-per-request when running alongside AdMob revenue, and three traps that indie developers walk into early
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Even with high cache hit rate, the 20% that doesn't hit is still billed. To shrink that further, I add context compression. Three techniques I use:
Summary snapshots — Replace history older than ~10 turns with a ~200-token summary.
Semantic dedup — Pull duplicate topics out of reference docs into a single block.
Structured summaries — Distill history into three categories: decisions, open issues, user preferences.
By far the biggest win was the first one. Antigravity agents are built for long-running conversation, so history grows without bound if you let it. Here's the threshold logic I use:
const MAX_HISTORY_TOKENS = 6000;const SUMMARY_TARGET_TOKENS = 200;const compressHistoryIfNeeded = async ( history: Array<{ role: string; content: string }>, estimateTokens: (text: string) => number, summarize: (text: string) => Promise<string>,): Promise<Array<{ role: string; content: string }>> => { const total = history.reduce((sum, h) => sum + estimateTokens(h.content), 0); if (total < MAX_HISTORY_TOKENS) return history; // keep the last 4 turns verbatim; summarize everything older const recent = history.slice(-4); const old = history.slice(0, -4); const oldText = old.map((h) => `${h.role}: ${h.content}`).join("\n"); const summary = await summarize( `Summarize the following dialogue history into <=${SUMMARY_TARGET_TOKENS} tokens, ` + `organized as: decisions / open issues / user preferences.\n\n${oldText}`, ); return [ { role: "system", content: `[Prior dialogue summary]\n${summary}` }, ...recent, ];};
The 6,000-token threshold came from measurement: above that, cache hit rate started dropping. I started at 10,000 and tuned down — the break-even between summarization cost and cache-miss cost landed at 6,000 for me. That number is model- and workload-dependent; tune it against your own data.
TTL Design and Cache Invalidation Traps
The hardest part of the three-layer setup was actually TTL. Antigravity's ephemeral cache is documented as "5 minutes," but in practice it gets extended with continued access.
I misread this at first and built around the assumption that the system prompt would stay cached for 24 hours. After a 6-hour gap overnight, my next morning's first request had a full-stack cache miss and the input token count tripled.
The fix is a "warmup request" right before I expect the agent to fire:
I run this warmup roughly 3 seconds before the agent's real task starts. The cost is negligible (max_tokens=1), and it eliminated the first-request cost spike.
The other trap is reference doc updates. Change one byte in Layer 2, and the cache key changes, invalidating everything downstream. I now batch reference doc updates to a 6-hour rhythm and always re-run warmup right after.
The Break-Even Against AdMob Revenue
Time for numbers. Running an indie app business, API cost is a fixed deduction from AdMob revenue.
Input tokens per request: 12,400 → 4,800
Cost per request (JPY equivalent): ¥4.8 → ¥0.6
Monthly requests: 14,000 (unchanged)
Monthly cost: $480 → $112
As a percentage of monthly AdMob revenue: ~38% → ~9%
Before the redesign, agents were consuming ~38% of AdMob revenue, which honestly was not sustainable as a business. At 9%, the value of the 2-3 hours per day that agents save me clearly exceeds the cost.
The key is combining both gains: cache hit rate up to 78% AND 60% compression. Either alone caps out around 30-40% improvement. Together, they bring monthly cost into a different order of magnitude.
Three Traps Indie Developers Walk Into Early
Here are three traps I actually walked into. Each one can spike your bill for days or weeks before you notice.
Trap 1: Embedded timestamps. If your system prompt includes "current time," every cache key changes per request and Layer 1 dies. Move time-fetching into a tool call. I lost three days to a cache hit rate stuck near 0% because of this.
Trap 2: Cancelled streaming responses. If you cancel a stream mid-flight, you're still billed for the tokens read but the cache write never lands. I had been using streaming aggressively for UX, but for agent internal calls, batch (non-streaming) is more cache-efficient.
Trap 3: Parallel cache race conditions. Two simultaneous requests with the same prompt can both get processed as cache misses. When running parallel agents, fire one request first to warm the cache, then parallelize from request two onward.
None of these are in the official docs, but you'll hit them in production. Putting guards in front of them keeps invoices smooth.
Recommended Approach by Scenario
Not every agent needs the full three-layer + compression treatment. It depends on shape.
Short bursts (a session that completes in minutes): Layer 1 and Layer 2 are enough. Layer 3's cache won't pay off in sub-TTL sessions. Skip compression.
Long-running operations (tens of minutes to hours): Full three-layer + compression. This is most of what I run.
Overnight batches: Always include warmup. The first request after a quiet period will miss, and without warmup you'll spike the batch cost.
Multiple apps sharing a system prompt: Extract Layer 1 into a shared service. I run the same operations template across my wallpaper apps and relaxation apps, and the shared Layer 1 lifts hit rate horizontally.
A Minimal Starting Step
If you want to try this tomorrow, start by switching Layer 1 alone to ephemeral cache. It's a few lines of code, and it usually moves hit rate from 30% to 60%. Run that for a week, watch the numbers, then layer on Layer 2 and Layer 3 incrementally.
Trying to put everything in at once makes it impossible to tell what's actually working. Add layers one at a time and measure each. That ends up being the fastest path.
For anyone running an indie app business alongside agent automation, the single most important thing is making costs legible. The design in this article is what let me keep using agents as an offensive tool instead of treating them as an expense to be minimized. I hope it helps you do the same.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.