The Do-Not-Call List for Free AI Tiers: A Request-Level Policy You Can Version A developer published a request-level policy pattern for governing when free AI tiers can be used, packaging it as a versioned JSON policy file plus a stdlib-only Python classifier that runs before each model call. The classifier returns ALLOW, SANDBOX, or DENY based on data class, sink, reversibility, human-in-the-loop status, and agent tools, and every decision record carries a SHA-256 hash of the exact policy version that produced it. The approach argues that gating at the pipeline level misses the single risky request inside an otherwise green pipeline. A weekly dependency bot opens a pull request against a release branch. A bot posts an AI-written summary of the diff into the PR thread. Nobody reviews that summary before merge. That is the whole incident, and it needed no attacker. This is a composite of ordinary failure modes, not a customer report. It happens because teams gate at the pipeline level. The risky call is usually a single request inside a green pipeline. This article is an anti-use field guide. It teaches where a free AI lane must not be the caller, and what to call instead. A paid tier and a free tier can run the same prompt. The difference is who owns the failure. Free access usually means fewer contractual guarantees, shared capacity, and terms you did not negotiate. So the question is never "is the model good enough". The question is "what is the blast radius when this specific call is wrong". Classify the request, then pick the lane. Ask these for every request, not every service. A request that is public, reversible, and tool-free is a fine candidate for a free lane. A request that is internal, irreversible, and shell-capable is not. Policies in prose drift. Policies in a file can be hashed, diffed, and pinned per release. Start with a small JSON policy. Keep it boring on purpose. { "version": 3, "hard deny": { "data class": "secret", "pii" , "sink": "signed artifact", "prod config", "money" }, "sandbox only": { "data class": "internal" , "agent tools": "shell", "network" }, "allow": { "task": "summarize", "rename suggestion", "changelog draft" } } Note the shape. Deny rules are absolute. Sandbox rules are advisory and require human review. The allow list is explicit, so unknown tasks fall to review instead of shipping. This runs before the model call. It is stdlib-only and short enough to read in a review. python classify.py — Python 3.11+, no third-party deps from future import annotations import hashlib import json import sys from dataclasses import dataclass from enum import Enum from pathlib import Path class Decision str, Enum : ALLOW = "ALLOW" SANDBOX = "SANDBOX" DENY = "DENY" @dataclass frozen=True class Request: task: str data class: str public | internal | pii | secret sink: str draft | comment | signed artifact | prod config | money reversible: bool human in loop: bool agent tools: tuple str, ... = def load policy path: Path - tuple dict, str : raw = path.read bytes return json.loads raw , hashlib.sha256 raw .hexdigest :12 def classify req: Request, policy: dict - tuple Decision, list str : reasons: list str = for name, blocked in policy "hard deny" .items : if getattr req, name in blocked: reasons.append f"hard deny:{name}={getattr req, name }" if reasons: return Decision.DENY, reasons for name, gated in policy "sandbox only" .items : value = getattr req, name hit = any v in value for v in gated if isinstance value, tuple else value in gated if hit: reasons.append f"sandbox only:{name}={value}" if req.task not in policy "allow" "task" : reasons.append f"task not allowlisted:{req.task}" if not req.reversible and not req.human in loop: reasons.append "irreversible without review" return Decision.SANDBOX if reasons else Decision.ALLOW , reasons or "clean" The policy hash is the important line. Every decision record carries the exact policy version that produced it. The log is JSONL. One line per request. It is the artifact you will actually use during an incident. python def emit req: Request, decision: Decision, reasons: list str , policy hash: str, out=sys.stdout - None: record = { "policy": policy hash, "decision": decision.value, "reasons": reasons, {k: list v if isinstance v, tuple else v for k, v in req. dict .items }, } out.write json.dumps record + "\n" Run it against a batch and read the distribution before you trust it. python classify.py policy.json < requests.jsonl decisions.jsonl jq -r 'select .decision = "ALLOW" | .reasons ' decisions.jsonl \ | sort | uniq -c | sort -rn | head That second command is the debugging workflow. It tells you which rule fires most. If task not allowlisted dominates, your policy is too narrow. If hard deny:sink=signed artifact fires often, you have a design problem upstream. A classifier without tests is a comment. Parametrize the matrix and run it in CI. python test classify.py import pytest from classify import Decision, Request, classify POLICY = { "version": 3, "hard deny": {"data class": "secret", "pii" , "sink": "signed artifact", "prod config", "money" }, "sandbox only": {"data class": "internal" , "agent tools": "shell", "network" }, "allow": {"task": "summarize", "rename suggestion", "changelog draft" }, } CASES = Request "summarize", "public", "draft", True, False , Decision.ALLOW , Request "summarize", "pii", "draft", True, True , Decision.DENY , Request "summarize", "public", "prod config", True, True , Decision.DENY , Request "summarize", "internal", "comment", True, True , Decision.SANDBOX , Request "deploy", "public", "draft", False, False, "shell", , Decision.SANDBOX , @pytest.mark.parametrize "req,expected", CASES def test matrix req, expected : assert classify req, POLICY 0 is expected The fifth case is the one that saves you. A capable agent on an unlisted task lands in review, not in production. Most free-lane failures have a cheaper replacement. Reach for that first. | Red flag | Why the free lane is wrong | Better alternative | |---|---|---| | Output signs or approves anything | No accountability trail on a shared tier | Deterministic signer, human approver | | Input carries PII or secrets | Terms and retention are not yours | Redaction step, then classify again | | Caller holds shell or network tools | Irreversible side effects, no rollback | Sandbox with no credentials | | Same prompt runs on every commit | Cost and drift scale silently | Cached fixture, golden-file test | | Output lands in a config or migration | One bad token is an outage | Schema validation, then review | | Task is formatting or renaming | Deterministic problem, probabilistic tool | Formatter, AST rewrite, linter | | Latency sits on a user request path | Shared capacity, unknown queueing | Precompute, or a committed tier | Print that table. Tape it to the review checklist. The first three rows are hard stops. Write these before adoption, not after the first incident. Trigger any one and move the workload. Criterion five is the quiet one. A sandbox nobody reads is a production lane with extra steps. MonkeyCode ships as an open-source project with a free model allowance and a free server option. Those are operator-stated availability claims. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Treat both as shared capacity, not as entitlements. Quotas, limits, and terms change, so read the project's current documentation instead of trusting any blog post, including this one. In this policy, that lane maps to task in allow , data class == public , sink == draft , and agent tools == . Summarize a public doc. Draft a changelog entry a human will edit. Suggest a test name. Keep the classifier in front of it, and the free server stays a staging surface rather than a signing surface. If you want to test the workflow, start with the summarizer task. Leave the deploy bot out of it. This classifier is a policy check, not an enforcement boundary. A caller can pass wrong labels. Pair it with egress rules, scoped credentials, and separate identities per lane. Skip this approach in three cases: The pattern is small on purpose. Copy the two files, replace the allow list with your real tasks, and run the test matrix once. If more than half the cases land in DENY , your pipeline shape is the problem, and no tier will fix it.