Skip to content

AI Economics

The Token Price Collapse: What Half-Price Frontier AI Actually Changes

The Maths That Changes What You Build

📉 🪙 💰

When GPT-5.6 Sol landed on 9 July 2026 at half the price of Claude Fable 5, most of the coverage treated it as a scoreboard update. It isn't. A 50% price cut doesn't make the same projects cheaper — it makes a whole category of projects exist that didn't before.

I've spent two weeks re-costing my own pipelines against the new numbers. Here's what actually moved.

The four prices that matter (not one)

Everyone quotes the headline in/out rate and stops. There are four rates, and the two nobody quotes are where the money is:

Per 1M tokensInputOutputCached inBatch in / out
GPT-5.6 Sol$5$30$0.50$2.50 / $15
Claude Fable 5$10$50$1.00$5.00 / $25

Cached input is 10× cheaper than fresh input on both providers. Batch is 50% off again on both. Stack those two discounts against the cheaper base rate and the spread between a naive integration and a tuned one is roughly 20× — far bigger than the gap between the two vendors.

Which means the interesting question was never "which model is cheaper". It's "am I paying the fresh-input, real-time rate for work that could be cached and batched".

The workload that just flipped

Concrete example from this site. I wanted every one of ~880 blog posts audited for a weak meta description, a missing FAQ block, and a heading structure that doesn't match the target keyword. Call it 4k input tokens per post and 800 output.

Fable 5, real-time$70.20
Sol, real-time$38.60
Sol, batch$19.30

880 posts × 4,000 input + 800 output = 3.52M in / 0.70M out. Identical audit, three ways of buying it.

$70 versus $19. At $70 I think about it, scope it down to the top 100 posts, and do it once. At $19 I run it every month and stop thinking about it. That's not a discount — it's a different habit.

And this is a small corpus. Scale it to a real content operation, a support-ticket backlog, or a product catalogue and the same flip happens at every size.

The money pattern: a provider adapter you write once

You cannot exploit any of this if your model choice is hard-coded across forty files. The single highest-ROI thing in your AI stack is a boring adapter with one job: make the model a config value.

// lib/ai.ts — one seam, two providers, zero call-site churn
type Job = { system: string; user: string; batch?: boolean };

const ROUTES = {
  cheap:   { provider: 'openai',    model: 'gpt-5.6-luna' },
  default: { provider: 'openai',    model: 'gpt-5.6-sol'  },
  careful: { provider: 'anthropic', model: 'claude-fable-5' },
} as const;

export async function run(tier: keyof typeof ROUTES, job: Job) {
  const { provider, model } = ROUTES[tier];
  return provider === 'openai'
    ? openai(model, job)
    : anthropic(model, job);
}

Now a price change, a new release, or a bad week from one vendor is a one-line edit. I've re-pointed this seam twice in 2026 and neither move touched a single call site.

The second half of the pattern is tiering by task, not by vibe. Most calls in a real system are not hard:

  • cheap — "did anything in this log look like an error?", classification, tagging, extraction. A small model nails these and costs a rounding error.
  • default — the actual work. Drafting, refactoring, multi-step agent turns.
  • careful — irreversible or expensive-if-wrong. Migrations, money, anything customer-facing that ships unreviewed.

I audited my own usage and found I'd been running the flagship on tasks a small model handles perfectly — watching a dev-server log for the strings error|failed|cannot|undefined. That's a grep with opinions, and it belongs on a cheap always-on watcher. It does not need a frontier model, and paying frontier rates to stare at a file is the most common waste I see in AI stacks.

The decision table: what to run where

Before the code, the shape of the decision. This is the table I actually reason from — task type down the left, and the reason a tier wins on the right.

