DEV Community

Morgan Zhou
Morgan Zhou

Posted on

Score the Client They Point at Your Server

You get the email at 4:07 p.m. The candidate is polite. They say the take-home looked fair until their personal key died in the second hour, and they did not want to put a card on a weekend assignment.

You already know how this ends. They stall, or they finish on a coworker’s editor seat, or they paste a local demo that never left their laptop. You are not grading engineering. You are grading who could afford a model that afternoon.

That is the quiet leak in a lot of “AI-native” take-homes. The prompt lives in your zip. The runtime lives in their wallet. When the two disagree, the pretty transcript wins, and you never see the client they would have shipped on your stack.

So rewrite the assignment until the runtime is something you can name in the README. Not “use any LLM.” A base URL. A free model path. A server you can hand every candidate without asking them to pay. Then grade the client they pointed at that box.

What you are actually hiring for

The argument around AI coding is loud this month: fluent output is not engineering. Fine. You still have to test something on a Tuesday. The useful test is not whether they can summon a fluent agent on a paid laptop. The useful test is whether they can write a small, boring client that talks to a provided endpoint, keeps secrets out of the log, and fails in a way a reviewer can replay.

Think of it like a driving exam that hands everyone the same car. You do not score the stereo. You score whether they signal, brake, and leave the lot without taking the curb with them.

A practical place to pin that car is a hosted coding environment that already offers free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source project. This article will not invent model names, token ceilings, or hardware claims. The only facts that matter for the take-home are the two you can put in a README without lying: candidates can reach a free model path, and they can use a free server instead of renting one at home.

If that pair is in the zip, the assignment stops being “impress me with your local agent” and becomes “honor this contract.”

The prompt you put in the zip

Keep the story small. You are not asking them to build a platform. You are asking them to ship a client that cannot quietly wander off to another host.

Paste something like this. Label it as the take-home, not as a production spec.

TAKE-HOME: bound-client (90 minutes, no paid key)

You are given:
- TASK.md — a failing unit test and a short product note
- a base URL in .env.example (do not hardcode)
- a requirement: all model calls go to that URL
- a requirement: write receipt.json after every run

Build a CLI `bound-client` that:
1. Reads TASK.md
2. Sends one JSON request to $BOUND_BASE_URL/v1/complete
3. Writes ./out/patch.diff if the model returns a unified diff
4. Writes ./out/receipt.json with timestamp, request_id,
   endpoint host, no secrets, and exit_reason
5. Exits 2 if the server is unreachable, 3 if the response
   is not a diff, 0 on a well-formed receipt

You may not:
- call any other model host
- print Authorization headers
- require a paid cloud account

Submit: client source, receipt.json from a run against the
provided server, and a 15-line notes.md on one failure you forced.
Enter fullscreen mode Exit fullscreen mode

Notice what is missing. There is no “build an agent framework.” There is no branded model nickname. The candidate who already has a gold local setup does not get a head start, because that gold setup is the wrong host.

Wire the example env to the free server you actually intend to grade.

# .env.example — ship this, never a live secret
BOUND_BASE_URL=https://YOUR_FREE_SERVER_HOST
BOUND_TOKEN=
Enter fullscreen mode Exit fullscreen mode

If you are using MonkeyCode’s free server option for the cohort, put that host in .env.example and nowhere else. Candidates who “accidentally” point at a personal gateway fail the contract even if the patch looks clever.

A sample solution you keep in the answer key

Do not ship this in the candidate zip. Keep it for calibration so two reviewers score the same way. The sample is intentionally dull. Dull is the point.

#!/usr/bin/env python3
"""bound-client: one request, one receipt, no leaked headers."""
from __future__ import annotations

import json
import os
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
from urllib.parse import urlparse

OUT = Path("out")
RECEIPT = OUT / "receipt.json"
PATCH = OUT / "patch.diff"


def redact(url: str) -> str:
    parsed = urlparse(url)
    return parsed.netloc or "missing-host"


def write_receipt(**payload: object) -> None:
    OUT.mkdir(exist_ok=True)
    RECEIPT.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")


