The founder sat under a kitchen bulb at 23:40.
Rain hit the window of a one-room rental.
A signup form posted and came back as 500.
A cloud console waited with an empty billing form.
One hosted model refused to run without a card.
The night would die inside that checkout flow.
Forum threads this week praised models over human coders.
Other threads treated agents as costumes over plain scripts.
A solo founder can leave both debates unread.
This shop does not need a digital employee.
It needs a borrowed garage for a single bicycle.
The bay offers wall tools, a floor, and one working wheel.
The method stays small on purpose for this night.
A human writes a failing test by hand.
A model may patch product code until that test turns green.
No agent loop sits on top of that.
No tool-calling graph rents a fleet of servers.
A file, a receipt, and a test runner finish the job.
Indie work has a cash shape as well as a code shape.
The first invoice should follow the first user, not precede it.
A free coding path is a loaned wrench for that order.
MonkeyCode is an open-source project aimed at that constraint.
It currently offers free model access and a free server option.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The product mention stops here so the method can stand alone.
The workflow below still holds if that workshop is ignored.
A laptop and pytest already close the Friday loop.
The extra server is only a second bay for a public URL.
Paid platforms often optimize for long looping sessions.
A one-person shop optimizes for a billed-at-zero night.
The receipt exists to keep that night from sprawling.
The target for the night is deliberately boring.
The night needs one register function and one blank-email hole.
One handwritten test guards that hole before any prompt.
Blank email is a good Friday bug for a reason.
It is local, testable, and visible in one function.
A founder can finish it before the kettle cools.
The snippets are a worked example, not a client story.
Create a folder and drop in a tiny module.
# app.py — worked example, not production history
STORE = []
def register(email: str) -> dict:
STORE.append({"email": email})
return {"ok": True, "email": email}
Empty strings still walk into the in-memory store.
The founder can see that without a model.
The model is not invited to play detective.
The red test is written by hand next.
The file lives under tests and imports the module.
# tests/test_signup.py — worked example
from app import STORE, register
def test_register_rejects_blank_email():
STORE.clear()
try:
register("")
except ValueError:
assert STORE == []
return
raise AssertionError("blank email must not register")
Run that test before any prompt is written.
Keep the module on PYTHONPATH so pytest can import it.
mkdir -p tests
export PYTHONPATH=.
python3 -m pytest tests/test_signup.py -q
echo $?
The pytest command must fail on that file.
That failure is the ticket into the garage.
Without a red ticket, the wrench stays on the hook.
The founder now asks a coding model for a patch.
The prompt names the test and forbids new files.
The answer is saved as a unified diff, nothing else.
# worked example: store the model output, do not pipe it yet
cat > /tmp/model.patch <<'EOF'
--- a/app.py
+++ b/app.py
@@ -3,5 +3,7 @@ STORE = []
def register(email: str) -> dict:
+ if not email or not email.strip():
+ raise ValueError("email required")
STORE.append({"email": email})
return {"ok": True, "email": email}
EOF
A typical agent would now chain more tool calls.
This loop refuses that chain on purpose.
A receipt script reads the patch like a landlord.
#!/usr/bin/env python3
"""Worked example: accept a model patch only with a red test."""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
SECRET_HINT = re.compile(
r"(api[_-]?key|secret|password|BEGIN (RSA |OPENSSH )?PRIVATE KEY)",
re.I,
)
MAX_FILES = 4
MAX_LINES = 120
def parse_diff_paths(diff: str) -> list[str]:
paths = []
for line in diff.splitlines():
if line.startswith("+++ b/"):
paths.append(line[6:].strip())
return paths
def main() -> int:
parser = argparse.ArgumentParser(description="Borrowed-garage patch receipt")
parser.add_argument("--diff", required=True)
parser.add_argument("--require-test", required=True)
parser.add_argument("--out", default="garage_receipt.json")
args = parser.parse_args()
diff_text = Path(args.diff).read_text(encoding="utf-8")
test_path = Path(args.require_test)
if not test_path.is_file():
print("receipt: missing named test", file=sys.stderr)
return 2
paths = parse_diff_paths(diff_text)
if not paths:
print("receipt: empty diff", file=sys.stderr)
return 2
if len(paths) > MAX_FILES:
print(f"receipt: too many files: {paths}", file=sys.stderr)
return 2
for path in paths:
if Path(path).name == test_path.name:
print("receipt: model cannot edit the red test", file=sys.stderr)
return 2
if path.endswith(".pem") or path.endswith(".env"):
print("receipt: forbidden path", file=sys.stderr)
return 2
touches = sum(
1
for line in diff_text.splitlines()
if line.startswith("+") or line.startswith("-")
)
if touches > MAX_LINES:
print("receipt: diff too large", file=sys.stderr)
return 2
if SECRET_HINT.search(diff_text):
print("receipt: secret-shaped text", file=sys.stderr)
return 2
digest = hashlib.sha256(diff_text.encode("utf-8")).hexdigest()[:16]
receipt = {
"test": str(test_path),
"files": paths,
"line_touches": touches,
"diff_sha256_16": digest,
"recorded_at": datetime.now(timezone.utc).isoformat(),
"status": "ok_to_apply",
}
Path(args.out).write_text(json.dumps(receipt, indent=2) + "\n", encoding="utf-8")
print(json.dumps(receipt, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
Save it as garage_receipt.py and run it once.
The command prints JSON or a rejection line.
chmod +x garage_receipt.py
python3 garage_receipt.py --diff /tmp/model.patch --require-test tests/test_signup.py
A JSON blob should appear with status ok_to_apply.
That blob is the broom at the end of the night.
It records files, a hash, and the named test.
Apply the patch only after that status prints.
Use patch so an empty folder does not need git yet.
patch -p1 < /tmp/model.patch
export PYTHONPATH=.
python3 -m pytest tests/test_signup.py -q
A green result means the bicycle wheel turns.
A red result means the patch goes in the bin.
There is no debate with a failed receipt.
A Makefile keeps the hands in that order.
Four targets refuse any mixed sequence from agent demos.
.PHONY: red receipt apply green
export PYTHONPATH := .
red:
python3 -m pytest tests/test_signup.py -q; test $$? -eq 1
receipt:
python3 garage_receipt.py --diff /tmp/model.patch --require-test tests/test_signup.py
apply:
patch -p1 < /tmp/model.patch
green:
python3 -m pytest tests/test_signup.py -q
The founder types make red, then make receipt, then make apply.
The last step is make green, nothing else.
Any other order is a habit from paid agent demos.
The garage metaphor stays strict for a reason.
A neighbor who lends a bay does not expect a new pit.
The visitor fixes one bike and sweeps the oil.
Free model access plays the role of that loaned wrench.
It is not a staff engineer and not a research lab.
The free server option is a second bay for a demo URL.
Skip the remote box when the laptop still compiles.
Copy only a built folder when a URL would help.
Do not let the model invent a second database.
# unexecuted example — replace HOST with a box the founder already controls
# rsync -az --delete dist/ HOST:~/first-ship/
That copy remains optional in this worked example.
It assumes a host the founder already owns.
A free server option can stand in for that host.
Limits sit beside the receipt, not under it.
Free model access can change without a letter.
Patch quality swings with task shape and luck.
The script does not prove users want the product.
It does not prove the patch is kind to later readers.
It only proves one named test moved from red to green.
Wide refactors do not belong in this bay.
Migrations, IAM, and payment webhooks need a slower desk.
Those changes want two humans and a weekday morning.
Regulated customer data should never enter this loop.
Secrets in prompts leak into logs and receipts.
A public free server is the wrong shelf for customer dumps.
Latency SLAs do not live on borrowed floors.
Overnight agent runs do not belong here either.
This method wants a human awake at the folding desk.
Awe and scorn will keep trading places in feeds.
An indie founder can mute both until the form returns 201.
The score is a working signup, not a thread reply.
Write the red test on the founder's own time.
Borrow the wrench only for the smallest patch.
Sweep with a receipt before anyone else sees the bay.
If the laptop still has heat, stop there.
Invite one user only after the green test holds.
Then close the billing tab and go to sleep.
Top comments (0)