DEV Community

Charlie Zhu
Charlie Zhu

Posted on

The Assumption Receipt: A Two-Hour Lab for AI Patches

The lab was quiet except for keyboards. A teaching assistant walked the aisle of a Saturday workshop and saw the same invented file on three laptops: redis.conf. The assignment had been a twelve-line inventory script that stored counts in a JSON file. Nobody had asked for a cache. The model treated a sold-out bug as a scale problem, and scale, in its prior, meant Redis.

That habit is the lesson, not the embarrassment. Generation is cheap. Silent architecture is cheap with it. A two-hour lab can intercept the habit before it reaches git.

The trend in agent write-ups is familiar: tools that assume a missing service, a missing queue, a missing cloud account. This outline does not catalog those terms. It gives students a contract they can rerun on a laptop, score with a script, and fail on purpose.

The contract, in one file

Every patch in the lab must ship an assumption_receipt.json beside the diff. The file is not documentation theater. It is a gate. If the receipt claims a dependency the repo does not already import, the grader fails the student even when the unit tests pass.

The schema stays small so a free coding model can fill it without a lecture on agents.

{
  "task": "fix sold-out handling in stock.py",
  "files_touched": ["stock.py", "test_stock.py"],
  "new_dependencies": [],
  "new_services": [],
  "assumptions": [
    {
      "claim": "inventory is a local JSON file",
      "evidence": "stock.py reads STOCK_PATH",
      "confidence": "high"
    }
  ],
  "unknowns": ["concurrent writers"]
}
Enter fullscreen mode Exit fullscreen mode

Confidence is a word, not a score. Students learn faster when they have to write low next to a guess. The unknowns array is the escape hatch. Inventing Redis to fill that hatch is the failure mode the clock is built around.

Minute 0–15: the phantom cache

The instructor starts from a cold repo, not a slide. Clone, run, watch it lie.

python3 -m venv .venv
. .venv/bin/activate
pip install pytest
python stock.py buy widget 1
pytest -q
Enter fullscreen mode Exit fullscreen mode

stock.py is the entire product. It loads stock.json, subtracts a quantity, and writes the file back. The bug is a comparison that treats zero as available. The prompt students will receive is deliberately sloppy, because sloppy prompts are what they paste at 1 a.m.

This inventory keeps selling after stock hits zero.
Add caching if needed and fix the bug. Keep it production-ready.
Enter fullscreen mode Exit fullscreen mode

"Production-ready" is bait. In this lab it is how a model justifies Docker, Redis, and a health check for a script that never listens on a port. The instructor shows one unrestrained completion, then freezes the tree. Students do not start coding yet. They only name what was assumed.

Minute 15–40: Exercise 1, generate without a receipt

Each pair pastes the same prompt into whatever coding model they already use. The point is not vendor comparison. The point is the first diff.

They save the raw patch as round1.diff and answer, in prose, three lines the instructor writes on the board: which files appeared that were not in the repo, which services were named, which tests were not run. Fifteen minutes of generation, ten minutes of annotation. No merge.

A typical round-one tree looks like architecture fan fiction. docker-compose.yml arrives. requirements.txt grows redis and flask. Sometimes a second JSON file appears as a "cache layer" that never invalidates. The sold-out bug may even be fixed. That is the trap. A green test suite can still smuggle a new runtime.

Minute 40–55: the receipt becomes a test

The instructor adds grade_receipt.py to the repo. Students do not write the grader. They have to survive it.

#!/usr/bin/env python3
import json, sys, ast, pathlib

ALLOWED = {"stock.py", "test_stock.py", "stock.json", "assumption_receipt.json"}
BANNED_IMPORTS = {"redis", "docker", "boto3", "pymongo", "celery"}
BANNED_NAMES = {"Redis", "Flask", "FastAPI", "docker-compose"}

def fail(msg):
    print("FAIL:", msg)
    sys.exit(1)

receipt_path = pathlib.Path("assumption_receipt.json")
if not receipt_path.exists():
    fail("missing assumption_receipt.json")

receipt = json.loads(receipt_path.read_text())
for key in ("task", "files_touched", "new_dependencies", "new_services", "assumptions", "unknowns"):
    if key not in receipt:
        fail(f"receipt missing {key}")

