Did that free host prove your patch tonight? Or did it only print a cheerful done? I keep hearing the same five claims from otherwise careful people.
I am not against cheap agent loops. I am against unpaid theater that still ships. A free model can still fail a test. A free server can still hold a convenient lie.
Why I wrote this FAQ
Do you promote diffs because the run cost nothing? That habit hides the only question that matters. Did the tree, the test, and the channel stay still?
This FAQ busts five claims I still catch myself repeating. Each myth has a claim, a check, and a better picture. Steal the receipt script further down. Then argue with the fields, not with vibes.
I sometimes park a bounded drill on MonkeyCode. It offers free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach. You can skip the product if you want. Do not skip the receipt file.
Myth 1: A free model cannot catch a real bug
Claim: "Free models are toys. Wait for the paid channel."
Which job are you hiring that model for? Catching a red test is not owning architecture. Those jobs pay different salaries in your head. They should pay different salaries inside the loop.
A constrained sensor can still yell. It can spot a broken import in one file. It can spot a missing assertion in one test. It cannot bless a public API shape.
Corrected model: treat the free model as a sensor. Never treat it as the author of record.
Try this bound tonight.
# One command. One exit code. No extra story.
python -m pytest tests/test_receipt.py -q
echo "exit=$?"
If the sensor cannot run that command, stop. Do not prompt it to think harder. Shrink the task until the command exists. Or change the channel on purpose. Then write the channel name into a file.
Myth 2: The free server is just another laptop
Claim: "SSH in, hack a while, copy files back."
Is your laptop reclaimed without a ticket? Mine is not, and that difference matters. A free server is a loaned blast chamber. Loaners get reused, then they vanish. Loaners are not origin.
Corrected model: clone, run, export, then burn. Do not build a life on that disk.
set -euo pipefail
WORKDIR=$(mktemp -d /tmp/agent-run.XXXX)
git clone --depth 1 "$REPO_URL" "$WORKDIR"
cd "$WORKDIR"
git checkout -B agent-try origin/main
echo "cwd=$WORKDIR"
Who owns that directory after the process dies? You own the patch you copied off-host. You do not own the host.
Myth 3: Unpaid runs do not need a receipt
Claim: "Nobody billed us, so why log it?"
Is money really the scarce thing here? Attention is scarce, and repro is scarcer. You will not remember that tree next Tuesday.
A transcript is not a receipt. A green emoji in chat is not a receipt. A receipt is a file you can diff later.
Corrected model: every loop writes the same shape. Paid or free does not change that shape.
Here is an example harness. Treat it as example code, not a vendor SDK.
#!/usr/bin/env python3
"""Write RECEIPT.json from a git checkout. Example only."""
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
from pathlib import Path
def sh(args: list[str]) -> tuple[int, str]:
proc = subprocess.run(args, text=True, capture_output=True)
out = (proc.stdout or "") + (proc.stderr or "")
return proc.returncode, out.strip()
def main() -> int:
started = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
code, head = sh(["git", "rev-parse", "HEAD"])
if code != 0:
print("not a git checkout", file=sys.stderr)
return 2
_, status = sh(["git", "status", "--porcelain"])
test_cmd = os.environ.get("TEST_CMD", "python -m pytest -q")
test_exit, test_out = sh(test_cmd.split())
receipt = {
"started_utc": started,
"ended_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"git_head": head,
"git_status": status.splitlines(),
"channel": os.environ.get("AGENT_CHANNEL", "unspecified"),
"host_kind": os.environ.get("HOST_KIND", "unspecified"),
"test_cmd": test_cmd,
"test_exit": test_exit,
"test_tail": test_out[-2000:],
"step_budget": int(os.environ.get("STEP_BUDGET", "8")),
}
path = Path(os.environ.get("RECEIPT_PATH", "RECEIPT.json"))
path.write_text(json.dumps(receipt, indent=2) + "\n")
print(f"wrote {path} test_exit={test_exit}")
return 0 if test_exit == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())
Pin the channel in the environment. Do not hide it inside a prompt.
export AGENT_CHANNEL=free-model
export HOST_KIND=free-server
export STEP_BUDGET=8
export TEST_CMD="python -m pytest -q"
python run_receipt.py
No receipt, no promotion. That is the whole rule I trust.
Myth 4: Mixing channels mid-loop is still one experiment
Claim: "I started on free. I finished on paid."
Same run of what, exactly? You changed the instrument. You changed tool-following. You maybe changed the host too. That is two trials glued together with hope.
Corrected model: one receipt, one channel, one host kind. Switch anything, and you start a new file. Keep both files. Compare patches. Do not compare feelings.
jq -r '.channel, .host_kind, .git_head, .test_exit' RECEIPT.json
If those four lines do not print, you recorded nothing. I still swap channels after a timeout. That swap is a new trial. Name it like one.
Myth 5: Files on the free host mean the work is durable
Claim: "The directory is still there. Relax."
Still there against which clock? Free hosts are not backups. They are not origin. They are not your laptop disk.
Corrected model: durability lives in git plus the receipt. Everything else is smoke you happened to like.
Promotion checklist I actually run:
- Save
git diffor a patch off the host. - Copy
RECEIPT.jsonoff the host. - Replay
TEST_CMDon my laptop. - Confirm
channelstill matches the story. - Let the host vanish after step four.
git diff origin/main > /tmp/agent.patch
# copy RECEIPT.json off the host by your usual path
git apply --check /tmp/agent.patch
TEST_CMD="python -m pytest -q" python run_receipt.py
If local test_exit disagrees, the free host proved a story. It did not prove the patch.
Decision table
Read this before you spend a loop.
| Situation | Use free model + free server? | Better move |
|---|---|---|
| Prove the harness writes receipts | Yes | Keep the budget tiny |
| Catch an obvious failing test | Yes, as a sensor | Pin TEST_CMD
|
| Design a public API | No | Human first |
| Touch secrets or prod creds | No | Locked-down local checkout |
| Merge onto main | No | Local replay plus review |
| Compare two prompts | Yes, if channel stays fixed | One receipt per prompt |
Read the No rows twice. Free is not a moral failing. It is a scope box.
A labeled test plan
This is a plan, not a benchmark. I am not attaching fake timings. Time your own machines.
- Commit a deliberate failing test on a branch.
- Run the receipt script with
HOST_KIND=local. - Confirm
test_exitis not zero. - Repeat on the free server with
HOST_KIND=free-server. - Copy both receipts and diff them.
- Fix the test and repeat until both exits match.
What must match? test_cmd, test_exit, and the patch itself.
What may differ? Timestamps, host_kind, and noisy test_tail.
If git_head differs, you never tested one tree. Stop calling those files "the run."
Failure analysis I keep repeating
Last month I almost merged from a loaned host. The tests looked fine in the scrollback. The receipt was missing git_head. Local replay hit a different commit. Guess which tree was dirty?
The failure was not "the free model is dumb." The failure was an unlabeled channel swap. The host still had files. The files were the wrong tree.
Write that on a sticky note. I did, after I ate the mistake.
Limitations
This workflow will annoy you, and that is useful. A receipt cannot fix a vague prompt. A free model will miss design bugs. A free server will vanish at a bad moment.
Do not use this approach when:
- You need an uptime or SLA story.
- The repo cannot leave your laptop.
- The change is a security boundary.
- You cannot state
TEST_CMDin one line. - You plan to merge from the host with no local replay.
Also skip it if you will not read the JSON. An unread receipt is more theater.
What I want you to try
Which myth did you repeat this week? Run run_receipt.py once on a throwaway branch. Then tell me which field surprised you.
Top comments (0)