Free inference is a sandbox with a door, not a load-bearing wall. Agent loops that treat retries as cheap, context as private, and output as merge-ready should stay off free models and free servers until a stop condition is written down and enforced in code. That is the whole argument. The rest of this piece is a fail-closed preflight, plus the red flags that should trip it.
Cheap tokens change behavior the way unlimited coffee changes a night shift. People keep pouring. An agent that can call tools, rewrite its own prompt, and retry on timeout will treat a donated endpoint as an infinite hallway. The hallway is not infinite. It is also not a place that owes the caller isolation, pinning, or an audit trail.
MonkeyCode currently offers free model access and a free server option that some teams use as a scratch pad for those loops. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The name matters here only as a concrete sink where the stop list can be practiced. Remove the product and the same refusals still apply to any unpaid, shared, or unpinned inference path.
The failure mode is not “the model is dumb.” The failure mode is routing. A loop that assumes the remote side is a private colleague will leak prompts, burn a shared queue, and then launder a plausible answer into a pull request. Architecture articles this week keep celebrating agents that plan, tool-call, and self-correct. Those loops are exactly the ones that need a bouncer, because they generate traffic without generating judgment.
A useful analogy is a loading dock that accepts unlabeled crates. The dock is free. The crates are not. Once a crate goes on the truck, the warehouse has already lost the argument about contents. Free inference works the same way: the moment a prompt leaves the laptop, classification is no longer theoretical. The preflight below exists to keep the crate off the truck.
The original artifact is a small classifier, not a dashboard. It reads a job envelope, scores red flags, and exits non-zero before any HTTP client is allowed to wake up. Teams can drop it in front of a local runner, a CI sidecar, or a laptop alias. It does not call a model. That is the point.
# stop_preflight.py
# Fail closed. No network. No model names. No invented quotas.
from __future__ import annotations
import json
import re
import sys
from dataclasses import dataclass, field
from typing import Iterable
SECRETISH = re.compile(
r"(api[_-]?key|authorization:|bearer\s+[A-Za-z0-9._\-]{12,}|"
r"-----BEGIN (?:RSA )?PRIVATE KEY-----|"
r"aws_secret_access_key|x-api-key)",
re.I,
)
PIIISH = re.compile(
r"\b\d{3}-\d{2}-\d{4}\b|" # US SSN-shaped, as a teaching pattern only
r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b",
re.I,
)
@dataclass
class Job:
purpose: str
prompt: str
max_steps: int | None
data_class: str # public | internal | restricted
destination: str # scratch | review | merge | customer
needs_pin: bool
needs_slo_ms: int | None
multi_tenant: bool
human_facing: bool
audit_required: bool
@dataclass
class Verdict:
allow_free_inference: bool
reasons: list[str] = field(default_factory=list)
alternative: str = "local-script"
def reasons_for(job: Job) -> list[str]:
why: list[str] = []
blob = job.prompt or ""
if SECRETISH.search(blob):
why.append("secret-shaped material in prompt")
if PIIISH.search(blob) or job.data_class == "restricted":
why.append("restricted or PII-shaped payload")
if job.max_steps is None or job.max_steps > 8:
why.append("unbounded or oversized agent loop")
if job.destination in {"merge", "customer"}:
why.append("output is headed off the scratch pad")
if job.needs_pin:
why.append("job requires a pinned, reproducible model")
if job.needs_slo_ms is not None:
why.append("latency SLO does not belong on a free shared queue")
if job.multi_tenant:
why.append("multi-tenant data on donated infrastructure")
if job.human_facing:
why.append("customer-visible text is not a lab exercise")
if job.audit_required:
why.append("retention and audit logs are not implied by free access")
if job.purpose in {"oncall", "hotfix", "billing", "authz"}:
why.append("production-adjacent purpose")
return why
def judge(job: Job) -> Verdict:
why = reasons_for(job)
if not why:
return Verdict(True, ["scratch-only, bounded, non-sensitive"], "free-lab")
alt = "paid-pinned-endpoint" if job.needs_pin or job.destination == "merge" else "local-model-or-script"
if job.data_class == "restricted" or SECRETISH.search(job.prompt or ""):
alt = "do-not-leave-the-machine"
return Verdict(False, why, alt)
def load_job(path: str) -> Job:
raw = json.loads(open(path, encoding="utf-8").read())
return Job(**raw)
if __name__ == "__main__":
job = load_job(sys.argv[1])
v = judge(job)
print(json.dumps(v.__dict__, indent=2))
sys.exit(0 if v.allow_free_inference else 2)
The envelope is boring on purpose. Purpose, destination, data class, step budget, pin requirement, SLO, tenancy, human-facing, audit. If a field is missing, the job does not ship. Missing metadata is a red flag with better manners than a stack trace from production.
{
"purpose": "oncall",
"prompt": "Summarize this pager payload and draft a public status blurb.",
"max_steps": 24,
"data_class": "internal",
"destination": "customer",
"needs_pin": true,
"needs_slo_ms": 800,
"multi_tenant": false,
"human_facing": true,
"audit_required": true
}
Run it as a gate, not as a suggestion. A non-zero exit is the entire user interface.
python3 stop_preflight.py job.json
echo $?
# 2 means stay off the free path
Tests keep the classifier honest when someone later “just this once” widens a regex. The cases below are labeled as unexecuted examples of the intended contract. They are not measurements of any hosted model.
# test_stop_preflight.py
from stop_preflight import Job, judge
def test_scratch_note_may_use_lab():
job = Job(
purpose="notes",
prompt="Rewrite this public README paragraph for tone.",
max_steps=3,
data_class="public",
destination="scratch",
needs_pin=False,
needs_slo_ms=None,
multi_tenant=False,
human_facing=False,
audit_required=False,
)
v = judge(job)
assert v.allow_free_inference is True
def test_oncall_customer_copy_is_refused():
job = Job(
purpose="oncall",
prompt="Draft a status page sentence from these internal logs.",
max_steps=24,
data_class="internal",
destination="customer",
needs_pin=True,
needs_slo_ms=800,
multi_tenant=False,
human_facing=True,
audit_required=True,
)
v = judge(job)
assert v.allow_free_inference is False
assert v.alternative != "free-lab"
assert any("customer-visible" in r or "SLO" in r or "pinned" in r for r in v.reasons)
A wrapper around the actual client should be equally rude. The function does not exist to retry smarter. It exists to refuse.
# labeled proposal: do not paste secrets; this never opens a socket
import subprocess
import json
from pathlib import Path
def maybe_call_free_endpoint(job_path: Path) -> None:
proc = subprocess.run(
["python3", "stop_preflight.py", str(job_path)],
capture_output=True,
text=True,
)
verdict = json.loads(proc.stdout or "{}")
if proc.returncode != 0:
raise RuntimeError(
"free inference refused: "
+ "; ".join(verdict.get("reasons", ["unspecified"]))
+ " | try: "
+ verdict.get("alternative", "local-script")
)
raise RuntimeError("lab path not wired in this snippet; refusal path is the feature")
Red flags earn their keep when they are boring. Secrets and restricted data never leave the machine, even if the prompt “only needs a rewrite.” Unbounded max_steps is how a confused agent becomes a distributed fan. Merge-bound or customer-bound destinations are not lab work, no matter how short the prompt looks. A job that needs a pinned model cannot be honest on an endpoint that does not promise pinning. An SLO is a queueing claim; a free shared server does not owe anyone a queueing claim. Multi-tenant payloads turn the sandbox into a hallway with other people’s crates. Human-facing copy and audit-required work need retention, not generosity.
Better alternatives follow the same classification, not a brand preference. Restricted material stays in a local script or an air-gapped small model. Merge-path text goes to a paid, pinned endpoint with logs the team actually owns, or it does not go to a model at all. Deterministic transforms beat agents when the input is structured. If the only available capacity is unpaid, the correct move is to queue the job until a non-lab slot exists. Waiting is an architecture. It is also cheaper than an incident write-up.
Exit criteria should be mechanical, because mood is a terrible SRE. Leave the free path when any preflight reason fires. Leave when retries would be the next “feature.” Leave when the output acquired a URL, a ticket, or a reviewer. Leave when nobody in the room can name the data class out loud. Leave when the loop started rewriting its own stop conditions. That last one is not cute. It is how an agent spends a night explaining why it is still running.
Who should not use free inference is a longer list than the marketing page will ever print. On-call automation should not. Billing, authz, and anything that mints credentials should not. Teams under retention rules, contractual audit, or regulated data handling should not. Multi-tenant SaaS backends should not park customer traces on donated hardware. Beginners following an agent tutorial should not paste production .env files “just to see.” Editors drafting public incident copy should not. The lab is for public, bounded, throwaway work with a human still holding the delete key.
Limitations of this stop list are real. Regexes are cowards; they miss well-formed secrets and they false-positive on documentation. The classifier cannot see tool output that is fetched after the first hop, so a “safe” first prompt can still become a leak on step four unless the runner re-checks every tool result. The envelope can be forged by a person who wants the lab more than they want the policy. None of this measures model quality, uptime, token ceilings, or hardware. Those numbers are omitted because they were not verified here. A preflight that claims to know a quota is already lying.
The current wave of agent write-ups makes this discipline feel old-fashioned. That is useful. Agents that assume the world will fill in the blanks are the same agents that will assume a free server is a private one. A stop condition is how a team keeps the assumption from becoming a routing table.
A reader who wants a harmless drill can classify ten jobs from their own backlog against stop_preflight.py and count how many would have been refused. The refusals are the lesson. If a scratch pad such as MonkeyCode’s free model access and free server option is in reach, it still belongs on the far side of that gate, never in front of it.
Top comments (0)