A free token allowance only becomes a real decision input once you can count tokens on your own side of the wire, per task, without waiting for a vendor dashboard to catch up.

That sentence is the whole argument, and everything below is the machinery that makes it true. If you cannot attribute spend to a task, then "free tier or self-hosted?" stays a vibe instead of an answer. So let me show you the smallest ledger I would accept before making that call, plus a fence that stops a runaway agent before it eats a whole allowance in one afternoon.

Quick context on the allowance itself: MonkeyCode currently offers free model access, and the operator's figure at the time of writing is on the order of ten million tokens, alongside a free server option. Verify the current number, its expiry, and any per-key limits inside your own account, because promotional figures move and this article must not be your source of truth for them.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

Why a dashboard cannot be your budget control

A usage dashboard answers "how much did I spend", which is an accounting question asked after the fact. Your agent needs "may I spend more", which is a control question asked before the call. Those are two different systems with two different failure modes, and conflating them is how teams discover a hard cap at 2am.

Three gaps bite most often. Aggregation lag means a burst can stay invisible for minutes while your client keeps firing. Attribution means org-level totals rarely map cleanly onto one task inside your product. Retries mean a client-side retry storm can double real calls before any counter you own notices it. None of that is a vendor flaw; it is just the difference between billing and budgeting.

So what does the client-side version look like? It looks boring, which is exactly the point.

Step 1 — Define the task record before you write a wrapper

Pick the unit you actually make decisions about, and it is almost never "a request". It is usually "a summarize-issue run", "one review pass", or "one agent turn", because that is what you can price and compare across providers. Choose the unit your product manager would ask about, not the unit your HTTP client happens to send.

Write the record down as fields: task id, estimated prompt tokens, estimated completion tokens, the provider's reported total when available, a timestamp, and a short note. That note field matters more than people expect, because six weeks later the only way to explain an outlier is the sentence you wrote beside it.

Do not add a schema you cannot fill from inside the process. Any field that needs a manual step will be empty within two weeks, and an empty column is worse than no column at all.

Step 2 — Put the counter in the client, with a drift column

Here is a stdlib-only ledger you can drop into a repository today. It estimates tokens with a character heuristic, records the provider's reported number when you have one, and keeps the difference visible instead of hiding it.

# ledger.py — provider-agnostic token ledger with a client-side fence. Stdlib only. import json import os import time LEDGER_PATH = os.environ.get('LEDGER_PATH', 'token_ledger.jsonl') TOKEN_BUDGET = int(os.environ.get('TOKEN_BUDGET', '0')) # 0 disables the fence TOKEN_DIVISOR = int(os.environ.get('TOKEN_DIVISOR', '4')) # calibrate against reported usage FENCE_AT = float(os.environ.get('FENCE_AT', '0.8')) # spending stops at 80% of budget  class BudgetExceeded(RuntimeError): pass def estimate_tokens(text): # Cheap heuristic, never a claim. Calibrate TOKEN_DIVISOR on your own prompts.  return max(1, len(text) // TOKEN_DIVISOR) class Ledger: def __init__(self, path=LEDGER_PATH, budget=TOKEN_BUDGET): self.path = path self.budget = budget self.rows = self._load() def _load(self): if not os.path.exists(self.path): return [] rows = [] with open(self.path, encoding='utf-8') as handle: for line in handle: line = line.strip() if not line: continue try: rows.append(json.loads(line)) except json.JSONDecodeError: continue return rows @property def est_spent(self): return sum(row['est_total_tokens'] for row in self.rows) @property def fence(self): return int(self.budget * FENCE_AT) if self.budget else None def headroom(self): if not self.budget: return None return max(0, self.fence - self.est_spent) def record(self, task_id, prompt, completion, reported_total=None): est_prompt = estimate_tokens(prompt) est_completion = estimate_tokens(completion) row = { 'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), 'task_id': task_id, 'est_prompt_tokens': est_prompt, 'est_completion_tokens': est_completion, 'est_total_tokens': est_prompt + est_completion, 'reported_total_tokens': reported_total, } with open(self.path, 'a', encoding='utf-8') as handle: handle.write(json.dumps(row) + '\n') self.rows.append(row) return row def summarize(self): by_task = {} for row in self.rows: slot = by_task.setdefault(row['task_id'], {'est': 0, 'reported': 0, 'runs': 0}) slot['est'] += row['est_total_tokens'] slot['reported'] += row['reported_total_tokens'] or 0 slot['runs'] += 1 for task_id in sorted(by_task): slot = by_task[task_id] reported = slot['reported'] drift = (slot['est'] - reported) / reported if reported else -1 line = '{} runs={} est={} reported={} per_run={:.0f} drift={:+.0%}' print(line.format(task_id, slot['runs'], slot['est'], reported, slot['est'] / slot['runs'], drift)) def guarded(ledger, task_id, prompt, call, reserve_completion=0, **kwargs): need = estimate_tokens(prompt) + reserve_completion headroom = ledger.headroom() if headroom is not None and need > headroom: raise BudgetExceeded( 'task {} needs about {} tokens, headroom is {}: park it and hand off'.format( task_id, need, headroom)) result = call(prompt, **kwargs) ledger.record(task_id, prompt, result['text'], result.get('reported_total_tokens')) return result if __name__ == '__main__': ledger = Ledger(path='/tmp/demo_ledger.jsonl', budget=20000) fake_call = lambda prompt, **_: {'text': 'ok ' * 60, 'reported_total_tokens': 45} for index in range(3): guarded(ledger, 'summarize-issue', 'x' * 400, fake_call, reserve_completion=200) ledger.summarize() 

