Decision 03 of 05 · 2 min read
Spend is reserved before a run starts
How can a chat tool have a hard budget?
Each run reserves its worst-case cost in a Firestore transaction before any model or BigQuery call. The worst case comes from the per-run caps on tokens and billed bytes. The same transaction checks:
- that a repeated click is not a new run;
- the person's daily caps;
- the pilot's total.
When the run finishes, a second transaction replaces the reservation with the run's estimated cost, from its billed bytes and tokens at configured prices, exactly once; a run with no usage record keeps the worst case. These limits cover estimated Gemini and BigQuery usage at configured prices; hosting costs are separate. Two concurrent requests cannot both squeeze under a cap, because Firestore retries a transaction whose reads changed. A run cut off by a restart keeps its full reservation, because its real cost is unknown.
On the data side, BigQuery runs as a separate account with read access to the pilot dataset and no write permission; the SQL guard limits queries to approved tables. Every query is dry-run first and refused if it would exceed the remaining byte allowance, and the real job carries its own byte cap.
The trade-off
Reserving the worst case (about USD 0.45 a run) admits fewer runs at once than the typical cost would: the answers recorded for the replay were estimated at under a cent each. For a pilot with hard limits, refusing early beats overspending.
What was shown
- StagingEach run reserves its worst-case estimated cost in one Firestore transaction before it starts; of three concurrent requests over a cap, the extra one was refused.
- StagingOn staging each run reserved USD 0.446, its worst-case estimated cost, before it started.
- StagingRepeated or concurrent submissions of the same request produced one run.
- StagingWrite attempts made directly as the execution account, outside the guard, were refused with 403, on staging and in the live guard-rail suite. Public datasets stay readable to any account, so the SQL guard, not IAM, decides which tables a query may use.
- OfflineA run cut off by a restart is closed as interrupted and keeps its full reservation.