We price the call before we make it.

Every other cost tool reads the bill after the money is gone. Meter reads the prompt and forecasts the bill first, in about three hundredths of a millisecond, with no network and no database. That forecast is what a budget ceiling reserves against, which is the only reason the ceiling can hold.

See it compute 0.031ms · no I/O · deterministic
0Cache
1Structural override
2Explicit length
3Task stacking
4Bucket factor
5History
6Clamp and bound

One step of seven, and the only one with nothing to wait on.

Every request through Meter runs the same lifecycle. Steps 1 to 5 have to finish before a single byte reaches the provider, so the estimate cannot afford a database round trip. It does not make one. The engine is pure computation over the prompt already in hand.

1Authenticateresolve key to project
2Attributefeature, actor, trace
3Estimatethis engine
4Reservehold bound_cost_usd
5Breakerthrottled? revoked?
6Forwardstream, tee for usage
7Captureprice actual, release
steps 1 to 5 complete before the provider sees a byte
off the request path

predictor/refresh.py reads the ledger on a 120 second timer, fits on the older 75% of rows, scores against the newest 25%, and installs only the per-feature factors that beat their own held-out rows. It writes into memory that predict() reads. It never queries anything during a request.

The forecast is not decoration.

Step 4 reserves real budget against it before the call goes out. Reserving after the fact is the bug: a thousand simultaneous requests all read the same healthy balance and all proceed.

Learning runs off the hot path.

A background task refits from the ledger every 120 seconds into an in-memory dict. The request path only ever reads that dict, so the engine gets smarter without getting slower.

The obvious model is wrong, and we measured how wrong.

The tempting formula is output ≈ ratio × input_tokens. Longer prompt, longer answer. Toggle it on and watch the fit fall apart against real calls.

no line fits — R² = 0.9%
input tokens →↑ output tokens
+0.096correlation between input and output length
0.9%of output variance that input length explains
2task buckets that were negatively correlated
Output length is set by the scope of the work requested, not by the length of the request.

Build me a CRM is four tokens and produces five thousand. Fix this typo, followed by an 800-token file, produces twelve. Any model keyed on input length has it exactly backwards.

Every call returns two numbers. Hover to turn them over.

Conflating these is why the first version got both wrong. A forecast wants to be accurate. A ceiling check wants to be safe. Those are different objectives and one number cannot serve both.

predicted_output_tokens

What will this probably cost?

hover →

Dashboard figures, Treasurer runway, cost per outcome. Accuracy is the job, so it carries no safety padding at all.

bound_output_tokens

What could it cost at worst?

hover →

The hard ceiling reserves against this one. With max_tokens set, output cannot exceed it, so the guarantee is structural rather than statistical.

the bug this fixed

The forecast used to carry a 1.30 safety buffer. It was removed on purpose. The buffer and the history factor are both fitted as actual over scope, so applying both computed the correction twice and median error rose from 77% to 204% as the loop learned. Safety now lives entirely in the bound, where it cannot corrupt the forecast.

Seven stages. Hard data exits early, soft signals stack.

Stages 0 to 2 are a deterministic waterfall: if hard evidence exists, take it and leave. Only when none does do the soft signals compound, and each of those terms is bounded so a keyword-stuffed prompt cannot run away. Scroll, and the readout on the left carries one real prompt through every stage.

  1. 0
    Cache

    Have we answered this exact call before?

    The key covers the payload, the model, max_tokens, and the attribution tags. Tags belong in it because they select the history correction seven stages later. Leave them out and one team gets served another team's calibrated answer, silently, and worse the better the correction gets.

    key = sha256(payload, model, max_tokens, project, feature, actor)
  2. 1
    Structural override

    Is the shape of the answer already fixed?

    A JSON-schema response is bounded by its own structure. The input term stays in, because pulling entities out of a 10k-token document is not the same size job as describing one user, and a flat constant would repeat the mistake this whole engine exists to fix.

    scope = 100 + input_tokens x 0.1
  3. 2
    Explicit length

    Did the prompt just tell us the length?

    If the user stated it, we do arithmetic instead of guessing, and this is the most accurate rule in the system. Coverage is the highest-value detail in the engine: one missed hyphenated form, two-sentence against in two sentences, moved total error from 84% to 192%. More than half the error came from a single hyphen.

    "in three sentences" -> 3 x 35 = 105
  4. 3
    Task stacking

    No hard signal. Read the scope of the work.

    A prompt is rarely one task. Summarize this AND write the query is both, so scopes add rather than compete. Five multipliers then read intent: how forceful the verb is, whether reasoning is being demanded, terse against exhaustive, how much of the message is instruction rather than pasted material, and whether it opens with a generation verb.

    scope = min(80 + sum(tasks), 1500) x verb x cot x qualitative x instruction x imperative
  5. 4
    Bucket factor

    Scale it to how this kind of task actually behaves.

    A fitted per-bucket multiplier, searched offline against held-out calls and loaded from data/fitted.json at startup. This is the number written to the ledger as scope_tokens, and it has to be, because it is the fixed baseline the learner fits its correction against. Fit against anything downstream and each refresh divides by the last one and oscillates forever.

    scope_tokens = scope x factor[bucket] (code = 4.03)
  6. 5
    History

    Learn how this one team prompts.

    Teams are consistent, because their prompts come from one template written by one person. Inside a single template, observed output length varies by only 1.0 to 1.4x, against 10x across templates. That regularity is the entire mechanism. The factor is a shrunk median of actual over predicted, looked up down a ladder from most specific to least.

    factor = exp( n x ln(median(actual/scope)) / (n + 1) ) (project,feature,actor) -> (project,feature) -> (project) -> (feature) -> (bucket,model) -> (bucket) -> 1.0
  7. 6
    Clamp and bound

    Two numbers leave the engine.

    max_tokens clamps the forecast, it never replaces it. Letting it short-circuit the pipeline measured 594% error against 192% for clamping, because max_tokens is a safety valve most SDKs set by default, not a statement of intent. A team with 4096 boilerplate would otherwise get one identical prediction for every prompt they ever send.

    predicted = min(max(15, scope x factor), bound) bound = max_tokens, else p95[bucket] x 1.2, else 4096

