Most retries against a free model quota are wasted work. Your agent hits a wall, retries, and burns the same dead budget three more times.
You have probably seen that loop in a log. It looks like activity. It is not progress.
Here is a catalog of three retry anti-patterns. Each entry has symptoms, a root cause, and a replacement pattern you can ship today.
The shift that broke old retry code
AI coding agents are everywhere this month. Free model tiers are part of why.
But a free tier fails differently from a paid API. A paid API fails when it is down. A free tier fails when it is used up.
One state is transient. The other is terminal until a window resets. Your HTTP client cannot tell them apart.
So what should the retry loop actually inspect?
Anti-Pattern 1: Treating every 429 as a slow-down
Symptoms
- The same step retries three to five times with zero change.
- Every log line shows an identical
429and an identical body. - The run dies with
max retries exceededseveral minutes later.
Root cause
The policy was copied from a rate-limited paid API. There, 429 means "slow down". On a free tier, 429 often means "budget exhausted". Retrying a terminal state only delays the failure.
Replacement pattern
Classify the failure before you sleep and retry.
- Read
Retry-Afterwhen the server sends it, and honour it. - Scan the body for quota keywords. Halt on those.
- Cap transient retries at three attempts per step.
- Add jitter so parallel workers do not thunder.
# Sketch. Not run against any specific provider endpoint.
RETRYABLE = {429, 500, 502, 503, 504}
TERMINAL = {400, 401, 403, 404, 422}
QUOTA_HINTS = ("quota", "exhausted", "credit", "limit reached")
def classify(status: int, body: str) -> str:
lowered = body.lower()
if status in TERMINAL:
return "fail_fast"
if status == 429 and any(h in lowered for h in QUOTA_HINTS):
return "halt_and_report" # no retry, no sleep
if status == 429:
return "backoff" # respect Retry-After
if status in RETRYABLE:
return "backoff"
return "halt_and_report"
The important line is halt_and_report. A terminal state deserves a human decision, not a loop.
Anti-Pattern 2: Counting tokens after the run
Symptoms
- The agent finishes a long plan, then discovers it ran out mid-way.
- Nobody can say how much of the budget the run consumed.
- The failure report arrives after all the useful work is gone.
Root cause
Cost tracking is bolted on as post-hoc reporting. That is accounting, not control. By the time the number appears, the decision window has closed.
Replacement pattern
Keep a ledger that runs ahead of the model.
- Estimate the cost of the next step before you send it.
- Hold a reserve for the fix pass. Twenty percent is a reasonable start.
- Stop at the ceiling and write a checkpoint.
- Never let the provider be the first thing to tell you the budget is gone.
from dataclasses import dataclass
@dataclass
class Ledger:
budget: int # operator-stated figure; verify your own terms
spent: int = 0
reserve: float = 0.20 # room for the repair pass
def ceiling(self) -> float:
return self.budget * (1 - self.reserve)
def can_afford(self, estimate: int) -> bool:
return self.spent + estimate <= self.ceiling()
Call can_afford before each step. When it returns False, stop cleanly and persist state.
Anti-Pattern 3: Re-running the whole plan after one bad step
Symptoms
- Step 7 fails, and the run restarts from step 1.
- Already-verified steps get regenerated with different output.
- Costs double while confidence drops.
Root cause
The plan is treated as one atomic request. It is not. A plan is a sequence of separately verifiable steps, and only one of them failed.
Replacement pattern
Resume from a fingerprint, not from the top.
- Hash each step's inputs into a short fingerprint.
- Persist
fingerprint -> okwhen a step verifies. - Re-emit only steps without a verified fingerprint.
- Store partial output hashes so truncation is detectable.
import hashlib, json
def step_fingerprint(step: dict) -> str:
payload = json.dumps(step, sort_keys=True).encode()
return hashlib.sha256(payload).hexdigest()[:12]
def pending(plan, checkpoints):
for step in plan:
fp = step_fingerprint(step)
if checkpoints.get(fp) == "ok":
continue # verified work is skipped, not repeated
yield step, fp
Skip verified work. Recompute the failure only.
The decision table I keep next to the loop
| Failure | Retry? | Wait | Persist first |
|---|---|---|---|
429 plus quota keyword |
No | — | checkpoint + ledger |
429, no quota keyword |
Yes, max 3 |
Retry-After, else exp + jitter |
attempt count |
500 / 502 / 503 / 504
|
Yes, max 5 | exp backoff + jitter | attempt count |
400 / 422 schema error |
No | — | payload + prompt hash |
| Truncated stream | Yes | immediate | partial output hash |
Print this table in the run header. It is cheap, and it ends arguments later.
Where a free tier actually belongs
Free model access fits throwaway and exploratory work best. Prototypes, harness tests, and one-off refactors are good candidates.
The MonkeyCode project offers free model access and a free server option. The team states the free allowance is 10 million tokens. Check the current terms yourself before you build anything on a number.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Used that way, the tradeoff is honest. A free server is fine for a harness that runs for minutes. It is not capacity planning, and it is not a substitute for paid guarantees.
Run the ledger before the model, not after. That one habit survives every tier change.
Limitations and who should skip this
- Teams with hard SLAs should budget for a paid tier. Free quotas reset on someone else's schedule.
- Long-running batch jobs should not depend on a scratch server.
- Anyone who cannot verify quota terms should not build billing logic on them.
- The classifier above is a sketch. Test it against your provider's real error bodies.
- Quota keywords differ per provider. Copy the hints from actual responses, not from a blog post.
What to do next
Pick one anti-pattern and delete it this week. Start with the classifier; it is roughly ten lines.
Then add the ledger. Then add fingerprints.
If you want a free tier and a free server to run the harness on, the MonkeyCode project page is the entry point. Bring the ledger with you.
Top comments (0)