Most free model failures are not model failures. They are routing failures: the request should never have left the repo.
Before you call any free endpoint, the first job is not to test the server. It is to filter the requests.
The problem
A free endpoint creates a temptation. You have a stuck task, so you send it to the model. If the response is bad, you spend time reviewing it. If the task should have stayed local, the model was never the bottleneck. The review is.
A small router fixes that. It forces each task into one of three lanes:
- LOCAL: deterministic, trivial, or missing a clear acceptance check.
- MODEL: low-stakes, reversible, and easy to verify.
- HUMAN: security, secrets, auth, migrations, deploy path.
The routing policy
| Route | Condition | Example |
|---|---|---|
| LOCAL | input is very short, no acceptance check, or local logic already works | trim a string, parse a semver, rename a local variable |
| MODEL | reversible, low stakes, output can be checked in under a minute | summarize a traceback, draft a changelog entry, suggest a regex from examples, rewrite an error message |
| HUMAN | auth, secrets, data migration, deploy path, or irreversible action | login handler, IAM policy, database schema, rate limiting |
The rule is boring. That is the point.
The artifact
This is a standard-library-only router. Add your own secret markers before using it.
from dataclasses import dataclass
from enum import Enum
class Route(Enum):
LOCAL = 'local'
MODEL = 'model'
HUMAN = 'human'
@dataclass
class Task:
text: str
touches_auth: bool = False
has_secrets: bool = False
reversible: bool = True
has_acceptance_check: bool = False
SECRET_MARKERS = ('api_key', 'api key', 'password', 'token', 'secret', 'private key', 'bearer')
def looks_sensitive(text: str) -> bool:
lowered = text.lower()
return any(marker in lowered for marker in SECRET_MARKERS)
def route(task: Task) -> Route:
if task.has_secrets or task.touches_auth or looks_sensitive(task.text):
return Route.HUMAN
if not task.reversible:
return Route.HUMAN
if len(task.text.strip()) < 20:
return Route.LOCAL
if not task.has_acceptance_check:
return Route.LOCAL
return Route.MODEL
A quick sanity check keeps the router honest:
assert route(Task('summarize this traceback for a bug report', has_acceptance_check=True)) == Route.MODEL
assert route(Task('short', has_acceptance_check=True)) == Route.LOCAL
assert route(Task('rotate the production api key', has_acceptance_check=True)) == Route.HUMAN
If those asserts pass, the router is ready to sit in front of a model call.
What to send where
Take five real tasks from the last week and sort them:
- Writing a release note? MODEL.
- Refactoring the auth middleware? HUMAN.
- Extracting a version number? LOCAL.
- Formatting a stacktrace for a bug report? MODEL, after redacting hostnames and tokens.
- Adding a database migration? HUMAN.
If a task is embarrassing to get wrong and easy to review, MODEL is fine. If it is impossible to review quickly, keep it out of MODEL.
Where MonkeyCode fits
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The operator-supplied premise is that MonkeyCode has free model access and a free server option. For this workflow, that is just another Route.MODEL destination. The router does not care which provider is behind the call; it only limits what leaves your machine.
You can keep the call behind a small function:
def call_model(text: str) -> str:
# Replace with the provider SDK or an HTTP POST.
# Keep timeout, retry, and size limits here.
raise NotImplementedError
The important part is that the router runs before this function, not after.
Limitations
- The secret markers are a rough filter, not a security boundary. Encoded or indirect secrets can still slip through.
- A 20-character minimum is arbitrary; tune it to your tasks.
- An acceptance check can be wrong. A task with an easy check may still produce subtly bad output.
- Routing says nothing about model quality. It only decides what is worth sending.
Who should skip this
- Teams handling regulated data or production secrets.
- Work where every output must be reproducible from source alone.
- Teams that already have a prompt-access policy or review workflow.
Closing
Start with five tasks from last week. Sort them into LOCAL, MODEL, and HUMAN before writing any endpoint code. If fewer than half land in MODEL, the router is doing its job.
Top comments (0)