A Thursday lab emptied faster than the logs. Laptops snapped shut while the projector still showed a green check copied from a chat transcript. The instructor asked for the command that produced it. Nobody could name one. The volunteer machine that had supposedly run the tests was already wiped for the next class.
Machine shops solved a similar vanishing act a century ago. A job jacket travels with the part. Prints, inspection notes, and the last measured size stay in the envelope even when the bench changes hands. The next shift does not have to trust a story about a clean run. The envelope either holds the numbers or the part does not move.
Agent-assisted homework fails the same way a part fails without a traveler. A model proposes a patch. Someone pastes it. A laptop somewhere maybe ran a test. The directory disappears. The grade rests on a screenshot of confidence. This workshop treats evidence as the workpiece. Students freeze a failing case, fill a tiny jacket file, and close the job only when a gate script can read both the failure and the repair.
The outline below is a proposed 75-minute lab. Instructors can rerun it as written on a single laptop. A shared machine is useful when the room cannot promise identical local setups, not because the jacket format needs a vendor.
Classroom operators sometimes need a loaner bench rather than a fleet of matching laptops. MonkeyCode's free model access and free server option can sit at the back of that room as Station B, where the gate script actually runs. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The files in this article do not depend on that product. Remove the vendor and the exercises still close.
Minute zero through ten is the scene, not a lecture. The instructor seeds a one-function repository with a quiet off-by-one and refuses chat screenshots as proof. Students may use an assistant to read the code. They may not close the job until the jacket names the command that failed, the command that passed, and the file that changed. The rest of the period is four stations on a clock, with a short buffer at the end for jackets that still refuse to close.
Station A occupies minutes ten through twenty-five. Each pair clones the seed and runs the test file before touching production code. The point is to capture a real failure, not to negotiate with a model about whether a failure exists. A jacket that starts with "tests were red in the chat" is rejected on sight. The seed looks like this.
# clip.py — seeded defect, do not "fix" until the jacket records a failing run
def clip_index(n: int, i: int) -> int:
"""Clamp i into [0, n-1] for n > 0.
The seeded bug returns n when i == n, which is one past the last valid index.
"""
if n <= 0:
raise ValueError("n must be positive")
if i < 0:
return 0
if i > n: # seeded defect: should be i >= n
return n - 1
return i
# test_clip.py
import unittest
from clip import clip_index
class ClipIndexTests(unittest.TestCase):
def test_zero(self):
self.assertEqual(clip_index(5, 0), 0)
def test_last_valid(self):
self.assertEqual(clip_index(5, 4), 4)
def test_equal_to_n(self):
self.assertEqual(clip_index(5, 5), 4)
def test_past_n(self):
self.assertEqual(clip_index(5, 9), 4)
def test_rejects_empty(self):
with self.assertRaises(ValueError):
clip_index(0, 0)
if __name__ == "__main__":
unittest.main()
The first recorded command is ordinary. Students run it in the repo root and paste the tail of the output into the jacket, including the FAIL line. They do not summarize it. They do not translate it into optimism.
python -m unittest test_clip.py -v
Station B occupies minutes twenty-five through forty. Pairs write jacket.json before they edit clip.py. The jacket is deliberately boring. It is a traveler card, not a diary. Empty strings are allowed at this station only for pass_cmd and changed_file. Everything else must already be true.
{
"job_id": "lab-clip-01",
"operator": "pair-c",
"seed_sha": "replace-with-git-rev-parse-HEAD",
"fail_cmd": "python -m unittest test_clip.py -v",
"fail_excerpt": "FAIL: test_equal_to_n",
"pass_cmd": "",
"pass_excerpt": "",
"changed_file": "",
"closed": false
}
git rev-parse HEAD
A jacket without a seed revision is a jacket for a rumor. Assistants are allowed to draft the JSON. They are not allowed to mark closed true. That bit belongs to the gate, which does not parse natural language and does not care how confident the model sounded.
Station C occupies minutes forty through sixty. This is the only station that may edit clip.py. The repair is a single comparison change, which is the pedagogical trap. Students who skip the jacket will finish in ninety seconds and have nothing to hand over. Students who keep the traveler will still finish quickly, but they will have a command log that another pair can rerun on a different machine. After the edit they run the same unittest module again and fill pass_cmd, pass_excerpt, and changed_file.
# clip.py — expected repair
def clip_index(n: int, i: int) -> int:
if n <= 0:
raise ValueError("n must be positive")
if i < 0:
return 0
if i >= n:
return n - 1
return i
If the class is using a loaner bench, Station C runs there. The local laptop keeps the editor. The bench keeps the interpreter. That split is the whole point of a job jacket: the part can move, the envelope has to stay consistent. A pair that cannot reproduce the passing command on the bench has not closed the job, even if their chat window is full of green checkmarks.
Station D occupies minutes sixty through seventy-five. The gate script reads the jacket, refuses missing fields, and reruns fail_cmd against a copy of the seed only when instructors want a stricter lab. The default teaching version is milder. It checks that the jacket is complete, that closed is still false coming in, that the named file exists, and that pass_cmd exits zero now. Students invoke it once.
# close_job.py — proposed gate; run from the repo root
import json
import subprocess
import sys
from pathlib import Path
REQUIRED = (
"job_id",
"operator",
"seed_sha",
"fail_cmd",
"fail_excerpt",
"pass_cmd",
"pass_excerpt",
"changed_file",
)
def die(msg: str) -> None:
sys.stderr.write(msg + "\n")
sys.exit(1)
def main() -> None:
jacket_path = Path("jacket.json")
if not jacket_path.is_file():
die("no jacket.json on the bench")
jacket = json.loads(jacket_path.read_text(encoding="utf-8"))
for key in REQUIRED:
value = jacket.get(key, "")
if not isinstance(value, str) or not value.strip():
die(f"jacket field empty: {key}")
if jacket.get("closed") is True:
die("jacket already closed; reopen by setting closed to false")
changed = Path(jacket["changed_file"])
if not changed.is_file():
die(f"changed_file missing: {changed}")
excerpt = jacket["fail_excerpt"].strip()
if "FAIL" not in excerpt and "Error" not in excerpt:
die("fail_excerpt does not look like a failure")
pass_cmd = jacket["pass_cmd"]
completed = subprocess.run(pass_cmd, shell=True, check=False)
if completed.returncode != 0:
die("pass_cmd still failing; jacket stays open")
jacket["closed"] = True
jacket_path.write_text(json.dumps(jacket, indent=2) + "\n", encoding="utf-8")
print("job closed:", jacket["job_id"])
if __name__ == "__main__":
main()
python close_job.py
cat jacket.json
A closed jacket is the only artifact collected at the door. Chat logs are optional attachments. They are not the workpiece. Pairs who finish early swap jackets and try to reopen a neighbor's job by checking out the seed revision and running fail_cmd again. If the failure cannot be revived from the recorded command, the jacket was theater and the station is still open.
The method has limits that matter in a real room. The gate trusts pass_cmd as a shell string, so a student can point it at true or at a test file they gutted. Instructors who need a harder lab should pin the command to python -m unittest test_clip.py -v inside close_job.py and stop reading it from JSON. The seed shown here is a one-line defect. It teaches the traveler, not numerical methods, concurrency, or prompt injection. A jacket does not make a model honest. It only makes missing evidence obvious.
This lab is the wrong tool for take-home exams that must stay offline, for teams that already require reviewed CI on every branch, and for anyone hoping a free shared bench will replace code review. It is also the wrong tool if the instructor cannot see the gate output. A jacket closed on a private laptop and pasted into Slack is another screenshot. The envelope has to be on a bench the next pair can touch.
The repair itself is almost besides the point. What the period trains is a habit older than software: the part does not leave the shop because someone remembers it fitting. It leaves because the jacket still contains the measurement. Students who keep that envelope can change assistants, laptops, and even the loaner server. The job remains the same size.
Top comments (0)