DEV Community

Charlie Zhu
Charlie Zhu

Posted on

The Loaner Bench Workshop

On a Tuesday evening a teaching assistant still has twenty USB sticks on the desk. Half the room can clone a repo. The other half cannot paste an API key into a homework file that will later be committed, screenshotted, and forgotten. The lab is supposed to teach how a coding assistant actually asks a program to do work. The bottleneck is not syntax. It is the missing bench.

A loaner bench is an old shop-floor idea. A machinist does not buy a surface plate for a two-hour class. The tool crib issues one, logs the serial, and wants it back with the workpiece and a note. The same pattern fits a ninety-minute session on tool calling. Students borrow a remote coding environment, run a tiny agent against a local function, and return a transcript that a script can grade. They do not take the bench home as a secret.

This outline is a teaching plan, not a field report from a particular campus. Instructors should treat the timings as a starting grid and the code as a rerunnable kernel. The scene above is a composite of a common lab failure, not a claim about one named classroom.

What the room is trying to prove

Industry conversation this week keeps circling agents that never leave the browser, models that emit tool calls, and the quieter worry that engineers stop reading what they ship. A class can use that noise without reenacting it. The learning goal is narrower. After ninety minutes a student can show that a model requested a named function, that the runtime executed only that function, and that a second pass with a wrong name was rejected.

MonkeyCode enters here as the loaner, not as the lesson. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The product's free model access and free server option are enough to give a room a shared bench when the school does not want twenty personal keys in a homework repo. The exercises below still run if that mention is stripped. The verifier is local Python. The remote side is only a place to draft and compare prompts.

The bench is optional on purpose. A student who already has another endpoint can point the same client at it. A student with no network can still run the grader against a recorded transcript. The class is about the call slip, not the brand of the crane.

Timing for a ninety-minute room

The first fifteen minutes are paper. Each pair writes two function names on a card: lookup_part and check_bin. They write one sentence each about arguments and one sentence about what the model is forbidden to invent. No laptops yet. The card is the contract. If the later transcript disagrees with the card, the card wins.

The next twenty-five minutes are the local runtime. Students paste the kernel below into a file named loaner_bench.py and run it once with a fixture transcript. The fixture is honest. It already contains a legal call and an illegal one. The point is to watch the gate close before anyone talks to a model.

Minutes forty through seventy are the live pass. Pairs send a short parts question through whatever draft environment the instructor issued. They paste the model's raw function-call text into the same script. They do not paste generated business logic into the inventory functions. If the model writes a new helper, that helper is treated as contamination and the run is marked dirty.

The last twenty minutes are the handback. Each pair submits three files: the card photo or text, loaner_bench.py, and transcript.json. The teaching assistant runs one command. Green means the legal call executed and the illegal call died. Red means the bench was used as a vending machine.

The kernel students can rerun

The inventory is deliberately boring. Boredom is the point. A SKU either exists or it does not. The model is not asked to design a warehouse.

# loaner_bench.py
# Teaching kernel. Label: runnable locally with Python 3.11+.
from __future__ import annotations

import json
import sys
from dataclasses import dataclass
from typing import Any, Callable

BIN_MAP = {
    "SKU-1044": {"bin": "A-12", "qty": 8},
    "SKU-2201": {"bin": "C-03", "qty": 0},
}

def lookup_part(sku: str) -> dict[str, Any]:
    row = BIN_MAP.get(sku)
    if row is None:
        return {"ok": False, "error": "unknown_sku"}
    return {"ok": True, "sku": sku, "qty": row["qty"]}

def check_bin(sku: str) -> dict[str, Any]:
    row = BIN_MAP.get(sku)
    if row is None:
        return {"ok": False, "error": "unknown_sku"}
    return {"ok": True, "sku": sku, "bin": row["bin"]}

TOOLS: dict[str, Callable[..., dict[str, Any]]] = {
    "lookup_part": lookup_part,
    "check_bin": check_bin,
}

@dataclass(frozen=True)
class CallSlip:
    name: str
    args: dict[str, Any]

def parse_slip(raw: str) -> CallSlip:
    payload = json.loads(raw)
    name = payload["name"]
    args = payload.get("args") or {}
    if not isinstance(name, str) or not isinstance(args, dict):
        raise ValueError("malformed_slip")
    return CallSlip(name=name, args=args)

