Canary the Free Lane Before You Commit 2,000 Jobs
This week's AI threads argue about machines replacing us. Your queue has a smaller question. Should tonight's batch go to the free lane? You commit 2,000 jobs at 18:00. They look cheap: free model access, free server. A
This week's AI threads argue about machines replacing us. Your queue has a smaller question. Should tonight's batch go to the free lane?
You commit 2,000 jobs at 18:00. They look cheap: free model access, free server. At 02:00 you have 300 artifacts. The rest burned quota on rejects.
That is not a model problem. It is an unpriced decision. You queued work before measuring the lane.
This post builds a preflight probe. It samples the free lane, produces a quote, and picks one of four actions. The probe is cheap, reversible, and boring on purpose.
The decision you keep making blind
Free capacity is shared capacity. Its acceptance rate moves with the hour. So does its latency.
You cannot read that from a pricing page. You read it from a small, timed sample.
Two numbers decide your batch:
- Accept rate — share of probes that return a usable completion.
- Tail latency — p95 seconds per probe at your real concurrency.
Add one constraint you already own: deadline slack. The minutes between "batch done" and "results needed."
If projected wall clock exceeds slack, free is the wrong bet. No metric fixes that.
Build the probe: 40 lines, stdlib only
Point it at any OpenAI-style chat endpoint you have access to. Model name and key come from flags and env.
#!/usr/bin/env python3
"""probe_free_lane.py - quote free-lane capacity before committing a batch."""
import argparse, json, os, time, urllib.error, urllib.request
from concurrent.futures import ThreadPoolExecutor
def pct(xs, p):
if not xs:
return None
xs = sorted(xs)
return xs[max(0, min(len(xs) - 1, int(round(p / 100 * (len(xs) - 1)))))]
def probe(args, i):
body = json.dumps({
"model": args.model,
"messages": [{"role": "user", "content": "Reply with the single word OK."}],
"max_tokens": 8,
"temperature": 0,
}).encode()
req = urllib.request.Request(
args.endpoint.rstrip("/") + "/chat/completions",
data=body,
headers={
"Content-Type": "application/json",
"Authorization": "Bearer " + os.environ.get(args.key_env, ""),
"X-Canary-Id": args.canary_id,
},
)
t0, status, tokens, note = time.monotonic(), 0, 0, ""
try:
with urllib.request.urlopen(req, timeout=args.timeout) as r:
status = r.status
payload = json.loads(r.read() or b"{}")
tokens = int(payload.get("usage", {}).get("total_tokens", 0))
except urllib.error.HTTPError as e:
status, note = e.code, (e.headers.get("Retry-After") or "")[:8]
except Exception as e:
status, note = -1, type(e).__name__
rec = {"i": i, "status": status, "latency_s": round(time.monotonic() - t0, 3),
"tokens": tokens, "retry_after": note, "canary_id": args.canary_id}
print(json.dumps(rec), flush=True)
return rec
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("--endpoint", required=True)
p.add_argument("--model", required=True)
p.add_argument("--key-env", default="FREE_LANE_KEY")
p.add_argument("--canary-id", default=time.strftime("canary-%Y%m%dT%H%M"))
p.add_argument("--size", type=int, default=12)
p.add_argument("--concurrency", type=int, default=3)
p.add_argument("--jobs", type=int, default=2000)
p.add_argument("--slack-min", type=float, default=240.0)
p.add_argument("--timeout", type=float, default=30.0)
a = p.parse_args()
with ThreadPoolExecutor(max_workers=a.concurrency) as ex:
recs = list(ex.map(lambda i: probe(a, i), range(a.size)))
ok = [r for r in recs if r["status"] == 200]
lat = [r["latency_s"] for r in ok]
accept = len(ok) / len(recs)
p95 = pct(lat, 95) or a.timeout
waves = a.jobs / max(1, a.concurrency)
projected_min = waves * p95 / 60
tokens = sum(r["tokens"] for r in ok)
burn = tokens / max(1, len(ok)) * a.jobs
if accept < 0.80 or projected_min > a.slack_min:
action = "ESCALATE_OR_DEFER"
elif accept < 0.95 or projected_min > 0.70 * a.slack_min:
action = "SPLIT"
else:
action = "RUN_FREE"
print(json.dumps({
"action": action, "accept_rate": round(accept, 3),
"p95_s": p95, "projected_min_optimistic": round(projected_min, 1),
"slack_min": a.slack_min, "projected_tokens": int(burn),
"short_circuit": all(r["status"] == 429 for r in recs[:4]),
}, indent=2))
Run it:
export FREE_LANE_KEY=...
python3 probe_free_lane.py \
--endpoint https://your-endpoint/v1 \
--model your-free-model \
--size 12 --concurrency 3 --jobs 2000 --slack-min 240
Twelve probes at concurrency three cost under a minute. That is under one percent of a 2,000-job batch.
Use the free model access for exactly this. Probing is what free capacity is good for. Then host the probe worker on the free server, next to the queue, so it measures the path your jobs actually take.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Read the quote like an operator
Expected output for a healthy lane:
{
"action": "RUN_FREE",
"accept_rate": 1.0,
"p95_s": 2.4,
"projected_min_optimistic": 26.7,
"slack_min": 240.0,
"projected_tokens": 14800,
"short_circuit": false
}
Label it optimistic. p95_s * waves assumes zero queueing growth after the canary. Real batches degrade; treat the number as a floor.
Now the decision table.
| Accept rate | Projected wall clock | Action | Why |
|---|---|---|---|
| ≥ 0.95 | ≤ 70% of slack | RUN_FREE |
Headroom absorbs one bad hour |
| ≥ 0.95 | 70–100% of slack | SPLIT |
Run half now, re-probe before the rest |
| 0.80–0.95 | any | SPLIT |
Rejects are the real tax, not latency |
| < 0.80 | any | ESCALATE_OR_DEFER |
Free lane is telling you no |
ESCALATE_OR_DEFER is not failure. It is the quote working.
One more rule: expire the quote. Fifteen minutes, then re-probe. Shared capacity does not hold a price overnight.
Short-circuit on four consecutive 429s. Stop the canary, keep the logs, do not retry.
Inject the failure before production does
Do not wait for the shared lane to rate-limit you. Reproduce it locally.
# fake_lane.py - local 429 injector for probe rehearsal
import json, random, time
from http.server import BaseHTTPRequestHandler, HTTPServer
FAIL_RATE, LATENCY = 0.45, 0.08
class H(BaseHTTPRequestHandler):
def do_POST(self):
self.rfile.read(int(self.headers.get("Content-Length", 0)))
time.sleep(LATENCY)
if random.random() < FAIL_RATE:
self.send_response(429)
self.send_header("Retry-After", "30")
self.end_headers()
self.wfile.write(b'{"error":"rate_limited"}')
return
body = json.dumps({"choices": [{"message": {"content": "OK"}}],
"usage": {"total_tokens": 26}}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("127.0.0.1", 8099), H).serve_forever()
Run it, then point the probe at http://127.0.0.1:8099/v1. You should see accept_rate near 0.55 and action ESCALATE_OR_DEFER.
Raise FAIL_RATE to 0.9 and confirm the short-circuit flag fires. Tune thresholds against that, not against a good day.
What the quote does not tell you
Be honest about the limits:
- It measures one window, not your batch. Prompt length and output size change burn.
- Token burn scales with your real
max_tokens, not the 8-token probe. - Free latency is not an SLO. The p95 you measured can vanish in ten minutes.
- The projection ignores your own downstream stages: validation, storage, uploads.
- A canary that is 1% of a 200-job batch is too expensive. Probe smaller jobs only.
Skip this approach if you have a hard p99 latency commitment, strict data-residency rules, or jobs longer than the free server tolerates. Buy the boring capacity instead. Free is the wrong bet when a missed deadline costs more than the bill.
Cleanup and rollback
Every probe carries a X-Canary-Id header. That is your undo button.
- Filter artifacts by
canary_idprefix and delete them. - Keep
canary.jsonlfor the weekly cost review; drop payloads. - Do not change billing plans to run a canary. Ever.
- Add a
STOP_PROBEflag file. Check it between waves. - If the probe itself errored, defer the batch. A broken ruler is not a green light.
Rollback of a RUN_FREE decision is harder than rollback of a canary. That asymmetry is the whole argument for probing first.
The threshold is the artifact
Your canary is not the deliverable. Your accept-rate floor and slack ratio are.
Write them down with a rationale. "0.95 accept rate, because one reject in twenty doubles our tail wall clock." Then re-check them monthly against real batches.
If you want a free place to rehearse this loop, MonkeyCode's free model access and free server option are enough to run the probe, the injector, and the decision table end to end. Verify the current quota in your dashboard before you size a batch against it; allowances change.
Probe twelve jobs tonight. Then decide what the other 1,988 are worth.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.