DEV Community

Morgan Zhou
Morgan Zhou

Posted on

Hand Them a Lease, Not a Key

You open the folder on a Sunday night and the take-home falls apart in the first minute. One README says the agent only works on a personal laptop, because the model key sits in a shell profile you were never meant to see. Another candidate attached a cloud invoice and a note that they stopped when the bill spiked. You cannot rerun either submission, so you cannot tell whether the code is any good.

That is a wallet signal, not an engineering signal. The person who already pays for a model API and a spare server gets to show a demo. The person who does not gets to show a story, and you are about to grade the story as if it were the work.

Calling a model got easier to try. The assignment around it did not change. A take-home still assumes a personal key, a personal host, and a reviewer who will trust a paragraph. If you hire people to build agent-shaped tools, test the fence around the tool, not the credit card that paid for the demo.

Write the assignment as a lease, then bind four artifacts to it. You send the prompt, a rubric a second reviewer can run, a sample solution you would accept, and the failure modes that sample is built to catch. A free server option gives every candidate the same empty host. Free model access gives them a call path that does not belong in the repo. You still write the cap yourself. A banner is not a rubric.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is one place you can look when you want that shared empty host, because this draft was prepared against an operator note that the project currently offers free model access and a free server option. This draft does not name a model, a token quota, a machine size, or a duration. Those details move, and an article is a bad place to freeze them. Before you send the packet, open the project and copy only the limits you can see that week. If the page and this article disagree, the page wins.

Treat the lease like a hotel key. It opens one room. It stops working in the morning. Housekeeping has to prove the room is empty before the next guest arrives. You would not hand someone the master key to the building and call that a fair test.

The prompt can be short. Save it as PROMPT.md so nobody can claim they solved a different task than the one you graded.

# Take-home: leave a working fence, then leave

You have a throwaway server and model access that is not yours to keep.
Build a tiny command, `fence`, that reads `lease.txt`, performs one
bounded task, and writes `trace.jsonl` plus `teardown.log`.

The task: given `inbox/*.txt`, write `out/summary.json` with a count of
files and the longest line. You may call a model to draft one summary
sentence. You may not exceed `max_model_calls`. You may not read secrets,
reach past the host you were given, or leave a process behind.

When you finish, the server must look unused. `teardown.log` must contain
the line `LEASE_EMPTY` after you stop your processes and remove your files
from the shared host. Commit the command, the trace, and the commands you
ran. Do not commit a key.
Enter fullscreen mode Exit fullscreen mode

That prompt is a proposal you can edit. The file count is boring on purpose. You are not hiring a summarizer. You are hiring someone who can finish a small job inside a room you can revoke.

Keep lease.txt plain so a reviewer can read it without installing a parser.

lease_id: th-2026-10-13-demo
host: throwaway-free-server
model_access: shared-free-pool
max_model_calls: 12
max_wall_minutes: 90
secrets: none
teardown_token: LEASE_EMPTY
Enter fullscreen mode Exit fullscreen mode

Twelve calls is a number chosen for this exercise, not a product limit. Lower it if the pool you actually have is tighter. Raise the wall clock only if you will honor it. A lease you cannot expire is a shared laptop with extra paperwork.

The sample solution is a fence, not a clever agent. It is unexecuted: the script below is a review harness, it does not call a model, and it has not been run against a live host in this draft. That dryness is useful. A reviewer should be able to grade the fence without spending the pool you reserved for candidates.

#!/usr/bin/env python3
"""fence.py — unexecuted sample. Records a dry run inside the lease."""
import json
import sys
from pathlib import Path

def read_lease(path: Path) -> dict:
    data = {}
    for raw in path.read_text().splitlines():
        line = raw.strip()
        if not line or line.startswith("#") or ":" not in line:
            continue
        key, value = line.split(":", 1)
        data[key.strip()] = value.strip()
    return data

def main() -> int:
    lease = read_lease(Path("lease.txt"))
    cap = int(lease["max_model_calls"])
    inbox = sorted(Path("inbox").glob("*.txt"))
    longest = 0
    for path in inbox:
        for line in path.read_text(errors="replace").splitlines():
            longest = max(longest, len(line))
    trace = [{"kind": "model_call", "n": 0, "why": "deterministic summary"}]
    Path("trace.jsonl").write_text("".join(json.dumps(e) + "\n" for e in trace))
    Path("out").mkdir(exist_ok=True)
    summary = {
        "files": len(inbox),
        "longest_line": longest,
        "calls": 0,
        "cap": cap,
    }
    Path("out/summary.json").write_text(json.dumps(summary, indent=2) + "\n")
    Path("teardown.log").write_text(
        "stopped:none\nremoved:workspace\nLEASE_EMPTY\n"
    )
    print(json.dumps({"pass": True, "calls": 0, "cap": cap}))
    return 0

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

A rubric that lives in your head will drift between reviewers. Ship a checker. This one is also a proposal, meant to be run from the candidate folder after you recreate inbox/ yourself.

