The lab clock read 18:12. Twenty-eight laptops sat on one long table, each unzipped from the same starter archive. Course staff had pointed office hours at a shared coding-assistant endpoint so nobody had to paste a personal key into a homework README. Then the first wave of requests left the room, and the endpoint went quiet in the same way a compiler farm goes quiet when every job ships a tarball.
Nobody had written a clever exploit. Students had been told to give the assistant enough context. Enough, in this room, meant the solution file, the README, a 1.8 MB weather extract, and in two cases a stray node_modules tree that should never have landed on a lab image. The endpoint did not fail closed. It just got slow. Slow looks like thoughtfulness on one screen. From the TA desk it looks like a baggage carousel that never stops.
A compiler would have refused the blob before the link step. A prompt packer often does the opposite. It treats every extra path as kindness. This note is a proposed lab playbook, not a war story from a named campus. Label every snippet below as a throwaway sketch until you run it on your own image.
The room failed on weight, not on wit
Shared endpoints punish variance more than they punish bad ideas. One student sends 4 KB of normalize.py. The next student sends that file plus stations.csv plus a copied lecture PDF. Both think they followed the same instruction. Only one of those payloads is a homework question. The other is a moving van.
Airports already solved the social part of this. A bag that does not fit the scale does not board because the owner smiled. The scale is public. The cap is printed. The receipt is boring on purpose. A 90-minute lab needs that object more than it needs another slide about “being careful with context.”
The assignment, then, is not “get a patch from a model.” The assignment is to produce a receipt the autograder can read, then maybe spend one remote call. If the receipt is wrong, the remote call never happens. That ordering is the whole intervention.
What the receipt has to prove
A useful receipt is smaller than a chat log and stricter than a screenshot. It has to name every path that entered the prompt, the raw byte size of each path, a class-defined cap, and a boolean that says whether the packer would have boarded the bag. It also has to be local. Students without a network path still need a gradeable artifact.
Treat token estimates as a labeled heuristic, not as a tokenizer. Character count divided by four is a ruler you can explain in one sentence. It is not a claim about any vendor’s bill. Print both bytes and that estimate. Grade the bytes. The estimate exists so a student can see order of magnitude before they argue with the cap.
Keep secrets out of the receipt body. A CSV that contains a column named api_key is still a CSV. The packer should refuse that filename pattern the same way it refuses .env. The point is not cryptography. The point is that a homework zip should not become an egress channel because someone wanted “more context.”
Artifact: a compiler-shaped prompt scale
Create a disposable homework tree. Do not point this at a real course repo until the exit codes are boring.
mkdir -p lab_scale/{src,data,hidden}
cat > lab_scale/src/normalize.py <<'PY'
"""Normalize station rows. Keep this file small on purpose."""
COLUMNS = ("station_id", "celsius")
def row_ok(parts):
return len(parts) == 2 and parts[1].replace(".", "", 1).isdigit()
PY
printf '%s\n' 'station_id,celsius' 's1,12.0' 's2,bad' > lab_scale/data/stations.csv
python - <<'PY'
from pathlib import Path
p = Path("lab_scale/data/stations_big.csv")
p.write_text("station_id,celsius\n" + "s,1\n" * 80000, encoding="utf-8")
print(p.stat().st_size)
PY
echo 'TOKEN=lab-demo-not-a-secret' > lab_scale/hidden/dummy.env
stations_big.csv exists to fail. dummy.env exists to fail louder. The only file that should board in the happy path is src/normalize.py.
The packer below walks an allowlist, skips denylisted names, and writes prompt_receipt.json next to the homework. It never opens a socket. Network access is a later hop with a different exit code.
# prompt_scale.py
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
ALLOW_SUFFIX = {".py", ".md", ".txt"}
DENY_NAME = {".env", ".git", "node_modules", "__pycache__"}
DENY_SUFFIX = {".csv", ".pdf", ".zip", ".png"}
MAX_FILE_BYTES = 8_000
MAX_PROMPT_BYTES = 12_000
ROOT = Path(os.environ.get("LAB_ROOT", ".")).resolve()
def blocked(path: Path) -> str | None:
parts = set(path.parts)
if parts & DENY_NAME:
return "denied_name"
if path.name in DENY_NAME:
return "denied_name"
if path.suffix.lower() in DENY_SUFFIX:
return "denied_suffix"
if path.suffix.lower() not in ALLOW_SUFFIX:
return "suffix_not_allowed"
return None
def weigh() -> dict:
files = []
refused = []
total = 0
for path in sorted(ROOT.rglob("*")):
if not path.is_file():
continue
rel = str(path.relative_to(ROOT)).replace("\\", "/")
reason = blocked(path)
size = path.stat().st_size
if reason:
refused.append({"path": rel, "bytes": size, "reason": reason})
continue
if size > MAX_FILE_BYTES:
refused.append({"path": rel, "bytes": size, "reason": "file_cap"})
continue
text = path.read_text(encoding="utf-8", errors="replace")
files.append({"path": rel, "bytes": len(text.encode("utf-8"))})
total += len(text.encode("utf-8"))
estimate = total // 4 # heuristic ruler, not a vendor tokenizer
boarded = total <= MAX_PROMPT_BYTES and any(f["path"].startswith("src/") for f in files)
return {
"root": str(ROOT),
"files": files,
"refused": refused,
"prompt_bytes": total,
"char_div_4_estimate": estimate,
"caps": {"file": MAX_FILE_BYTES, "prompt": MAX_PROMPT_BYTES},
"boarded": boarded,
}
def main() -> int:
receipt = weigh()
out = ROOT / "prompt_receipt.json"
out.write_text(json.dumps(receipt, indent=2) + "\n", encoding="utf-8")
print(out)
print(f"PROMPT_BYTES={receipt['prompt_bytes']}")
print(f"BOARDED={str(receipt['boarded']).lower()}")
if not receipt["boarded"]:
print("preflight_refused")
return 2
return 0
if __name__ == "__main__":
sys.exit(main())
Run it from the homework root. The command is the lab’s compiler invocation. Students should learn that exit code 2 is a successful refusal, not a broken laptop.
cd lab_scale
LAB_ROOT=. python ../prompt_scale.py; echo EXIT:$?
python -m json.tool prompt_receipt.json | head
Expect data/stations.csv and data/stations_big.csv in refused with denied_suffix. Expect hidden/dummy.env in refused with denied_name. Expect src/normalize.py under files. If BOARDED=true on this tree, the allowlist drifted. Fix the packer before you add a network hop.
Autograder contract, not a vibe check
Staff can grade the receipt with a second script that never calls a model. That split matters in a room where twenty-eight machines share one wireless hop. The grader below is intentionally dull. Dull is what you want at 18:40 when three students still have a CSV in the prompt.
# grade_receipt.py
import json
import sys
from pathlib import Path
needed = {"files", "refused", "prompt_bytes", "boarded", "caps"}
def grade(path: Path) -> int:
data = json.loads(path.read_text(encoding="utf-8"))
missing = needed - set(data)
if missing:
print(f"schema_missing={sorted(missing)}")
return 2
boarded_csv = [r for r in data["files"] if r["path"].endswith(".csv")]
boarded_env = [r for r in data["files"] if r["path"].endswith(".env")]
if boarded_csv or boarded_env:
print("egress_path_in_files")
return 2
if data["prompt_bytes"] > data["caps"]["prompt"] and data["boarded"]:
print("cap_and_flag_disagree")
return 2
print("receipt_ok")
return 0
if __name__ == "__main__":
sys.exit(grade(Path(sys.argv[1])))
python grade_receipt.py lab_scale/prompt_receipt.json; echo EXIT:$?
A student who cannot reach any remote host can still submit a zip that contains prompt_receipt.json and a packer log. The course can mark that work without pretending the wireless hop was part of the learning objective.
Keep the last hop as an environment variable
Only after the receipt is green should a staff image offer a single POST. Leave the host name out of the homework zip. Bake a timeout. Send the already-weighed text, not a second walk of the tree. The sketch uses the standard library so a lab image does not need a vendor SDK.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. If course staff already have MonkeyCode's free model access and free server option, MODEL_HTTP_URL can point at that hop once the receipt is local and boring. The syllabus still grades the scale. The product is a last mile, not the assignment.
# last_hop.py
import json
import os
import urllib.error
import urllib.request
from pathlib import Path
ROOT = Path(os.environ.get("LAB_ROOT", ".")).resolve()
def pack_prompt(receipt: dict) -> str:
chunks = []
for item in receipt["files"]:
text = (ROOT / item["path"]).read_text(encoding="utf-8")
chunks.append(f"# {item['path']}\n{text}")
return "\n\n".join(chunks)
def main() -> None:
receipt = json.loads((ROOT / "prompt_receipt.json").read_text(encoding="utf-8"))
if not receipt["boarded"]:
raise SystemExit("refusing last hop: receipt not boarded")
url = os.environ.get("MODEL_HTTP_URL", "").strip()
if not url:
print("NO_REMOTE=1")
print("prompt_chars", len(pack_prompt(receipt)))
return
body = json.dumps({"prompt": pack_prompt(receipt), "receipt_bytes": receipt["prompt_bytes"]}).encode("utf-8")
req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=20) as resp:
print(resp.status)
print(resp.read()[:400])
except urllib.error.URLError as exc:
raise SystemExit(f"last_hop_failed:{exc}")
if __name__ == "__main__":
main()
LAB_ROOT=lab_scale python last_hop.py
# MODEL_HTTP_URL remains unset on purpose during the first drill
Unset MODEL_HTTP_URL is a feature. It lets a student on a train finish the weighed prompt without turning network loss into a zero. Staff can export the variable on the lab image later. Do not print the URL into the receipt. Receipts get zipped. Zips get uploaded. Uploads outlive the office-hour VLAN.
Three drills that should fail in public
Run these as a projected demo, not as a surprise on homework night. The first drill adds the big CSV to the allowlist in a local fork and shows the file cap fire. The second drill copies dummy.env to src/notes.env and shows that a src/ prefix does not launder a denylisted name. The third drill raises MAX_PROMPT_BYTES in a student copy and shows the grader catching cap_and_flag_disagree if they also force boarded to true by hand.
# drill 1: prove the CSV never boards even if a student edits ALLOW_SUFFIX
python - <<'PY'
from pathlib import Path
text = Path("prompt_scale.py").read_text()
assert ".csv" not in text.split("ALLOW_SUFFIX")[1].split("}")[0]
print("allowlist_does_not_admit_csv")
PY
# drill 2: name check beats directory prefix
cp lab_scale/hidden/dummy.env lab_scale/src/notes.env
LAB_ROOT=lab_scale python prompt_scale.py || true
python - <<'PY'
import json
from pathlib import Path
data = json.loads(Path("lab_scale/prompt_receipt.json").read_text())
hits = [r for r in data["refused"] if r["path"].endswith("notes.env")]
print(hits[0]["reason"] if hits else "MISSING_REFUSAL")
PY
rm lab_scale/src/notes.env
Public failure is the teaching aid. If the only demo is a green hop, students learn that the scale is ceremonial. Ceremonial scales get deleted the night before the deadline.
Decision table for the 90-minute session
| Object | In the zip | Why |
|---|---|---|
prompt_receipt.json |
Yes | Autograder can mark a dead wireless hop |
| Packer source | Yes | Staff can see who widened the allowlist |
src/*.py that boarded |
Yes | That is the actual question |
| Large CSV or PDF | No | Weight, not “context” |
.env and key-shaped files |
No | Homework is not an egress path |
Absolute MODEL_HTTP_URL
|
No | Receipts outlive the lab VLAN |
| Vendor SDK | No | Lab images stay small |
| Multi-file agent loop | No | Congestion returns under a new name |
Print the table in the assignment PDF. Future staff will try to replace it with a paragraph about responsibility. Paragraphs do not produce exit codes.
Limits of a byte scale
This playbook measures weight and obvious filename classes. It does not understand that two short Python files can still leak a production hostname. It does not parse CSV headers for PII. It does not prove that a remote model will return a useful patch. A boarded receipt is permission to spend a hop, not evidence that the hop was wise.
The character-div-four figure will disagree with whatever tokenizer a host uses. Teach that disagreement in the first ten minutes or students will treat the receipt as a billing document. It is a baggage tag. Billing, if it exists at all, lives on the operator side of MODEL_HTTP_URL and is out of scope for this sketch.
Timeouts, auth headers, and retries are omitted. A hung last hop can still freeze a row of laptops if staff skip the unset-URL drill. Free remote capacity can change or disappear. Keep the local scale so the session still completes when the hop does not.
Do not use this as production policy for a company monorepo. Company egress needs review paths, secret scanners with real detectors, and an owner who is not a TA with a stopwatch. Do not use it as a substitute for test fixtures. A model that never sees stations.csv cannot be blamed for failing to clean s2,bad. That case belongs in tests/, which this packer never boards.
Who should skip the scale
Skip this design if you already meter prompts in CI with an enforced tokenizer and a budget account. Skip it if the course goal is long-horizon agents that must read data files to finish the task. Skip it if students are working on personal machines with no shared bottleneck and no zip-based hand-in.
A 30-seat room is a fairness problem before it is a model-quality problem. The compiler already taught that lesson with object files that grew too large to link. Put the same refusal in front of the prompt packer. Check the receipt into the homework zip. Leave the host name out of the zip. Students who never reach a remote still pass when the preflight refuses for a documented reason.
Top comments (0)