DEV Community

Alex Zhu
Alex Zhu

Posted on

Run a 20-Minute Load Interview Before You Put an Agent on a Free Server

Free compute is easy to accept and hard to operate. The failure mode is rarely an outage; it is a slow drift nobody measured, followed by an argument in a standup where both sides are guessing. This playbook turns that argument into a twenty-minute measurement you can paste into your wiki, with roles, thresholds, and a script that refuses to sign off on vibes.

Why a load interview instead of a quick smoke test

A smoke test answers "did it respond?". A load interview answers "what shape does it respond in, and which of our jobs does that shape support?". You want cold latency, warm latency, how latency moves as concurrency rises, and the error rate at each step, because a free tier that is fine at one request can be unusable at eight.

The project in this write-up is MonkeyCode, an open-source coding agent project whose operator lists a free model tier and a free server option. Both availability claims are operator-supplied and I have not independently audited quotas, hardware, or duration, so treat every number you need as something your own harness must produce.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The interview does not tell you whether the product is good. It tells you which of your jobs are allowed to run there this week, which is a decision your team can actually defend.

Roles: three seats and no shared memory

  1. Driver runs the harness and owns the terminal. The driver does not interpret results while the run is in progress.
  2. Reader holds the thresholds and signs pass or fail. The reader must be someone who did not write the harness, because authors grade generously.
  3. Recorder writes the wiki page, attaches the raw JSON, and records the commit hash of the harness so next month's run is comparable.

Three seats is deliberate. One person running, reading, and recording will quietly skip the third step, and the third step is the one that survives staff turnover.

The artifact: a stdlib-only load interview

The script below uses only the Python standard library, so it runs anywhere your CI runner exists. Save it as load_interview.py and point it at your own deployment.

#!/usr/bin/env python3
"""load_interview.py - cold/warm latency and error rate for one HTTP endpoint.
Stdlib only. Replace the URL and payload with your own; nothing here is product-specific."""
import argparse, json, statistics, time, urllib.request, urllib.error
from concurrent.futures import ThreadPoolExecutor


def one_call(url, payload, timeout):
    req = urllib.request.Request(
        url, data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},
    )
    t0 = time.perf_counter()
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            r.read()
            code = r.status
    except urllib.error.HTTPError as e:
        code = e.code
    except Exception:
        code = 0  # timeout, DNS, connection reset: all count as errors here
    return time.perf_counter() - t0, code


def summarize(samples, label):
    lat = sorted(s for s, _ in samples)
    errs = sum(1 for _, c in samples if c < 200 or c >= 300)
    pick = lambda q: lat[min(len(lat) - 1, int(q * len(lat)))]
    return {
        "label": label, "n": len(lat),
        "p50_s": round(statistics.median(lat), 3),
        "p95_s": round(pick(0.95), 3),
        "max_s": round(lat[-1], 3),
        "error_rate": round(errs / len(lat), 3),
    }


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--url", required=True)
    ap.add_argument("--prompt", default="Summarize this diff in one sentence.")
    ap.add_argument("--levels", default="1,4,8", help="concurrency steps")
    ap.add_argument("--warm", type=int, default=10, help="calls per level")
    ap.add_argument("--idle", type=int, default=120, help="idle seconds before cold probe")
    ap.add_argument("--timeout", type=float, default=60.0)
    ap.add_argument("--out", default="interview.json")
    args = ap.parse_args()

    payload = {"prompt": args.prompt}
    print(f"idling {args.idle}s for a cold probe...")
    time.sleep(args.idle)
    cold_lat, cold_code = one_call(args.url, payload, args.timeout)
    results = [{"label": "cold", "n": 1, "p50_s": round(cold_lat, 3),
                "p95_s": round(cold_lat, 3), "max_s": round(cold_lat, 3),
                "error_rate": 0.0 if 200 <= cold_code < 300 else 1.0}]

    for level in [int(x) for x in args.levels.split(",")]:
        with ThreadPoolExecutor(max_workers=level) as pool:
            samples = list(pool.map(
                lambda _: one_call(args.url, payload, args.timeout), range(args.warm)))
        results.append(summarize(samples, f"warm_c{level}"))
        print(results[-1])

    json.dump({"url": args.url, "levels": args.levels, "results": results},
              open(args.out, "w"), indent=2)
    print(f"wrote {args.out}")


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

A run looks like this, and you supply the numbers:

python3 load_interview.py \
  --url http://YOUR-HOST/v1/chat \
  --levels 1,4,8 --warm 10 --idle 120 \
  --out interview-2026-10-18.json