#!/usr/bin/env python3
"""rubric.py — unexecuted. Exit 0 only when the lease still holds."""
import json
import sys
from pathlib import Path

def lease_map(path: Path) -> dict:
    out = {}
    for raw in path.read_text().splitlines():
        line = raw.strip()
        if line and not line.startswith("#") and ":" in line:
            key, value = line.split(":", 1)
            out[key.strip()] = value.strip()
    return out

def main() -> int:
    lease = lease_map(Path("lease.txt"))
    cap = int(lease["max_model_calls"])
    token = lease["teardown_token"]
    reasons = []
    trace_path = Path("trace.jsonl")
    calls = None
    if not trace_path.exists():
        reasons.append("no_trace")
    else:
        rows = [json.loads(x) for x in trace_path.read_text().splitlines() if x.strip()]
        calls = sum(int(r.get("n", 1)) for r in rows if r.get("kind") == "model_call")
        if calls > cap:
            reasons.append(f"over_cap:{calls}>{cap}")
        blob = json.dumps(rows)
        if "sk-" in blob or any(r.get("secret") for r in rows):
            reasons.append("secret_in_trace")
    teardown = Path("teardown.log").read_text() if Path("teardown.log").exists() else ""
    if token not in teardown:
        reasons.append("lease_not_empty")
    if not Path("out/summary.json").exists():
        reasons.append("no_summary")
    report = {"calls": calls, "cap": cap, "pass": not reasons, "reasons": reasons}
    print(json.dumps(report))
    return 0 if not reasons else 1

if __name__ == "__main__":
    sys.exit(main())
Enter fullscreen mode Exit fullscreen mode
mkdir -p inbox out
printf 'alpha\nbeta-line\n' > inbox/a.txt
printf 'short\n' > inbox/b.txt
python3 fence.py
python3 rubric.py
Enter fullscreen mode Exit fullscreen mode

You should see a pass with calls at 0 and cap at 12. If you do not, the harness is wrong before any candidate is. Fix the checker first. A rubric that fails the sample is not a standard. It is a mood.

Walk the failures the way you walk a flaky build. Name the break. Then name what you will not forgive.

The first failure looks like effort. The trace holds forty model calls against a cap of twelve, each one a retry of the same prompt with a louder instruction. The summary happens to be fine. The lease is not.

You are looking at someone who will sit in a loop until a quota page says no. On a shared free pool, that loop is not persistence. It is how the next candidate finds an empty cup.

The second failure looks like taste. The repo is neat, the summary matches, and teardown.log is absent. On a host the next person will inherit, neat is not empty. A leftover tmux session, a file in /tmp, or a key in shell history is the same bug wearing different clothes.

Fail closed on that one. A missing turnover beats a pretty diff. The next guest should not have to discover your process list.

The third failure used to pass, which is why it belongs in the prompt. The candidate did the work at home, pasted a transcript, and wrote that the shared server was inconvenient. Under the old reading, that was initiative. Under this one, it is an unreproducible claim.

You are not grading their home network. You are grading whether a stranger can run the same fence on the host you handed out. A screenshot is a souvenir, not a rerun.

The fourth failure is the quiet one. The trace is under the cap, the token is present, and the summary is confidently wrong. A model call agreed with a bad count because the candidate fed it the number they hoped to see.

Do not let a fluent sentence outrank inbox/. Recount the files yourself. Fail the submission when the JSON disagrees with that recount.

python3 - <<'PY'
import json
from pathlib import Path
files = list(Path("inbox").glob("*.txt"))
longest = 0
for path in files:
    for line in path.read_text().splitlines():
        longest = max(longest, len(line))
claimed = json.loads(Path("out/summary.json").read_text())
ok = claimed.get("files") == len(files) and claimed.get("longest_line") == longest
print(json.dumps({"files": len(files), "longest": longest, "matches": ok}))
PY
Enter fullscreen mode Exit fullscreen mode

Keep the product out of the requirement. Tell candidates they may use any free or local model that fits the cap, and that they must write the name they actually called into the trace. MonkeyCode's free model access and free server option are a convenient way to make the starting line the same for a weekend dry run. They are not a requirement that the solution import a specific SDK. If another host still proves teardown, grade the proof.

Skip this shape when the job needs private customer data, a named commercial model, or a machine you cannot wipe the same day. Skip it when the free server is really a staging box with live credentials on disk. A lease works only if the room is empty at check-in and empty at checkout.

Skip it for a multi-day project you will not re-lease. Do not paste a token quota you have not re-read into the invitation. Free access moves. A rubric that quotes last month's banner will fail honest people for keeping a promise you no longer run.

Stand the current free server option up once, apply whatever free model access the project lists today, and try to break your own checker before you send anything. Fix it until a messy retry, a leftover process, and a made-up file count all fail in writing. Then send the lease. Grade the fence. Let the key expire.

Top comments (0)