DEV Community

Taylor Lin
Taylor Lin

Posted on

Admission Control Before Routing: A Free-Tier Quota Ledger for Agent Jobs

Picture a nightly batch: 400 classification calls, one free model endpoint, one free server, one cron entry. At 03:12 the run exits with code 0. The logs are green. The next morning half the labels are wrong, because the run quietly started receiving truncated or fallback responses once the quota window closed — and nothing in the pipeline noticed.

That scenario is constructed, but the arithmetic behind it is not. Almost every free-tier agent job I have seen has routing logic and no admission logic. It knows where to send work. It does not know whether it can afford to send work at all.

This article builds the missing layer: a local quota ledger plus a four-leaf decision tree that runs before any request leaves the machine. MonkeyCode's free model access and free server option are the deployment targets I use for the worked examples, because that is the combination most likely to hit this failure. Treat the availability claims as operator-supplied and re-read the project's current terms before you depend on them.

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

Terms first, tree second

Five terms carry the whole design. If you only take one, take replay cost.

  1. Quota window — the period after which the provider's allowance resets, if it resets. You do not control it, so you must assume it can close mid-batch.
  2. Unit cost — tokens charged per work item, input plus output, measured rather than guessed.
  3. Replay cost — the extra units burned by retries and re-runs. A 15% retry rate is a 15% tax on your budget, and it is invisible if you only count successful calls.
  4. Headroom — budget - spend, where spend includes failed and retried attempts, not just the ones that returned 200.
  5. Admission — the decision to not start a job, or to start a smaller version of it, before the first request is issued.

Routing asks where. Admission asks whether, and how much. Skipping the second question is how you get exit code 0 with wrong output.

The ledger artifact

The script below is standard library only and deliberately boring. It appends one JSON line per attempt, so retries are billable by construction.

# ledger.py - Python 3.11+, stdlib only
import json, os
from dataclasses import dataclass, asdict

LEDGER = os.environ.get("LEDGER_PATH", "usage.jsonl")
BUDGET = int(os.environ.get("QUOTA_BUDGET", "200000"))
SAFETY = 0.80          # never plan past 80% of headroom
REPLAY = 0.15          # measured retry tax, replace with your own

@dataclass
class Entry:
    ts: float
    job_id: str
    tokens_in: int
    tokens_out: int
    status: str        # ok | retry | failed
    idem_key: str

def append(entry: Entry) -> None:
    with open(LEDGER, "a", encoding="utf-8") as f:
        f.write(json.dumps(asdict(entry)) + "\n")

def load() -> list[Entry]:
    if not os.path.exists(LEDGER):
        return []
    with open(LEDGER, encoding="utf-8") as f:
        return [Entry(**json.loads(line)) for line in f if line.strip()]

def spent() -> int:
    return sum(e.tokens_in + e.tokens_out for e in load())

def headroom() -> int:
    return BUDGET - spent()

def plan(items: int, per_item: int, deadline_bound: bool) -> tuple[str, dict]:
    unit = per_item * (1 + REPLAY)
    want = items * unit
    left = headroom()
    if want <= left * SAFETY:
        return "A", {"items_now": items, "items_later": 0}
    if want <= left:
        now = int(left * SAFETY // unit)
        return "B", {"items_now": now, "items_later": items - now}
    if not deadline_bound:
        per_window = int(BUDGET * SAFETY // unit)
        return "C", {"windows": -(-items // per_window), "per_window": per_window}
    now = int(left * SAFETY // unit)
    return "D", {"items_now": now, "items_rerouted": items - now}
Enter fullscreen mode Exit fullscreen mode

Run it with an explicit budget for the window you are about to use:

QUOTA_BUDGET=200000 LEDGER_PATH=usage.jsonl python -c "import ledger; print(ledger.plan(130, 900, False))"
Enter fullscreen mode Exit fullscreen mode

Two design choices matter more than the code. First, status is recorded but never filtered out of spent() — a failed attempt still consumed quota. Second, SAFETY is a hard multiplier, not advice: it forces a plan to survive a 20% estimation error without touching the last fifth of the allowance.

The tree, with four worked leaves

Hold the fixture constant: BUDGET = 200,000, already spent 60,000, so headroom = 140,000; SAFETY = 0.80 gives a planning ceiling of 112,000; REPLAY = 0.15. All four numbers below are hand arithmetic against that fixture, not provider measurements.

Leaf A — admit unchanged. 40 items at 900 units, want = 40 × 900 × 1.15 = 41,400, under the 112,000 ceiling. Run it, record it, move on. Leaf A is where you want 90% of your batches to land; if it is not, your per_item estimate is wrong.

Leaf B — admit a shrunken batch. 130 items at 900 units, want = 134,550. That exceeds the ceiling but fits inside 140,000, so the right move is partial admission: items_now = 112,000 // 1035 = 108, with 22 deferred. Validate chunk one's output before submitting chunk two. If chunk one's labels look wrong, you just saved 22 items of quota and a full-window outage.

Leaf C — defer to the next window. 300 items at 900 units, want = 310,500, no deadline. per_window = 160,000 // 1035 = 154, so the job needs two windows. Note that even a fresh window cannot absorb it in one pass — deferral without re-chunking just moves the failure.

Leaf D — reroute the overflow. Same 300 items, but deadline-bound. Take 108 locally and push the remaining 192 to the free server or a local model path. Leaf D is the only branch that depends on a property of your job rather than of your budget: it is valid only if every item carries an idempotency key and the reroute target produces comparable output. If either is false, fall back to C and negotiate the deadline.

Test the tree, not the model

The decision logic is deterministic, so it is cheap to pin down:

  1. Boundary fixtures. Assert that want == 0.8 × headroom returns A and want == 0.8 × headroom + 1 returns B. Off-by-one at the ceiling is the most common regression.
  2. Injected clock and path. Set LEDGER_PATH to a temp file per test; never read the developer's real ledger.
  3. Conservation property. For every fixture, items_now + items_later (or items_rerouted) equals the input count. A leaf that loses work items is a bug, not an optimisation.
  4. Retry accounting. Append a status="retry" entry and assert that spent() increases. If it does not, your replay tax is not being billed.
  5. Window rollover. Move the ledger file aside, re-run, and confirm the plan returns to leaf A rather than remembering stale headroom.

Limitations, and who should skip this

The ledger counts what your process recorded. It cannot see another process using the same account, and without a file lock two concurrent writers will interleave lines. It also has no pricing knowledge: per_item and REPLAY are yours to measure, and they drift when prompt length or retry behaviour changes. Most importantly, admission control does not override a provider's own rate limiter; if the endpoint returns 429, stop the batch instead of retrying around the limit.

Skip all of this if you run one interactive session at a time, if your work items are genuinely non-repeatable and cannot be idempotent, or if you already have real billing dashboards with per-key spend. For a nightly batch on a free tier with a closing quota window, the ledger is roughly forty lines and it converts a silent failure into a printed plan.

If you want a place to try the tree end to end, MonkeyCode's free model access and free server option give you a realistic budget constraint to run these leaves against — check the current quota and server terms on the project page first, then let the ledger, not the cron entry, decide what runs tonight.

Top comments (0)