The familiar metered unit has a price known before the work happens: a request, a seat, a gigabyte-month. An assistant's unit of work is a turn, and a turn's cost is unknowable until it is over. The supplier bill depends on how long the answer runs, how many tools it calls, how much context rides along, how deeply the model thinks — none of which is knowable at the moment the user presses send, and all of which is precisely known the moment the turn completes. So the pricing problem is structural: you are selling a good whose cost of goods is revealed only after delivery, message by message, forever.
The bad answers form a neat triangle. Pre-authorise the worst case per message and the product feels like a taxi meter bolted to a conversation. Cap the output to bound the cost and you truncate answers mid-sentence — breaking the product to protect the margin. Go flat-rate unmetered and the heaviest users are subsidised by everyone else until adverse selection finishes the job. This entry is the machinery we built between those corners, and the week the credit system was made honest.
Meter the physical quantity; derive the money
The foundation decision: what gets recorded is the physical quantity — tokens, exactly as each model provider's own accounting reports them per turn — and money is always a derivation from that record, via a dated rates lookup. Cost per turn is therefore exact, not estimated: deterministic arithmetic on the counts returned with the turn, written as a per-turn audit entry in Firestore (attributed to the session and the agent that ran it), folded into a per-day rollup by atomic increment, and summed into a lifetime total. The rollup layer exists because time-series questions should never require scanning an audit trail; the audit trail exists because "why was yesterday expensive?" deserves a per-turn answer.
Storing quantity and deriving money buys the property that has paid for itself repeatedly: prices change, and records do not have to. When a rate moves, the lookup moves; when a historical figure needs recomputing, an idempotent backfill re-derives money from stored tokens without touching a single count. The failure mode it doesn't automatically buy — a quantity with no price row at all — gets its own section below, because it bit us.
Gate at the boundary; never truncate the work
Where does enforcement live, given that cost arrives after the fact? At the turn boundary, and only there. Before a turn starts, the gateway checks the balance; a turn that begins is allowed to finish, always — an answer is never cut off mid-stream because a meter ticked past zero somewhere in its middle. The overshoot this permits is bounded by a single turn's cost, and we absorb it, because the alternative is a product that stops mid-sentence, and a product that stops mid-sentence has communicated something about itself no refund repairs.
For users who opt in, exhaustion does not even surface: an automatic top-up charges a fixed increment through Stripe inline — an off-session payment, before the request is forwarded — and the conversation continues with no seam. The control that keeps that safe is a user-set monthly cap — and the cap's scope is a small design decision we got wrong before getting right: it bounds out-of-pocket automatic charges only. It does not count included credit, and a deliberate, explicit one-time purchase bypasses it — because a cap governs automation, not intent. A person clicking "add credit now" is not a runaway process, and a limit that blocks them anyway teaches people to disable limits.
What a credit has to mean
The week this entry is pinned to, we fixed a dishonesty we had shipped by accident. Purchased top-up credit was implemented as a per-period counter, zeroed at every renewal — meaning credit a user bought on day 29 quietly evaporated on day 30. Nobody designed that as a policy; it fell out of an implementation choice, which is exactly how products acquire dark patterns: not by malice, but by counters that reset. Expiring purchased credit is the gift-card move that consumer protection exists to police, it contradicted our own published no-dark-patterns position, and it was inconsistent with our other wallet, which had always persisted.
So a credit became a wallet: purchased credit never expires. It survives billing rollover, plan changes, and cancellation — after cancelling, the balance stays spendable on the free tier, because the plan bought the ceiling and the top-up bought tokens, and those are different purchases. Unspent credit is refundable on request. Consumption follows a fixed order — included allowance, then gifted credit, then the wallet — so the most-perishable credit is always spent first and the user's own money last. And the migration mattered as much as the model: converting the old counters to true remainders had to be done carefully and idempotently, because the naive conversion would have resurrected credit users had already spent — over-crediting as the cost of ending under-crediting is not a trade, it is a second bug.
A hard stop is a promise
When credit is gone and no top-up is configured, the product stops — a clear gate, a plain statement, a path to continue. It does not degrade quietly: no silent switch to a thinner model, no shortened answers, no throttled memory. The stop is the promise, and its value is exactly the trust it protects: every answer this product has ever given was produced at full capability, so no user ever has to wonder whether a disappointing reply was the product or the meter. A quality cliff you can see is a paywall; one you cannot see is a lie about what the product is.
The same promise, made from the platform's side, produced the phase's best war story. Cost being a lookup means a model id with no rate row values work at zero — and when a new model generation arrived without its row, one of our cost surfaces did precisely that: tokens recorded faithfully, cost silently flatlined, and the books were wrong for days before anyone noticed, because nothing fails when you undercharge. The fix has three layers, in escalating bluntness: a tripwire log on any rate miss with non-zero tokens; the idempotent backfill that repaired history from stored tokens once the row landed; and — the deliberate one — a fail-closed preflight: a turn arriving on that surface with a model that has no matching rate is now refused, loudly, before any answer streams, until someone ships the price. Blocking real usage over a missing table row is a heavy trade and we chose it on purpose. A system that would rather refuse work than misprice it is making the same promise to its own books that the hard stop makes to users.
What transfers
For anyone metering LLM-shaped work: record the physical quantity exactly and treat money as a derived view, so price changes and corrections never rewrite history. Enforce at work boundaries and let in-flight work complete — bound your write-off at one unit rather than truncating the product. Make purchased credit a wallet with the semantics a stranger would call fair, because the counter that quietly resets is how decent products end up with gift-card patterns. Disclose the costs the user controls before they spend; absorb the costs only you control. And fail closed on a missing price — undercharging is the failure mode nothing alerts on, and the books it quietly ruins are your own.
Postscript, August 2026. Three days after this entry, the cache seam closed: a provider can bill cache writes at a premium over ordinary input, and whether a turn hits a warm or cold cache is a function of our scheduling and traffic, not anything the user chose — so the billed figure now charges cache writes at the plain input rate and the premium is absorbed as platform cost. A user should never pay more because our cache went cold. The wallet semantics held and spread: the same never-expires, reconcile-at-rollover pattern now backs gift credit and the org pool, and redeemable credit codes were later built on the same grant primitive rather than a new one. A third cost rail arrived — external connector actions with real per-call supplier prices — and shipped as exact pass-through at cost, with the ambiguity in its rate detection deliberately biased so any misclassification undercharges us rather than overcharges the user. And the whole system went through two adversarial launch audits, the second of which found real defects that were fixed the same day — which we mention not for the count but for the posture: a billing system is the one part of a product where the audit finding you fix quietly today is the churned customer or the incident write-up you do not publish next quarter.