Two choices in that file are deliberate. First, the heuristic is a labelled guess and never a claim, so the drift column in summarize() is what turns it into something you can trust. Second, the ledger is append-only JSONL, because a file you can grep during an incident beats a table you have to query with credentials.

Run the demo, then delete the demo file before rerunning it, or your totals inflate and you will blame the estimator instead of the operator.

rm -f /tmp/demo_ledger.jsonl && python ledger.py # summarize a real ledger, using the advertised 10M allowance as the fence LEDGER_PATH=./token_ledger.jsonl TOKEN_BUDGET=10000000 python -c "from ledger import Ledger; Ledger().summarize()" 

Calibrate the divisor honestly. Push twenty representative tasks through your real prompts, compare est_total_tokens against reported_total_tokens, and adjust TOKEN_DIVISOR until drift sits inside roughly ten percent. Code, JSON payloads, and non-English text will still break the heuristic, so keep the reported column populated for those task types.

Step 3 — Fence at 80 percent, not 100

A fence at the allowance ceiling is not a fence, it is an outage with extra steps. Set it at eighty percent so the remaining fifth pays for in-flight work, retries, and the migration window you will need if the allowance turns out to be smaller than you planned.

When headroom runs out, the agent should degrade rather than fail silently. Give it a park-and-hand-off path: stop the task, notify the owner, and let a human decide whether to spend real money or shrink the job. You can test that path cheaply by setting TOKEN_BUDGET=2000 and watching what the agent does on task number three.

Ask yourself the uncomfortable question here: if the allowance vanished tomorrow morning, which of your agents would you notice first? Your answer tells you which code path deserves the test.

Step 4 — Reconcile weekly and treat drift as a bug

Once a week, pull the provider's usage page and compare it against summarize() output for the same window. You are looking for two numbers: absolute spend, and the drift ratio per task type. Any task type drifting above roughly fifteen percent deserves a look, because that is the one that will surprise you at scale.

Reconciliation also catches the boring failure you will actually hit. Two client instances writing to two different ledger paths give you two truths and one very confusing invoice. Print the resolved LEDGER_PATH at startup and the problem disappears.

Step 5 — Run the decision table on your own numbers

Now the comparison, using your measured numbers rather than anyone's marketing copy. Fill the middle column from your ledger, and fill the right-hand column from a quote or a GPU bill you already pay.

Dimension Free allowance (fenced) Paid API Self-hosted
Cost driver allowance divided by measured task cost price per token times volume GPU hours plus ops hours
Best fit prototype to early product, low retry rate spiky volume, small team steady baseline, prompts stay in-network
Latency whatever the free pool gives you usually documented yours to tune, and yours to break
Failure mode allowance ends mid-sprint invoice surprise driver upgrade at 2am
Ops load low low high, and permanent
Exit cost rewrite endpoint, keep the ledger contract terms hardware you already bought
Disqualifier you need hard spend guarantees prompts must stay in-network nobody is on call

The fit criteria matter more than the cells:

  • The free allowance fits when measured task cost times monthly volume lands comfortably under the fence, retries are rare, and you can survive the day it ends.
  • A paid API fits when volume is spiky, nobody wants an on-call rotation for inference, and predictable latency matters more than unit price.
  • Self-hosting fits when you have a steady baseline, a real reason to keep prompts inside your network, and someone who enjoys driver upgrades.
  • None of them fit if you cannot measure per-task cost, which is where this article started.

Where the free option and the free server actually change the picture

The free server option is the interesting half of this promotion, because it removes the provisioning argument that usually pushes small teams toward hosted APIs. What it does not remove is the operational question: check what the server option includes today, and do not assume persistence, scaling behaviour, or long-term availability that the current terms do not state.

MonkeyCode is open source, so the honest evaluation is to read the code and run the ledger against it for a week. That is also why I refuse to put a benchmark table in this article: your prompts are the only benchmark that matters, and mine would be theatre.

Limitations, and who should skip this

The ledger is a budgeting tool and never a security control. Anyone with write access to the file can rewrite it, so headroom must not be treated as an access boundary.

The heuristic estimator will mislead you on JSON-heavy and code-heavy prompts unless the reported column stays populated. Promotional allowances also change without notice, so any article quoting a number, including this one, is a starting point for verification rather than evidence.

Skip this approach if you need contractual spend guarantees, if regulatory obligations require a vendor SLA, or if your workload is a single batch job you can price by hand. In those cases a spreadsheet beats a middleware layer, and pretending otherwise wastes your afternoon.

What I would do next

Keep the fence, keep the drift column, and let your own numbers pick the default. Run the free allowance against your ledger for a week and check whether the fenced-off fifth was actually enough, because that margin is the cheapest migration insurance you will ever buy.