Enter fullscreen mode Exit fullscreen mode

The gate: a threshold file your Reader actually signs

Keep thresholds in a file so the Reader signs a diff, not a memory. The gate exits nonzero on any breach, which makes it usable in CI and in a wiki run.

{
  "warm_c1":  {"p95_s": 20, "error_rate": 0.0},
  "warm_c4":  {"p95_s": 35, "error_rate": 0.05},
  "warm_c8":  {"p95_s": 60, "error_rate": 0.10},
  "cold_max_s": 90
}
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env python3
"""gate.py - exit 1 if the interview breaches the signed thresholds."""
import json, sys

interview = json.load(open(sys.argv[1]))
limits = json.load(open(sys.argv[2]))
breaches = []

for row in interview["results"]:
    if row["label"] == "cold":
        if row["max_s"] > limits.get("cold_max_s", 10**9):
            breaches.append(f"cold {row['max_s']}s")
        continue
    cap = limits.get(row["label"], {})
    if row["p95_s"] > cap.get("p95_s", 10**9):
        breaches.append(f"{row['label']} p95 {row['p95_s']}s")
    if row["error_rate"] > cap.get("error_rate", 1.0):
        breaches.append(f"{row['label']} errors {row['error_rate']}")

print("BREACH:", "; ".join(breaches) if breaches else "none")
sys.exit(1 if breaches else 0)
Enter fullscreen mode Exit fullscreen mode
python3 gate.py interview-2026-10-18.json gate.json && echo SIGNED
Enter fullscreen mode Exit fullscreen mode

Steps: the twenty-minute run, in order

  1. Pick one job, not the whole agent. Choose the smallest recurring task, such as "one-sentence diff summary" or "triage a failing test name". Interviews of vague workloads produce vague thresholds.
  2. Freeze the payload. Paste the exact request body into the wiki page so two runs a month apart are comparable.
  3. Idle, then probe cold. Two minutes of silence before the first call is enough to expose cold-start behaviour on most shared hosts.
  4. Run the concurrency ladder. One, four, eight is a reasonable start; go higher only if your real queue does.
  5. Gate, then decide. A pass means the job may run there this week. A breach means the job goes back to the paid path until someone changes a threshold on purpose.
  6. Record the decision, not the feeling. The Recorder pastes the gate output and the JSON filename into the wiki table below.

The one-page wiki run you can paste

# Free-Server Load Interview - <job name>
Date: <YYYY-MM-DD>  Harness commit: <sha>  Interview JSON: <filename>
Driver: <name>  Reader: <name>  Recorder: <name>

Payload (frozen): <request body>
Thresholds: gate.json @ <sha>
Gate output: <paste>
Decision: PASS / BREACH -> jobs allowed on free path this week: <list>
Budget note: token allowance and server terms verified on <date> from <source URL>
Review date: <YYYY-MM-DD + 30>
Enter fullscreen mode Exit fullscreen mode

That budget line matters because free allowances are a moving target. If your operator lists a token allowance such as ten million tokens, write the number, the date you read it, and the URL you read it from in that line, then re-check it at the review date. Never let a remembered number become a planning assumption.

Decision table: what the numbers earn

Interview result Jobs allowed on the free path Jobs kept elsewhere
Warm p95 under your interactive budget, errors zero Interactive helpers, single-file summaries Anything on a customer deadline
Warm p95 fine at c1, degrading at c4 Batch jobs with a queue and retries Chat features, IDE autocomplete
Cold probe near your timeout Nothing interactive Nightly jobs with a warm-up call first
Error rate above the signed cap Nothing Everything, until re-measured

Limitations and who should skip this

This harness measures one endpoint on one day from one network location. Your numbers do not transfer to a colleague's laptop, and mine should not be quoted as a product benchmark, because they are not one. Shared free capacity can also change without notice, which is exactly why the review date exists.

Do not run this against endpoints you do not control or have permission to load; a concurrency ladder is still a load test. Skip this approach entirely if you carry a hard external latency SLO, if your data cannot leave your own infrastructure, or if nobody will own the Reader seat for more than one sprint.

Close the loop

A free server becomes dependable the moment your team can state, in one line, which jobs are allowed on it and what evidence produced that line. Run the interview, gate it, paste the page, and let the next person inherit a measurement instead of a rumour. If you want a starting point for the agent side of that run, MonkeyCode's free model tier and free server option are worth evaluating with exactly this harness before you promise anything to your team.

Top comments (0)