DEV Community

Dakota Huang
Dakota Huang

Posted on

Split Low-Stakes Prompts From Code Changes Before You Call a Free Model

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)