def main() -> int:
    base = os.environ.get("BOUND_BASE_URL", "").rstrip("/")
    token = os.environ.get("BOUND_TOKEN", "")
    task = Path("TASK.md").read_text(encoding="utf-8")
    host = redact(base)
    started = time.time()

    if not base:
        write_receipt(endpoint_host="missing", exit_reason="no_base_url")
        return 2

    body = json.dumps({"task": task, "format": "unified_diff"}).encode("utf-8")
    req = urllib.request.Request(
        f"{base}/v1/complete",
        data=body,
        method="POST",
        headers={"Content-Type": "application/json"},
    )
    if token:
        req.add_header("Authorization", f"Bearer {token}")

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            raw = resp.read().decode("utf-8")
            request_id = resp.headers.get("X-Request-Id", "none")
    except urllib.error.URLError as exc:
        write_receipt(
            endpoint_host=host,
            request_id="none",
            elapsed_ms=int((time.time() - started) * 1000),
            exit_reason=f"unreachable:{type(exc).__name__}",
        )
        return 2

    text = raw.strip()
    ok_diff = text.startswith("diff --git") or text.startswith("--- ")
    write_receipt(
        endpoint_host=host,
        request_id=request_id,
        elapsed_ms=int((time.time() - started) * 1000),
        exit_reason="ok" if ok_diff else "not_a_diff",
        response_bytes=len(raw),
    )
    if not ok_diff:
        return 3
    PATCH.write_text(text + "\n", encoding="utf-8")
    return 0


if __name__ == "__main__":
    sys.exit(main())
Enter fullscreen mode Exit fullscreen mode

A candidate who wraps five agent libraries around this has extra surface you did not ask for. A candidate who prints the bearer token in debug mode failed a security check you can defend in a debrief.

Force one failure in your own dry run so you know the exits work before anyone else touches the zip.

BOUND_BASE_URL=http://127.0.0.1:1 python bound_client.py; echo $?
# expect 2, and a receipt whose exit_reason starts with unreachable
Enter fullscreen mode Exit fullscreen mode

Then run the happy path against the free server you named. Keep that receipt next to the rubric. Reviewers compare hosts, not vibes.

You can add a tiny assertion so two interviewers do not argue about JSON shape.

# tests/test_receipt_contract.py — reviewer side only
import json
from pathlib import Path

def test_receipt_has_host_and_reason():
    data = json.loads(Path("out/receipt.json").read_text())
    assert data["endpoint_host"]  # must be your free server's host
    assert data["exit_reason"] in {"ok", "not_a_diff"} or data["exit_reason"].startswith("unreachable")
    assert "Bearer" not in json.dumps(data)
Enter fullscreen mode Exit fullscreen mode

Run it the same way you would run a candidate packet.

python -m pytest tests/test_receipt_contract.py -q
Enter fullscreen mode Exit fullscreen mode

If that test is red, you are not looking at a take-home. You are looking at a screenshot of one.

How you score it

Write the rubric as behavior, not adjectives. “Clean code” is how you argue in a hallway. “Receipt host matches .env.example” is how you end the argument.

Gate A is contract: the only host in receipt.json is the one you provided. Gate B is hygiene: no secrets in stdout, stderr, or the receipt. Gate C is failure shape: killing the server produces exit 2 and a receipt, not a stack trace dumped on the candidate’s desktop. Gate D is the patch: if they submit a diff, it must come from that run, not from a weekend they spent on another model.

A candidate can write an ugly client and still pass A through C. That is a hire signal for this role. A candidate can write a gorgeous agent loop that never called your server. That is a no, even if the unit test goes green.

Failure modes you will actually see

Endpoint laundering shows up first. They run the real work at home, then replay a stub against your URL so the receipt looks right. You catch that when elapsed_ms is a joke, or request_id is missing, or notes.md describes a model you never offered.

Log diarrhea is faster to fail. They set debug to true and the token lands in the zip. Automatic no. Do not debate it. The take-home said not to print Authorization.

Framework gravity wastes the clock. They spend ninety minutes wiring an agent toolkit because that is what their blog diet rewards, and they never produce receipt.json. You asked for a client. They built a parade.

Silent success is the one that fools tired reviewers. The server returns an error, they catch the exception, and they write a fake diff by hand so the take-home “passes.” You catch that because you control the free server logs. If your box never saw them, they were not on your box.

That last one is the whole reason to use a free server you can actually look at. A take-home that runs only on their GPU is a magic show. A take-home that has to appear in your access log is a work sample.

Who should not use this

Skip this design if the role never talks to a model. Skip it if you cannot send candidate traffic to a third-party host, even a free one; in that case you need an internal box, and the prompt stays the same but the URL changes. Skip it for staff-level architecture interviews, where the interesting failure is organizational, not a missing receipt. Skip it if you will not read receipt.json. A rubric you do not apply is just another PDF in the zip.

Also skip it if you need this assignment to prove which model is “best.” It will not. Free model access is enough to finish the prompt. It is not a benchmark. Anyone who turns this take-home into a leaderboard is grading the wrong thing.

What you change in the README

Change one line in your current take-home: name the server. Point .env.example at a free model path and a free server you are willing to watch. Keep the sample client in the answer key, not in the candidate folder. When the packet comes back, read the receipt before you read the diff.

If you want a free model path and a free server to pin that README to, MonkeyCode is one option that fits this contract. Use it as the box you hand every candidate, then score the client they pointed at it.

Top comments (0)