if receipt["new_dependencies"] or receipt["new_services"]:
    fail("this lab forbids new dependencies and services")

for path in pathlib.Path(".").glob("**/*"):
    if path.is_file() and path.name not in ALLOWED and path.suffix in {".py", ".yml", ".yaml", ".toml"}:
        fail(f"undeclared file {path}")

source = pathlib.Path("stock.py").read_text()
tree = ast.parse(source)
for node in ast.walk(tree):
    if isinstance(node, ast.Import):
        for alias in node.names:
            if alias.name.split(".")[0] in BANNED_IMPORTS:
                fail(f"banned import {alias.name}")
    if isinstance(node, ast.ImportFrom) and node.module:
        if node.module.split(".")[0] in BANNED_IMPORTS:
            fail(f"banned import {node.module}")

for name in BANNED_NAMES:
    if name in source:
        fail(f"banned token {name}")

print("PASS: receipt accepted")
Enter fullscreen mode Exit fullscreen mode

The script is harsh on purpose. A classroom needs a binary gate, not a style debate. Teams that want a hosted runner can put the grader on a free server next to a free coding model. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option fit this lab when the school does not want students wiring personal API keys into a shared VM. The receipt, not the host, is the pedagogy.

Minute 55–90: Exercise 2, regenerate under the gate

Students keep the original prompt and add four lines.

Do not add services, containers, or dependencies.
Touch only stock.py and test_stock.py.
Write assumption_receipt.json using the lab schema.
If you lack evidence, put the claim in unknowns, do not invent a component.
Enter fullscreen mode Exit fullscreen mode

They generate round2.diff, write or accept the receipt, then run the same two commands the instructor ran at the start, plus the grader.

python grade_receipt.py
pytest -q
python stock.py buy widget 1
Enter fullscreen mode Exit fullscreen mode

The worked example they can rerun is the zero-stock path. Seed stock.json with a single widget, buy it twice, and require the second buy to raise.

# stock.py — lab fixture, not a framework
import json
from pathlib import Path

STOCK_PATH = Path("stock.json")

def load():
    return json.loads(STOCK_PATH.read_text())

def save(data):
    STOCK_PATH.write_text(json.dumps(data, indent=2))

def buy(sku, qty):
    data = load()
    item = data[sku]
    # bug: `>=` should be `>` or qty should be checked first
    if item["qty"] >= 0:
        item["qty"] -= qty
        save(data)
        return item["qty"]
    raise ValueError("sold out")
Enter fullscreen mode Exit fullscreen mode

A correct patch changes the comparison and adds a test that starts at zero. It does not add a cache. The receipt should say the store is a file, the process is single-writer, and concurrent writers are unknown. Students who still emit Redis fail the grader even if they "fixed" the arithmetic.

Minute 90–110: Exercise 3, score the lies

The last block is not more generation. It is a decision table the pairs fill from their two diffs. Columns stay few so the conversation stays on evidence.

Signal in the diff Receipt should say Grade if omitted
New import new_dependencies fail
New process or port new_services fail
Behavior change with no test unknowns warn
File outside ALLOWED files_touched fail
Claim with no path or test name confidence: low warn

Warn is oral. Fail is the script. The instructor collects one round-one diff that invented a database and one round-two receipt that refused to. The contrast is the whole unit. Cheap patches are not free if the class spends the next week deleting infrastructure nobody asked for.

Minute 110–120: what the clock did not prove

The lab does not measure model quality. It measures whether students can force a model to show its priors. A receipt can be honest and still wrong. A banned-import list can be escaped with a subprocess that curls a package installer. The grader is a teaching fence, not a supply-chain scanner.

Teams that already run production agents should not treat this as a rollout plan. Incident response is the wrong room for a two-hour schema. So is a course that cannot spare a human to read the unknowns array. The contract only works when someone looks.

The Saturday Redis files were not malice. They were a prior with nowhere to stand. Give the prior a JSON box, a failing command, and a clock. The inventory script stays small. The architecture stays the one the repo already had.

Top comments (0)