A run that succeeds on the third attempt after a long queue wait can cost more than a paid run that succeeds once — even when the tokens are free.
That sounds obvious until you look at how most teams measure. They count tokens because tokens are the thing on the invoice. They ignore minutes because minutes never appear anywhere. Then they ship a pipeline whose real cost per accepted output is two or three times what the dashboard claims, and nobody can explain why the week felt expensive.
So here is the only rule worth carrying into free capacity: put tokens and minutes in the same ledger, then divide by outputs you actually kept. Not outputs you generated. Outputs you accepted.
The two currencies
Tokens are the cheap currency and the easy one to see. Minutes are the expensive one and the easy one to hide behind the word "waiting".
You need a number for your own minute. Use your loaded hourly rate divided by sixty. If you are not opportunity-cost constrained — a solo developer on a quiet week — pick a floor value anyway, because a zero here silently guarantees the ledger will always favor free.
The interesting subtlety is that queue wait is not fully lost. If you submit a job and go review a pull request, the wait is overlap, not cost. So the ledger carries an idle weight: how much of the wait actually blocked you. Set it to 0.0 for a fire-and-forget batch, 1.0 for a synchronous call in a request path, and something honest in between for interactive work.
The harness
The script below is deliberately boring. One dataclass, one cost function, one JSONL file you append to on every run. It is unexecuted on your provider — you supply the numbers.
# runledger.py — append-only run accounting for AI workloads
import json, time
from dataclasses import dataclass, asdict
@dataclass
class Run:
task_id: str
lane: str # "free" or "paid"
attempts: int = 0
tokens_in: int = 0
tokens_out: int = 0
queue_s: float = 0.0 # submit -> first byte
compute_s: float = 0.0 # first byte -> last byte
review_s: float = 0.0 # your minutes reading and fixing
accepted: bool = False
IDLE_WEIGHT = 0.5 # fraction of queue wait that actually blocked you
def cost_of(run, price_in, price_out, minute_rate):
token_cost = (run.tokens_in / 1e6) * price_in
token_cost += (run.tokens_out / 1e6) * price_out
review_cost = (run.review_s / 60.0) * minute_rate
wait_cost = (run.queue_s / 60.0) * minute_rate * IDLE_WEIGHT
return token_cost + review_cost + wait_cost
def log(run, path="runs.jsonl"):
with open(path, "a") as f:
f.write(json.dumps(asdict(run)) + "\n")
The number you actually decide on is not the sum. It is the sum divided by accepted runs.
def cost_per_accepted(path, price_in, price_out, minute_rate):
total, kept = 0.0, 0
for line in open(path):
run = Run(**json.loads(line))
total += cost_of(run, price_in, price_out, minute_rate)
kept += run.accepted
return total / max(kept, 1)
One line of arithmetic, and the retries stop being invisible. Three attempts on the same prompt re-bill the input tokens and re-bill your review minutes. A left-joined cost model hides that entirely, because it prices the successful call and forgets the two funerals before it.
A measurement plan that survives scrutiny
Take twenty tasks you have already solved by hand. Same prompts, same acceptance criteria, both lanes, run in the same hour so you are not comparing a quiet morning to a busy evening. Log accepted by hand — that flag is the whole measurement, and it is the one thing you cannot automate honestly.
Twenty is a smell test, not a benchmark. If the gap is under thirty percent, your sample is too small to conclude anything and you should decide on constraints instead of arithmetic. If the gap is a factor of two, you have a real answer and you can stop measuring.
The illustrative arithmetic, with the numbers left blank on purpose: suppose a free lane needs 1.6 attempts per accepted output at zero token price but an extra nine minutes of queue wait and review per attempt, while a paid lane needs 1.1 attempts at a nonzero price. Whether free wins depends entirely on your minute rate and your idle weight. That is not a dodge — it is the actual shape of the problem, and any vendor claiming otherwise is quoting their numbers at you.
When free capacity is the wrong bet
| Situation | Why free loses | What to do |
|---|---|---|
| Deadline-bound run | Queue variance is the dominant term, not price | Use capacity with a queue you can predict |
| High retry amplification | Each retry re-bills input tokens plus your review minutes | Fix the prompt before scaling the lane |
| Review-heavy output | Your minutes dwarf token cost by an order of magnitude | Optimize the acceptance loop first |
| Synchronous request path |
IDLE_WEIGHT is 1.0 and the wait is pure latency |
Free capacity belongs in batch, not in the path |
The last row is the one people get wrong most often. Free capacity is a batch instrument. Putting it behind a user-facing request converts a cost saving into a latency budget you did not agree to spend.
I ran this harness shape against free model access and the free server option in MonkeyCode to see whether the ledger changes a decision I had already made. It did, in one place: batch work moved, interactive work did not. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Verify the current limits yourself before you plan around them — availability and quotas are exactly the kind of fact that should never be taken from an article, including this one.
Limitations, and who should skip this
Your minute rate is a fiction if you are not actually constrained by time. The ledger does not capture model drift, so numbers older than a few weeks are archaeology. And it only measures the runs you remembered to log, which is why the append happens in code rather than in your head.
Skip this if you run fewer than ten jobs a day — eyeballing two runs will teach you the same lesson faster. Skip it if a paid lane already fits inside your latency budget at a price you do not think about. And skip it if your pipeline cannot be instrumented at all, because a cost model you cannot feed is just a spreadsheet with opinions.
If you want to run the harness against a free lane before you spend anything, try the free model access and the free server option, log twenty runs, and let cost_per_accepted argue with you.
Top comments (0)