def execute_slip(slip: CallSlip) -> dict[str, Any]:
    fn = TOOLS.get(slip.name)
    if fn is None:
        return {"ok": False, "error": "unknown_tool", "name": slip.name}
    try:
        return fn(**slip.args)
    except TypeError as exc:
        return {"ok": False, "error": "bad_args", "detail": str(exc)}

def grade(transcript_path: str) -> dict[str, Any]:
    data = json.loads(open(transcript_path, encoding="utf-8").read())
    results = [execute_slip(parse_slip(item["raw"])) for item in data["calls"]]
    legal = results[0]
    illegal = results[1]
    passed = legal.get("ok") is True and illegal.get("error") == "unknown_tool"
    return {"passed": passed, "results": results}

if __name__ == "__main__":
    path = sys.argv[1] if len(sys.argv) > 1 else "transcript.json"
    print(json.dumps(grade(path), indent=2))
Enter fullscreen mode Exit fullscreen mode

A fixture sits beside it. The first slip is the one the card allowed. The second slip is a name the model likes to invent when nobody is watching.

{
  "calls": [
    {"raw": "{\"name\": \"lookup_part\", \"args\": {\"sku\": \"SKU-1044\"}}"},
    {"raw": "{\"name\": \"optimize_warehouse\", \"args\": {\"sku\": \"SKU-1044\"}}"}
  ]
}
Enter fullscreen mode Exit fullscreen mode

Run it from a shell the same way a grader will.

python loaner_bench.py transcript.json
Enter fullscreen mode Exit fullscreen mode

A passing room prints "passed": true. The legal lookup returns quantity eight. The invented optimizer dies with unknown_tool. Students who "fixed" the illegal call by adding a new function have failed the lab even if the JSON looks pretty. The crib does not grow new tools because a paragraph sounded confident.

Live pass without baking a vendor into the kernel

The kernel never imports a vendor SDK. That is intentional. During the live window, students may use MonkeyCode's free model access on the free server to draft a prompt such as: ask for SKU-1044 quantity, reply only with a JSON call slip for lookup_part or check_bin. They copy the model's text into a new transcript.json and rerun the grader.

If an instructor wants a client, keep it labeled as a sketch. Endpoints, model identifiers, and quotas change, and this article does not freeze them. A minimal shape looks like a POST of a prompt string and a paste of the text that came back. Anything more specific belongs in the day's lab sheet, not in a public kernel that students will copy for a year.

The interesting failure is consistent. Models that have been trained to be helpful will offer optimize_warehouse or a paragraph of advice. The crib does not take advice. It takes a slip. Watching a fluent paragraph fail the grader teaches more than a green smoke test. A second mutation is worth planting on the projector: change SKU-1044 to SKU-9999 and confirm the runtime does not invent a bin.

Students who finish early write a third slip by hand, check_bin for SKU-2201, and predict the quantity-zero row before they run it. The prediction is the point. The remote bench is only a way to produce text fast enough that the gate still has time to slam.

What the assistant should mark

The teaching assistant is not scoring prose. Three checks are enough. The card names match TOOLS. The first live call executed a function that already existed. The second call, or a planted mutation, still returns unknown_tool. A louder room can add a fourth check: SKU-9999 must not become a new inventory row because a model apologized and invented stock.

Pairs that cannot get a remote draft can still pass. They hand-write two slips, save them as JSON, and run the same command. The loaner bench is a convenience. It is not the credential. A transcript produced on a bus with no signal is as valid as one produced on a free server, provided the grader stays green.

Limits, and who should skip this lab

The kernel is not a production agent. It has no authentication, no retries, no streaming, and no audit trail beyond a JSON file. It will not measure latency. It will not prove that a free server will be there next term. Instructors who need a named model, a paper trail for procurement, or a data-processing agreement should not route classroom secrets through a shared loaner at all.

Do not put real customer SKUs, badges, or homework credentials into the prompt. The BIN_MAP is fiction. If a department cannot tolerate paste-into-a-cloud-box, run only the fixture path. Teams that already have a locked-down endpoint with an allow-list should keep using that endpoint. This lab is for the first time someone watches a model reach for a tool that does not exist. It is not a bake-off and it is not a performance study.

A class that wants a loaner bench rather than a stack of personal keys can run this once on MonkeyCode's free model access and free server option, then keep the grader after the accounts go quiet. The workpiece is the transcript. The bench goes back to the crib.

Top comments (0)