A branch named agent/fix-timeout showed up on Monday with a green check. The tests in the prompt had passed. Staging then died on a migration the model never saw. Generation cost was close to zero. The incident was not.
That sequence is now ordinary on teams that adopted coding agents. Free model access removes the budget alarm that used to stop a runaway session. A free server removes the “this is just my laptop” boundary. Neither fact answers the merge question. Is the output a sketch, or a candidate patch?
This article treats that question as a gate. It is a glossary, a four-branch tree, and a worked example at every leaf. The artifact is a promotion record plus a script you can run before git push. No vendor bake-off. No latency table. The method still works if you delete every product name in it.
Six terms that should not collapse into one
Mixing these is how a weekend experiment becomes a Monday revert. Keep them as separate objects.
- Sketch output. A diff or file you will not merge as-is. Useful. Not a pull request.
- Candidate patch. A diff you will review as if a junior teammate wrote it. It still needs holdout evidence.
-
Backend class. A coarse provenance label:
free_model,free_server, orlocal. It is not a quality score. - Visible suite. Tests, logs, or fixtures that were in the prompt, in the repo context the agent could read, or both.
- Holdout check. A test, contract, or probe the agent did not see. If it lives in a file the agent was told to edit, it is not a holdout.
- Promotion record. A short JSON file that states backend class, intent, holdout path, and the git tree you actually ran. No record, no promote.
A green visible suite is a property of the session. A promotion record is a property of the change. Those are different objects. Treat them that way.
The four-branch gate
Walk the questions in order. Do not jump to the leaf that matches the tool you already opened.
Step 1. Does the working set contain secrets, credentials, customer data, or anything policy forbids sending off-machine?
Step 2. If no, is the intended outcome a merged change, or only a sketch?
Step 3. If merge, do you have at least one holdout check the agent never saw?
Step 4. If yes, can you apply the diff and run that holdout on a machine you control, without trusting the session’s “done” line?
secrets in the working set?
├─ yes → Leaf A redact or refuse any remote backend
└─ no → merge intended?
├─ no → Leaf B sketch; free backend is fine; no PR
└─ yes → holdout unseen by the agent?
├─ no → Leaf C write the holdout; do not promote
└─ yes → Leaf D local apply + holdout + record + review
| Leaf | Path | Allowed action |
|---|---|---|
| A | Secrets present | Redact or refuse remote backends |
| B | No secrets, sketch only | Free model or free server; never open a PR |
| C | Merge intent, no holdout | Write the holdout first |
| D | Merge intent, holdout exists | Local apply, holdout, record, then review |
Leaf A — secrets in the working set
Worked example. You are debugging a payment worker. Tests load .env. The failing trace includes a sandbox API key.
Do not paste the trace into a remote agent. Do not point a free server at that tree until the secret is gone from the context. Cheap tokens do not change the data-handling rule.
The helper below is a proposed local check, not a security product. It fails closed on obvious patterns and will miss custom formats.
# secret_scan.py — proposed helper, not a complete detector
import re, sys
from pathlib import Path
PATTERNS = [
re.compile(r"api[_-]?key\s*=\s*['\"][^'\"]+", re.I),
re.compile(r"BEGIN (RSA |OPENSSH )?PRIVATE KEY"),
re.compile(r"eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\."),
]
SKIP_DIRS = {".git", "node_modules", ".venv", "__pycache__"}
SCAN_SUFFIX = {".py", ".env", ".yml", ".yaml", ".json", ".md", ".txt", ".toml"}
def scan(root: str) -> list[str]:
hits = []
for p in Path(root).rglob("*"):
if not p.is_file() or p.suffix not in SCAN_SUFFIX:
continue
if any(part in SKIP_DIRS for part in p.parts):
continue
text = p.read_text(errors="ignore")
for pat in PATTERNS:
if pat.search(text):
hits.append(f"{p}: {pat.pattern[:40]}")
return hits
if __name__ == "__main__":
hits = scan(sys.argv[1] if len(sys.argv) > 1 else ".")
for h in hits:
print(h)
raise SystemExit(1 if hits else 0)
python secret_scan.py .
# exit 1 → do not start a remote session on this tree
If you still want a sketch, build a redacted fixture. Replace the key with TEST_KEY_REDACTED. You have then left Leaf A. You are on Leaf B or C with a synthetic tree.
Leaf B — sketch only, no secrets
Worked example. You want three shapes for a retry wrapper. You will pick one by reading, not by merging.
A free model or a free server is a reasonable place to generate those shapes. Label the output as sketch. Put it in /tmp or on a branch you will delete. Do not open a PR “so we don’t lose the work.” That sentence is how sketches become unreviewed code.
Proposed header for the sketch file:
# SKETCH — not for merge
# backend_class: free_model
# intent: compare retry shapes
# holdout: none
If a teammate later wants to merge one shape, the work leaves Leaf B. It re-enters the tree at Step 3. Regeneration is cheap. Unlabeled history is not.
Leaf C — merge intent, no holdout
Worked example. The agent edited retry.py and reported that test_retry.py passed. You read test_retry.py. Every assertion matches the happy path described in the prompt.
You do not have a holdout. Promotion is unavailable. The next action is to write one check the agent did not see. Cheap generation does not replace that design step.
Proposed holdout. Keep it out of the next session’s context, or add it only after the session ends.
# tests/holdout_retry_budget.py
"""Holdout: do not include this file in agent context."""
from retry import with_retry
def test_does_not_retry_on_4xx():
calls = {"n": 0}
def boom():
calls["n"] += 1
raise ValueError("400 bad request")
try:
with_retry(boom, retries=3, retry_on=TimeoutError)
except ValueError:
pass
assert calls["n"] == 1
python -m pytest tests/holdout_retry_budget.py -q
If you cannot name, in one sentence, a behavior the visible suite missed, you are not ready to merge. Write the sentence first. The agent can wait.
Leaf D — merge intent, holdout in hand
Worked example. Same retry.py diff. The holdout file exists and was not in the session context. Backend class was a free server. You still do not merge from the server’s “done” state.
Promotion means: apply the diff on a machine you control, run the holdout, write the record, then review the diff as human-authored code.
git checkout -b promote/retry-budget
git apply /tmp/agent.retry.diff
python -m pytest tests/holdout_retry_budget.py tests/test_retry.py -q
git rev-parse HEAD
Promotion record, proposed schema:
{
"backend_class": "free_server",
"intent": "candidate_patch",
"visible_suite": ["tests/test_retry.py"],
"holdout": ["tests/holdout_retry_budget.py"],
"holdout_in_session_context": false,
"local_command": "python -m pytest tests/holdout_retry_budget.py tests/test_retry.py -q",
"local_result": "pass",
"tree": "replace-with-git-rev-parse-HEAD"
}
Fill tree from git rev-parse HEAD after the apply. Store the JSON next to the PR description. Reviewers should re-run the holdout without the original session.
A gate script you can run locally
The script below is a proposed control, not a hosted service. It refuses promotion when the record is incomplete, when intent is still sketch, or when the recorded tree does not match HEAD.
# promote_gate.py — proposed workflow helper
import json, subprocess, sys
from pathlib import Path
REQUIRED = {
"backend_class",
"intent",
"holdout",
"holdout_in_session_context",
"local_result",
"tree",
}
def load(path: str) -> dict:
data = json.loads(Path(path).read_text())
missing = REQUIRED - set(data)
if missing:
raise SystemExit(f"missing keys: {sorted(missing)}")
return data
def main(record_path: str) -> None:
rec = load(record_path)
if rec["intent"] != "candidate_patch":
raise SystemExit("intent is not candidate_patch; stay on sketch")
if rec["holdout_in_session_context"] is True:
raise SystemExit("holdout was visible to the agent; refuse promotion")
if not rec["holdout"]:
raise SystemExit("no holdout listed")
if rec["local_result"] != "pass":
raise SystemExit("local_result is not pass")
head = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip()
if rec["tree"] != head:
raise SystemExit(f"tree mismatch: record={rec['tree']} HEAD={head}")
print("promotion record accepted; human review still required")
if __name__ == "__main__":
main(sys.argv[1])
python promote_gate.py .promotion.json
The last printed line is load-bearing. The script does not merge. It only blocks a class of self-deception that appears when generation is free.
Where free model access and a free server actually help
A coding assistant is useful on Leaves B and D, and sometimes as a redacted sketch on the way out of Leaf A. It is not a substitute for the missing holdout on Leaf C.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode is an open-source coding assistant with operator-supplied free model access and a free server option. Those two properties change the economics of Leaf B: you can generate alternative sketches without a budget alarm. They do not change Leaf A policy. They do not turn a visible suite into a holdout. If you use that backend class, write free_model or free_server into the promotion record. Then run promote_gate.py on your machine.
Remove the product name and the gate is unchanged. That is the point.
Limitations
This gate does not measure model quality. It does not claim that free backends are slower, faster, weaker, or stronger than paid ones. No token quotas, hardware lists, uptime promises, or benchmark numbers appear here, because those figures go stale and were not verified for this article.
The secret scan is a coarse regex. It will miss custom token formats. It will false-positive on documentation. Treat it as a seatbelt, not an audit.
Holdouts can be gamed if you later paste them into a new session. The rule is operational: the file must be absent from that session’s context, not merely absent from an old chat transcript.
The script trusts local_result as you typed it. If you lie to the JSON, the gate will believe you. That is a process failure, not a parser failure.
Who should not use this approach
Do not use a remote free model or free server if the working tree is in-scope for a policy that forbids outbound source. Leaf A is a stop, not a speed bump.
Do not use this gate as a reason to skip human review on Leaf D. A passing holdout is necessary. It is not sufficient for security-sensitive or public API changes.
Do not keep sketch branches as long-lived feature branches. If the sketch is older than the holdout design, start over. Cheap regeneration is the point of a free backend. Stale sketches are drift.
If the team cannot name a holdout behavior in one sentence, stop. Write the sentence. Then reopen the tool.
Classify the session. Refuse remote context that includes secrets. Keep sketches out of the PR queue. Promote only with a holdout you ran locally. Free generation makes that habit more important, not less. If you want to exercise Leaves B and D against a backend that offers free model access and a free server option, MonkeyCode is one place to run the same gate. The gate travels with you either way.
Top comments (0)