Writing code got cheap. Reviewing it did not. That is the whole problem now.
This post is a 90-minute spike. One hypothesis. One artifact. A ship-or-kill verdict on paper.
The angle comes from a topic signal I keep seeing on DEV this week: AI made coding cheaper, so why is software still expensive? My answer is narrow and testable. The cost moved. It left the editor and landed in your review queue.
The hypothesis, written before the clock starts
A spike without a pre-registered threshold is just browsing.
So write this down first, in a file, with a timestamp:
Agent patches that pass CI cost no more human review minutes than hand-written patches touching the same files.
Kill the workflow if median review minutes per accepted agent patch exceed the hand-written median by more than 50%.
Two rules keep this honest.
- Fixed sample: five tasks, chosen before you start. Not five tasks you like after.
- Fixed clock: 90 minutes total. When time is out, you write a verdict, not an extension.
Three numbers, not a dashboard
You only need three measurements per task. Anything more is procrastination with charts.
- Patch surface — files touched, lines added, lines deleted.
- Review minutes — wall clock from opening the diff to approving or rejecting it.
- Rework rounds — how many times the patch came back.
Patch surface is free to collect. Review minutes are not. Start a timer. Be boring about it.
The 90-minute budget
| Minutes | Activity | Output |
|---|---|---|
| 0–10 | Write hypothesis and kill rule | HYPOTHESIS.md |
| 10–25 | Build the ledger script | review_ledger.py |
| 25–60 | Run five fixed tasks | five patches |
| 60–80 | Review each patch, timing yourself | reviews.csv |
| 80–90 | Compute, compare to threshold, decide | VERDICT.md |
Note what is missing. No evaluation framework. No prompt library. No leaderboard.
The spike is not about which model is best. It is about whether your review loop absorbs the output at a sane price.
The artifact: one script, standard library only
Save this as review_ledger.py in the repo you are spiking. It reads git patch stats and your timed reviews, then prints a verdict object.
# review_ledger.py — stdlib only, run from the repo root
import json
import statistics
import subprocess
def sh(*args):
return subprocess.run(args, capture_output=True, text=True, check=True).stdout
def patch_stats(base, head):
out = sh("git", "diff", "--numstat", f"{base}..{head}").strip()
files = added = deleted = 0
for line in out.splitlines():
a, d, _path = line.split("\t")
files += 1
added += int(a) if a.isdigit() else 0
deleted += int(d) if d.isdigit() else 0
return {"files": files, "added": added, "deleted": deleted}
def load_reviews(path):
rows = []
with open(path) as fh:
next(fh) # header: task,kind,minutes,rework
for line in fh:
task, kind, minutes, rework = line.strip().split(",")
rows.append({
"task": task,
"kind": kind, # "agent" or "human"
"minutes": float(minutes),
"rework": int(rework),
})
return rows
def verdict(rows, max_ratio=1.5):
agent = [r["minutes"] for r in rows if r["kind"] == "agent"]
human = [r["minutes"] for r in rows if r["kind"] == "human"]
if not agent or not human:
return {"decision": "INCOMPLETE", "why": "need both kinds"}
a = statistics.median(agent)
h = statistics.median(human)
ratio = a / h if h else float("inf")
return {
"agent_median_min": a,
"human_median_min": h,
"ratio": round(ratio, 2),
"rework_agent": sum(r["rework"] for r in rows if r["kind"] == "agent"),
"decision": "SHIP" if ratio <= max_ratio else "KILL",
}
if __name__ == "__main__":
rows = load_reviews("reviews.csv")
print(json.dumps(verdict(rows), indent=2))
And a hard stop, so the clock can actually end you:
#!/usr/bin/env bash
set -euo pipefail
DEADLINE=$(( $(date +%s) + 90*60 ))
while true; do
left=$(( DEADLINE - $(date +%s) ))
[ "$left" -le 0 ] && { echo "TIME. Write VERDICT.md now."; exit 0; }
printf '\r%02d:%02d left ' $((left/60)) $((left%60))
sleep 15
done
Small note on the script: reviews.csv has four columns, and kind is what makes the comparison possible. Fill it in as you go, not from memory at minute 80.
Where a free tier and a free server fit in this spike
The spike measures your review loop, so the agent host should not be your laptop's variable. Free model access removes the "token budget" excuse, and a free server option removes the "my machine was busy" excuse. Both are exactly the kind of thing a 90-minute experiment needs: zero procurement before minute ten.
MonkeyCode is the open-source project I used for that slot in this workflow. The project advertises free model access and a free server option; at the time of writing the project page also lists a free token allowance of 10M tokens. I did not measure that quota, and terms like that change, so treat the number as project-page information and re-check it before you cite it. What the spike actually needs from the product is unglamorous: a place to run five patches, and a model that does not cost money per attempt.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Run it on the free server, keep review timing local, and the spike still measures what you care about.
Illustrative ledger output, not a measured result
The block below is a shape example, made up to show the format. Replace it with your own run.
{
"agent_median_min": 11.0,
"human_median_min": 9.5,
"ratio": 1.16,
"rework_agent": 4,
"decision": "SHIP"
}
Read the object, not the vibe. Ratio under the threshold and a rework count you can live with means the loop absorbs agent patches. Ratio over it means you are now a full-time diff reader.
The decision table
Use these rows when you write VERDICT.md. Pick one.
| Evidence | Verdict | Next action |
|---|---|---|
| Ratio ≤ 1.5, rework ≤ 1 per task | Ship narrow | Allow agent patches on low-risk files only |
| Ratio ≤ 1.5, rework high | Ship with a review cap | Cap diff at one concern per patch |
| Ratio > 1.5, patch surface small | Kill | Reviewer overhead is structural, not model-related |
| Ratio > 1.5, patch surface large | Kill and resplit | Enforce a file-count ceiling in the prompt |
| Fewer than three reviews logged | Incomplete | Do not conclude anything |
Last row is the important one. Half a spike is worse than none, because it feels like data.
Limitations, and who should skip this
Be honest about what 90 minutes cannot buy.
- Five tasks is a smoke test, not a study. Do not generalize to your whole backlog.
- Review minutes measure your attention, not the patch's correctness. Keep CI as the correctness gate.
- The ledger says nothing about licensing, security review, or generated-code provenance.
- A free tier is not a production plan. Plan the migration before the quota matters.
- Solo one-file bugfixes do not need a ledger. You already know the cost.
Skip this spike entirely if you have no test suite. Without CI, "accepted patch" has no meaning, and the ratio measures nothing. Skip it too if you cannot separate writing time from review time. Your numbers will be fiction with decimals.
Start the clock
One hypothesis, written first. Three numbers, collected five times. One script, ninety minutes, a verdict you can defend to a teammate on Monday.
If you want a low-friction place to run the agent side, the free model access and free server option on MonkeyCode are a reasonable starting slot — verify the current terms on the project page, then let the ledger, not the demo, make the call.
Top comments (0)