The output-token formula, whole.

Everything above collapses to this. Read it inside out: build a scope from the prompt, scale it by what this kind of task normally does, scale again by what this specific team normally does, then hold it under a ceiling it structurally cannot cross.

scope=min( base + Σ task_scopes, 1500 )
× verb × cot × qualitative × instruction × imperative
scope_tokens=scope × factor[bucket]the value written to the ledger, and the baseline the learner fits against
history=exp( n · ln( median( actual / scope_tokens ) ) ÷ (n + 1) )geometric, because these factors multiply. An arithmetic blend inflates factors below 1 far more than it deflates those above it.
bound=max_tokens, else p95[bucket] × 1.2, else 4096exact whenever the caller sets max_tokens
predicted=min( max( 15, scope_tokens × history ), bound )
Both numbers are then priced through the same versioned rate table the ledger uses, so a forecast and the row it is later compared against can never disagree about what a token costs.predicted_cost_usd = price(input_tokens, predicted, model)

One prompt, every step shown.

This is not a mock-up. These are the numbers predict() returns for that prompt on the shipped engine.

predict.trace
fast path

Had the prompt said "in three sentences", stage 2 fires first and short-circuits all of it: 3 × 35 = 105, then straight to the bucket factor. A stated length is the most accurate rule we have, so we never guess past it.

Measured on real calls, held out.

A prediction engine that quotes one accuracy number is hiding something, because accuracy depends entirely on the shape of the traffic. Here is every shape we tested, including the one we are worst at.

traffic shapemedian errorwithin 2xsample
Templated traffic, held out6.8%96.1%1,224 calls / 32 features
Templated, live through the proxy9.7%97%64 fresh prompts
Templated, cold start with no history79.5%not measuredsame rows
Open-ended strangers' prompts49.2%54.7%75, locked test set

Median absolute percentage error. We do not quote MAPE: a handful of 20-token answers dominate it permanently and it flatters nobody honestly.

What we are good at.

Templated production traffic, which is what agent workloads actually are. 24 of 32 feature tags sit at or under 15% median error. Best is entity-tag at 0.9%.

What we are not.

Open-ended prompts from strangers sit at 49% and that is near a hard ceiling. Output spread is std(log) = 1.16 and our features explain R² of 0.28, where reaching 30% error would need roughly 0.88.

The honest onboarding claim.

A brand new feature tag starts around 80% error and needs about 20 calls of its own. History does not generalise between features, and we measured that rather than assuming it.

We label every constant, including the ones we lost.

A number you cannot trace is a number you cannot trust. These are the values the engine loads at startup, not the ones in the design doc. Note the two marked tuned to neutral: we hand wrote those cues, an offline search over real traffic found they earned nothing, and we shipped the search result instead of our own idea.

tokens per word1.33property of English
tokens per sentence35fitted offline
base conversational scope80fitted offline
summary / code / extract / search+250 / +0 / +50 / +400fitted offline
verb intensity, build / fixx1.2 / x0.15fitted offline
chain-of-thought cuex1.0tuned to neutral
opens with a generation verbx1.2measured
safety buffer on the forecastnonemeasured
shrinkage k, geometric1measured

The optimizer went further than was comfortable. On real traffic our task keywords fire on 17.4% of prompts and chain-of-thought cues on 1.7%. Predicting a flat constant per bucket and ignoring the prompt entirely scores 51.4% against 44.2% for the full machinery, so the scope extraction is worth about seven points, not the bulk of the estimate. That is written down here rather than quietly dropped.

The loop the prior art left dead.

Pre-flight cost estimators exist. The ones we evaluated stopped at the guess: they never fed real usage back, so their learning tier was permanently inert. Closing that loop is the whole contribution here. At capture, the actual token count lands in the ledger beside the prediction that reserved for it, and a gated background fit turns those pairs into a per-feature correction.

predict
reserve
call
capture
refit
actual
feeds
forecast
82.6% → 28.8%median error once the loop installs, k-fold out of sample
65.0% → 31.6%the same method on a second, independent set of 8 templates
7 of 8features the live gate accepted, every one an improvement

The gate is per key, not all or nothing. An earlier version scored the whole candidate batch on a pooled median, so one bad feature vetoed four good ones and the loop silently installed nothing for days. It also scored a shrunk candidate and then installed the raw one, which means it validated an object that never existed. Both are fixed and pinned by a test, and both are the kind of bug that only appears when you boot the real thing against a real ledger.

Every row in the ledger was reserved before it happened.

See the predictions land against their actuals, live, on the dashboard.

Open dashboard