🎛️ free-tier-ai-router
Get the most AI out of free keys, without hitting limits.
Complements model-fallback (which reacts after failure) and local-llm-router (which routes
across local machines). This skill routes across remote free tiers and acts before failure,
using measured quota budgets.
The measurements this is built on
Every number below came from probing 113 models across 5 provider keys with live completions on 2026-07-30 — not from docs, not from model names.
| Provider | Models answered a live prompt |
|---|---|
| Mistral | 40 / 42 |
| OpenRouter (free) | 12 / 14 |
| Kilo (free) | 11 / 13 |
| Gemini | 11 / 41 |
| Cerebras | 0 / 3 — account unfunded, every call 402 |
| Total | 74 / 113 |
Three findings that drive the whole design
1. Gemini's free tier is 20 requests per DAY, per model.
The 429 body names it: limit: 20, metric: generate_content_free_tier_requests. Verified
per-model, not per-key: gemini-3.6-flash was exhausted while gemini-3.5-flash still returned
200 on the same key. → Gemini is treated as scarce, tried last, and budgeted per model.
2. Mistral publishes exact limits in headers, and they vary 187×.
Read from x-ratelimit-limit-req-minute:
| Model | req/min | Model | req/min | |
|---|---|---|---|---|
ministral-3b-latest |
750 | mistral-large-latest |
4 | |
ministral-8b-latest |
188 | magistral-medium-latest |
5 | |
codestral-latest |
125 | mistral-small/medium |
50 |
→ Routine work goes to 750/min models. The 4/min flagship is reserved, not squandered.
3. A 429 is not a failure — it is a fact worth remembering, and its scope differs.
Re-probing 21 Gemini 429s after a 45s cooldown recovered 0 — hard daily caps. Mistral 429s
clear in seconds. And OpenRouter meters account-wide: hitting
Rate limit exceeded: free-models-per-day kills every model on that key at once, so one 429
must sideline the whole provider rather than being retried model by model (observed live after
this skill's own probing exhausted the daily allowance). → Backoff is provider-specific, scoped
correctly (per-model vs per-account), and persisted so the next process inherits the knowledge.
Measured quality (5 objective questions: 91-prime, bat-and-ball, strawberry r's, 9.11 vs 9.9, Canberra)
10 models scored a perfect 5/5. Fastest first:
| Model | Score | Latency |
|---|---|---|
mistral/mistral-medium-latest |
5/5 | 0.43s |
gemini/gemini-3.1-flash-lite |
5/5 | 0.63s |
gemini/gemini-3.5-flash-lite |
5/5 | 0.66s |
openrouter/inclusionai/ling-3.0-flash:free |
5/5 | 0.93s |
kilo/kilo-auto/free |
5/5 | 1.46s |
kilo/nvidia/nemotron-3-ultra-550b-a55b:free |
5/5 | 2.30s |
⚠️ Two Gemini models scored 0/5 — that was quota exhaustion mid-test, not low quality. Verified separately. A benchmark that can't tell "wrong" from "rate-limited" produces a poisoned ranking; this one re-checks every zero.
Usage
R=~/skill_inventions/free-tier-ai-router/router.py
python3 $R "explain X in one line" # general: cheapest abundant model
python3 $R -t code "write a python retry decorator"
python3 $R -t fast "yes or no: is 17 prime"
python3 $R -t best -q 5 "audit this argument for logical errors"
python3 $R --status # live budget per route
python3 $R --plan -t code # show routing order, make no calls
python3 $R --reset # clear cooldowns
-q N— only use models that measured ≥N/5.--no-cache— skip the SHA-256 response cache.- Identical prompts return from cache in ~46 ms with zero API calls.
How it decides
task=general → cheap tier, highest req/min first (spend abundance)
task=fast → cheap tier, lowest measured latency
task=code → code-tagged models first
task=best → highest measured quality first
↓
skip anything in cooldown or over daily budget
↓
call → on success bank it; on 429 set provider-appropriate cooldown
(gemini 1h + park for the day · others 60-300s · 402/404 24h)
↓
persist state atomically → next process inherits the knowledge
Verified behaviour
| Test | Result |
|---|---|
| 25 distinct prompts back-to-back | 25/25 in 10s, zero 429s |
| Gemini daily budget after that burst | 0/20 used on all 4 models — scarce capacity untouched |
| Top 3 routes forced into cooldown | transparently fell through to route 4 |
| Repeat prompt | cache hit, 46 ms, no API call |
-q 5 gate |
only 5/5-measured models offered |
| OpenRouter daily quota genuinely exhausted | whole provider parked in one step; 12/12 prompts still served from remaining providers |
v1.2.0 — bugs found by audit and fixed
Audited by feeding the entire source to a 1M-context model, then independently verifying every reported bug before fixing it (one reported issue was a false positive; one real bug the review missed was caught by a crash during concurrency testing).
| # | Bug | Concrete failure | Status |
|---|---|---|---|
| 1 | Comment promised a 20% daily reserve; code allowed 100% | scarce Gemini quota could be fully drained by routine traffic | fixed — real 80% guard, only -q5/-t best may spend the tail |
| 2 | Cache key omitted max_tokens and quality |
a 50-token and a 4000-token request collided and served each other's answers | fixed — key is v2|task|quality|max_tokens|system|prompt |
| 3 | load_state/save_state were a read-modify-write race |
measured: 10 concurrent increments landed as 1 — daily caps silently exceeded | fixed — fcntl.flock around the whole cycle |
| 4 | Account-wide 429 parked for only 1h | router rediscovered the same daily wall every hour until midnight | fixed — parks until local midnight |
| 5 | Gemini cooldown 1h but day_count=9999 |
--status reported "✅ ready" while the model was actually blocked all day |
fixed — cooldown and budget now agree |
| 6 | Check-and-spend not atomic | two processes could both see 19/20 and both spend the last request | fixed — claim happens inside the lock |
| 7 | Missed by the AI review: shared state.json.tmp |
concurrent save_state crashed with FileNotFoundError; first writer won, rest died |
fixed — per-PID temp file |
| 8 | Missing credentials treated as per-model failure | one dead provider burned all 6 retries before healthy providers were reached | fixed — provider-wide park, doesn't consume the try budget |
Also fixed a double-count introduced by fix #6: _claim() and note_result() both incremented
day_count, so 20 calls recorded as 22. note_result no longer spends budget — the atomic
claim is the single source of truth.
Post-fix verification
| Test | Before | After |
|---|---|---|
| 12 concurrent processes | crashed, 1/12 recorded | 12/12, 0 crashes, 0 leaked counters |
| Accounting at N=5/12/20 | 20 calls → 22 recorded | exact at every N |
| Corrupt state file | silently wiped all state | recovers cleanly |
| Provider with no credentials | gave up entirely | falls through to a working provider |
| Cache hit | 46 ms | 40 ms |
v1.3.0 — workspace integration + zero wasted API calls
Install and wire up:
npx clawhub@latest install free-tier-ai-router
bash skills/free-tier-ai-router/integrate.sh # makes ZERO API calls
ai "your question"
integrate.sh creates the ~/ai entry point, validates credential files on disk (no
network), restores from cred_backup/ if a snapshot wipe removed them, and seeds the
cooldown state from shipped health.json. It is idempotent.
Eliminated API calls
| Waste source | Before | After |
|---|---|---|
Rediscovering permanently-dead routes on every fresh install / --reset |
1 wasted call per dead route (17 routes) | 0 — shipped in health.json, enforced before any network call |
| Cerebras (unfunded, always 402) | called, 402, cooldown, repeat next reset | never called |
| Repeat identical prompt | 1 call | 0 — cache checked first |
| One answer | 1 call | 1 call (no speculative parallel fan-out) |
Verified: ai --plan, ai --status and integrate.sh are entirely offline — they read
disk state only. Confirmed by asserting the success/failure counters stay at zero.
health.json records only structural failures (402/403/404/400, retired models, Labs-only
models). Transient 429s are deliberately excluded — those recover and must stay routable.
Limits and honesty
- Cerebras is in the table but unusable — the key authenticates (200 on
/v1/models) yet every inference returns 402. It is deliberately excluded from routing until funded. - Kilo paid models are excluded: balance is $0; only its 13 free models are routed.
- Quality scores come from 5 questions — enough to catch obvious reasoning failures, not a substitute for a full benchmark suite.
- Rate limits were read from live headers where published (Mistral) and from error text
elsewhere (Gemini). Providers change these; re-run
probe.pyto refresh. - The router does not stream. For interactive streaming, call a provider directly.
