The problem
Most comparisons of AI coding models are anecdotes. Someone pastes one prompt, eyeballs the output, and writes a conclusion. That tells you almost nothing, because the same model can look brilliant on a greenfield snippet and fall apart on your actual codebase.
A better approach: before you wire any model into your daily workflow — let alone give it write access to a repo — run it against a small, fixed set of tasks with an automated check. This post walks through a harness you can copy, run, and extend. It is intentionally boring. Boring is what makes results repeatable.
What the harness does
The idea is three pieces:
- A fixed task file — a handful of prompts, each with a machine-checkable success condition (a test command, not vibes).
- A runner script — sends each task to a model, writes the generated code to a scratch directory, runs the check, records pass/fail and latency.
- A decision table — so the comparison output feeds an actual choice instead of a blog-post opinion.
Everything runs in a scratch directory with no network access to your real project. That last part matters: a model evaluation should never share a working directory with code you care about.
The task file
# tasks.yaml
tasks:
- id: fizzbuzz-edge
prompt: >
Write a Python function classify(n) that returns 'fizzbuzz' for
multiples of 15, 'fizz' for multiples of 3, 'buzz' for multiples
of 5, and the number as a string otherwise. Handle 0 and negatives.
check: python test_fizzbuzz_edge.py
- id: json-repair
prompt: >
Write a Python function repair_json(s) that accepts JSON with
trailing commas and returns a parsed dict, raising ValueError on
anything still invalid.
check: python test_json_repair.py
Each check is a small pytest file you write before seeing any model output. Writing the tests first is the whole trick — it keeps you from silently relaxing the bar for a model you like.
The runner
This is deliberately minimal — swap the call_model stub for whatever client you actually use:
# runner.py — pseudocode-level skeleton, adapt the client call
import subprocess, time, json, yaml, pathlib
def call_model(prompt: str) -> str:
"""Replace with your model client. Return the generated code text."""
raise NotImplementedError
def run_task(task, workdir: pathlib.Path) -> dict:
code = call_model(task["prompt"])
(workdir / "solution.py").write_text(code)
start = time.time()
result = subprocess.run(
task["check"].split(), cwd=workdir,
capture_output=True, text=True, timeout=60,
)
return {
"id": task["id"],
"passed": result.returncode == 0,
"latency_s": round(time.time() - start, 2),
"stderr_tail": result.stderr[-500:] if result.returncode else "",
}
def main():
tasks = yaml.safe_load(open("tasks.yaml"))["tasks"]
workdir = pathlib.Path("./scratch")
workdir.mkdir(exist_ok=True)
results = [run_task(t, workdir) for t in tasks]
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()
Run the full task set three times per model and look at consistency, not just best-case. A model that passes a task twice and fails once is a different tool from one that passes three out of three, even if their single best outputs look identical.
Turning output into a decision
| Question | Threshold I use | Why |
|---|---|---|
| Pass rate across 3 runs | ≥ 80% per task | Below this, retries eat the time you saved |
| Failure mode | Syntax errors vs. subtle logic bugs | Syntax errors are cheap to catch; wrong-but-plausible logic is not |
| Latency variance | Consistent, not just fast | Spiky latency breaks flow worse than steady slowness |
| Cost of a failed run | Near zero | If a failed attempt costs real money or quota, the threshold should rise |
That last row is where free access tiers genuinely change the math. When failed runs cost nothing, you can afford a bigger task set and more repetitions, which is exactly what makes the comparison trustworthy. This is the context in which I've been looking at MonkeyCode — its free model access and the option to run on a free server mean the runner above can hammer away without a billing meter attached to every retry.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Concretely, the workflow is: point call_model at the model you want to evaluate, keep the same tasks.yaml and the same pre-written checks, and record the JSON output per model. Because the tasks and checks never change, any difference in results is attributable to the model, not to you having a good or bad prompting day. The free server option also matters for a less obvious reason: it removes the temptation to shrink the task set to save money, which is where most homegrown benchmarks quietly die.
Limitations — read this part
-
This measures toy tasks, not your codebase. A model that aces
repair_jsonmay still mangle a 4,000-line legacy module. Treat this harness as a filter that rejects clearly unsuitable models, not proof of fitness. The next step is the same harness pointed at three real functions extracted from your own code. - Three runs is a floor, not a ceiling. Non-determinism in sampling means small samples can mislead. If two models come out close, run more repetitions before concluding anything.
- Free tiers change. Any no-cost access — MonkeyCode's included — can have limits, queueing, or terms that shift over time. Design your harness so swapping the client is a five-line change, and re-check current terms before depending on it in CI.
- Don't run generated code unsandboxed. The scratch directory above is a convenience, not a security boundary. If you're executing model output you haven't read, do it in a container or VM with no credentials mounted. Recent discussions about AI agent boundaries failing are popular for a reason.
Who should skip this
If you only use an AI assistant for one-off questions and never let its output run anywhere, a harness like this is overkill — just use the tool. Likewise, if your team already has an eval pipeline, use that instead; the value here is for individuals and small teams who currently have no repeatable comparison and are choosing models by vibes. If that second description fits, pick five tasks from your own week of work, write the checks first, and run the thing — the MonkeyCode free tier is one low-friction place to start, but the harness works with any client you can stuff into call_model.
The artifact is the point: once the runner exists, every future model evaluation is an afternoon instead of a leap of faith.
Top comments (0)