TaskTierWhyRough cost per 1k calls
"Did this log line mean a break?"smallBinary classification. No reasoning needed.~$0.02
Tag / extract / categorisesmallStructured output from clear input.~$0.05
First-pass filter on search resultssmallCheap recall, precision comes later.~$0.08
Draft, refactor, multi-step agent turndefaultThe actual work. Needs real capability.~$4–12
Long-context repo reasoningdefaultCache the prefix and it's affordable.~$8–20
Migration, billing, anything irreversiblecarefulWrong once costs more than the whole month's tokens.~$25–60

The right-hand column is the one that changes behaviour. When you can see that a watcher costs two cents per thousand calls and a careful-tier review costs forty dollars, you stop agonising over whether to run the watcher and start agonising over whether the careful tier is warranted — which is the correct place to spend your attention.

Where the money actually goes

The flow below is worth internalising, because almost every wasted dollar in an AI stack is one of the two leaks marked on it.

stable prefixsystem + tools + rules + retrieved docs — cache this, 10× cheaper
volatilethe user's actual turn — always full price
modeloutput tokens at 6× input
next turnthat output re-sent as input, every turn after

A cache-aware prompt builder

Caching only pays if the prefix is byte-identical every turn. That's easy to say and easy to break, so make it structural rather than a thing you remember:

// lib/ai/prompt.ts
// The ONLY way to build a prompt. Stable segments first, volatile last,
// and the stable part is serialised once and frozen.

type Stable = {
  role: string;          // system persona
  tools: unknown[];      // tool definitions
  rules: string[];       // project rules
  docs?: string[];       // retrieved context
};

const cache = new Map<string, string>();

/** Deterministic serialisation — sorted keys, no incidental whitespace drift. */
function freeze(s: Stable): string {
  const key = JSON.stringify(s, Object.keys(s).sort());
  const hit = cache.get(key);
  if (hit) return hit;

  const out = [
    s.role.trim(),
    '## Tools',
    JSON.stringify(s.tools, null, 0),      // never pretty-print: whitespace = cache miss
    '## Rules',
    ...s.rules.map((r) => `- ${r.trim()}`),
    ...(s.docs?.length ? ['## Context', ...s.docs] : []),
  ].join('\n');

  cache.set(key, out);
  return out;
}

export function build(stable: Stable, userTurn: string): Msg[] {
  return [
    { role: 'system', content: freeze(stable) },  // identical bytes every call
    { role: 'user', content: userTurn },          // the only thing that varies
  ];
}

Three cache-killers this prevents, all of which I have personally shipped:

  • A timestamp or request ID in the system prompt. Guarantees a 0% hit rate forever, and it looks completely harmless in a diff.
  • Pretty-printed JSON for tool definitions. JSON.stringify(x, null, 2) is stable, but switching indentation — or a library upgrade that changes key order — silently invalidates everything downstream.
  • Putting retrieved documents before the rules. Retrieval changes per query, so anything after it is uncacheable. Volatile content goes last, always.

Verify the cache is actually hitting

Do not assume. The providers report cache reads in the usage object, so assert on it — a silent 0% hit rate is a 10× cost bug that produces no error and no visible symptom:

// scripts/check-cache.ts — run twice, second call must hit
const stable = { role: SYSTEM, tools: TOOLS, rules: RULES };

const a = await run('work', { messages: build(stable, 'ping') });
const b = await run('work', { messages: build(stable, 'pong') });

const rate = b.usage.cachedInputTokens / Math.max(1, b.usage.inputTokens);
console.log(`cache hit: ${(rate * 100).toFixed(0)}%  (${b.usage.cachedInputTokens}/${b.usage.inputTokens})`);

if (rate < 0.5) {
  console.error('FAIL: prefix is not caching. Check for volatile content in the prefix.');
  process.exit(1);
}

Wire that into CI and a cache regression becomes a red build instead of a bill.

Batch the work that can wait

The other 50% is free if the job is asynchronous. Most content and audit work is — you just have to stop treating everything as interactive:

// scripts/audit-batch.ts — one request per post, submitted as a batch
import fs from 'node:fs/promises';

const posts = JSON.parse(await fs.readFile('posts.json', 'utf8'));

const requests = posts.map((p: any) => ({
  custom_id: p.slug,                       // how you map results back
  method: 'POST',
  url: '/v1/chat/completions',
  body: {
    model: 'gpt-5.6-sol',
    max_tokens: 800,
    messages: build(AUDIT_RULES, `Audit this post:\n\n${p.contentHtml}`),
  },
}));

// JSONL, one request per line
await fs.writeFile('batch.jsonl', requests.map((r) => JSON.stringify(r)).join('\n'));

const file = await client.files.create({
  file: await fs.readFile('batch.jsonl'),
  purpose: 'batch',
});

const batch = await client.batches.create({
  input_file_id: file.id,
  endpoint: '/v1/chat/completions',
  completion_window: '24h',               // this is what buys the 50% discount
});

console.log(`submitted ${requests.length} requests as ${batch.id}`);

The tradeoff is honest: you trade latency for half the price. For a nightly audit of 880 posts that runs while you sleep, latency is worth nothing and the discount is worth $19 instead of $39. For anything a user is waiting on, batching is obviously wrong.

One practical note — custom_id is the only thing tying a result back to its input, because batch results come back unordered. Use the slug or primary key, never an array index.

Prompt caching is the discount you're probably leaving on the table

Agentic sessions re-send an enormous, near-identical prefix every turn: system prompt, tool definitions, project rules, repo context. Fresh, that prefix is full price on every single turn. Cached, it's a tenth.

Two rules to actually get the hit rate:

Put the stable stuff first, the volatile stuff last. Caches match on prefix. One changing token near the top invalidates everything after it. So: system prompt → tool defs → project rules → retrieved docs → then the user's turn. If you're injecting a timestamp or a request ID at the top of your system prompt, you have a 0% cache hit rate and a very expensive bug.

Keep the prefix byte-identical. Rebuilding the prompt with a different key order, a re-serialised JSON blob, or a trailing-whitespace difference reads as a different prefix. Serialise it once, deterministically, and reuse the string.

Zoom out from the invoice and the same collapse is an argument about the businesses selling the tokens — I've made that case at length in why I think Anthropic is the most overvalued company in tech.

The catch: cheaper tokens invite more tokens

Here's the trap, and I walked straight into it. When the marginal cost of a model call drops, the discipline to not make the call drops with it. I caught myself spinning up parallel sub-agents for work that was one grep, and leaving verbose agents running because "it's only a few cents".

Two guardrails that fixed it for me:

Compress the comms. My default output mode is deliberately terse — abbreviations, arrows for causality, fragments, one word where one word does, full technical accuracy preserved. It cuts roughly 75% of output tokens, and output is the expensive side of the ledger at 6× input. The biggest single win is right before dispatching parallel sub-agents, because their output flows back into the main context and gets re-billed as input on every subsequent turn.

Cap the loop. An agent with a cheap model and no iteration ceiling is a subscription to nothing. Hard-limit turns per task and make the model report what it couldn't finish, rather than discovering the spend afterwards.

There's a structural version of this too: inference is a variable cost that scales with usage. More success means more GPU spend. That's a utility's margin profile, not a software company's — which is why the price war has further to run and why nobody's premium is safe.

The verdict

Half-price frontier tokens don't make your existing AI features cheaper so much as they change your threshold for what's worth automating at all. The corpus-wide audit, the nightly content pass, the "re-check every record" job you shelved on cost — go back and re-run the numbers, because a lot of shelved ideas are now $20 jobs.

Then do the three unglamorous things: put a provider adapter behind your calls, route by task tier instead of habit, and order your prompts so the cache actually hits. Those three outlive whichever model is winning this quarter — and something will be winning next quarter.

Cheap tokens are only cheap if you're not paying flagship rates to watch a log file.

Want your AI spend audited and a provider-agnostic layer wired in so you can swap models on a config change? Get in touch, or browse the work first.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.