The scene repeats in teams that recently adopted a coding assistant. A single failing test opens the morning, the assistant proposes a patch, the patch fails, and the clock slides from nine to three without anyone deciding to spend six hours. What remains is a dirty worktree, a tired operator, and a claim like "it mostly works" that nobody can verify. A spike without a referee does not fail loudly; it simply refuses to end.
Abstract discipline is not the remedy here, because discipline is precisely what a long assistant session erodes first. The remedy is a small executable referee that owns the calendar and has veto power over the operator who wrote it. The sections below describe that referee, the one-sentence hypothesis format it enforces, and the commands used to run a ninety-minute experiment inside a disposable worktree.
One sentence, written before the clock starts
A spike becomes testable the moment its author commits to a falsifiable sentence containing a number. The format is deliberately rigid, because vagueness is what lets a session drift past its budget.
HYPOTHESIS: <change> makes <observable> true within <budget>,
measured by <command>
A usable instance reads like this: a coding assistant given the failing test and the module under test closes the failure within 25 minutes of wall clock, measured by python -m pytest tests/test_invoice_rounding.py -x -q. Two details carry the weight. First, "closes" must mean exit code zero with the assertion untouched, not a greener summary line. Second, the measurement command is copied into the ledger before the run begins, so a command edited at minute eighty is visible to anyone reading the log afterwards.
Budget the checkpoints, not the enthusiasm
The ninety minutes are split into four checkpoints, and each one gets its own allowance. Most spikes die inside attempt, which is why that checkpoint receives the largest share and the strictest gate.
| Checkpoint | Budget | What it buys |
|---|---|---|
setup |
15 min | Worktree, dependency install, first failing run |
reproduce |
25 min | Failure confirmed twice, command pinned in the ledger |
attempt |
30 min | One assistant-assisted patch, applied and re-run |
verify |
15 min | Clean re-run, ledger closed, verdict written |
The remaining five minutes exist only so that verify is never the checkpoint that spills over.
The referee script
The referee is short enough to read in one sitting, and it stores everything as JSON Lines so the ledger can be committed, diffed, and deleted along with the branch. It refuses to be helpfully permissive.
#!/usr/bin/env python3
# spike_referee.py -- a ninety-minute spike needs a clock with veto power.
import json, pathlib, sys, time
LEDGER = pathlib.Path("spike-ledger.jsonl")
BUDGET = {"setup": 15, "reproduce": 25, "attempt": 30, "verify": 15}
TOTAL = 90
def rows():
if not LEDGER.exists():
return []
return [json.loads(l) for l in LEDGER.read_text().splitlines() if l.strip()]
def write(row):
with LEDGER.open("a", encoding="utf-8") as fh:
fh.write(json.dumps(row) + "\n")
def start(name):
write({"event": "start", "checkpoint": name, "epoch": time.time()})
def stop(name, met):
begins = [r["epoch"] for r in rows()
if r["event"] == "start" and r["checkpoint"] == name]
minutes = (time.time() - begins[-1]) / 60
over = minutes > BUDGET[name]
write({"event": "stop", "checkpoint": name, "minutes": round(minutes, 1),
"allowed": BUDGET[name], "over": over, "met": met})
print(f"{name}: {minutes:.1f}m / {BUDGET[name]}m" + (" OVER" if over else ""))
if over:
print("referee: budget void, close the spike and write the verdict")
sys.exit(3)
def verdict():
data = rows()
spent = sum(r.get("minutes", 0) for r in data if r["event"] == "stop")
overs = [r["checkpoint"] for r in data if r.get("over")]
last = [r for r in data if r["event"] == "stop"][-1]
if len(overs) >= 2 or spent > TOTAL:
label = "KILL"
elif last["met"] and not overs:
label = "SHIP"
else:
label = "PARK"
print(json.dumps({"verdict": label, "elapsed": round(spent, 1), "over": overs}))
The met flag belongs to the checkpoint rather than to the hypothesis, which keeps the two questions separate. A checkpoint can be completed on time while the hypothesis remains unproven, and that distinction is what makes PARK a legitimate outcome instead of a consolation prize.
Running the spike in a worktree
The experiment happens on a throwaway branch so that a kill costs nothing beyond the ledger. The commands below are the ones exercised in the flow this article describes.
python spike_referee.py start setup
git worktree add ../spike-invoice -b spike/invoice-rounding
cd ../spike-invoice
python -m pip install -q -r requirements.txt
python -m pytest tests/test_invoice_rounding.py -x -q || true
python ../spike_referee.py stop setup yes
After the failure has been reproduced twice, the ledger holds a line that looks like the sample below. The numbers are illustrative of the format rather than a measured result.
{"event": "start", "checkpoint": "setup", "epoch": 1792237200.41}
{"event": "stop", "checkpoint": "setup", "minutes": 11.3, "allowed": 15, "over": false, "met": true}
The attempt checkpoint, and where a free tier fits
The middle checkpoint is where a paid API key turns a disposable experiment into a procurement ticket. A team that wants to test whether an assistant closes one specific failure should not need approval before the first attempt, and this is the only place where the tooling choice genuinely affects the method. The operator states that MonkeyCode provides free model access together with a free server option, and the operator-supplied figure for the free allowance is ten million tokens; quotas and hardware change over time, so confirm the current allowance on the project page rather than budgeting a month of spikes on a number quoted in an article.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Because the client differs by installation, the invocation below is a shape rather than a verbatim command. Substitute whatever the installed client actually requires, and keep the output path stable so the diff stays reviewable.
# shape only -- replace with your installed client's real invocation
$ASSISTANT_CLI patch \
--test tests/test_invoice_rounding.py \
--source src/invoice/rounding.py \
--out /tmp/candidate.patch
git apply --check /tmp/candidate.patch
git apply /tmp/candidate.patch
python -m pytest tests/test_invoice_rounding.py -x -q && MET=yes || MET=no
python ../spike_referee.py stop attempt "$MET"
A free tier changes the economics of the attempt checkpoint, not the evidence standard, and a patch that fails the measurement command still produces met: no. Latency on a shared free server can also consume the thirty-minute allowance before any patch exists, which is why a cold start belongs in setup rather than in attempt.
Reading the evidence at minute ninety
The table below is the part operators consult before touching a keyboard, because the verdict should follow the ledger rather than the operator's mood.
| Evidence in the ledger | Verdict | Next action |
|---|---|---|
| Hypothesis met, no over-budget checkpoint, patch under 40 lines | SHIP |
Open a ticket and attach the ledger |
Hypothesis met, but attempt went over |
PARK |
Re-scope into a narrower spike next week |
| Hypothesis unmet, two checkpoints over | KILL |
Write three sentences on what the assistant missed |
| Hypothesis unmet, time remaining, no new unknown opened | KILL |
Repetition is not evidence |
| Measurement command edited mid-run | KILL |
The spike lost its ruler |
Limits, and who should skip this entirely
The referee measures wall clock and checkpoint completion, not the value of what was learned, so it can end a productive session on schedule. The ledger assumes one operator on one machine, since two terminals writing to the same file produce a timeline nobody can trust, and clock skew across machines makes the elapsed totals meaningless. Teams that already run structured discovery with tickets and discovery notes will find the script redundant, and so will anyone whose work is genuinely exploratory; that activity deserves the name research, not spike.
The referee is not there to be fair, and it is not there to keep the operator company. Its job is to end the argument at minute ninety with a file that anyone can read. Anyone trying the workflow on the free tier should keep the first ledger they produce, because the checkpoint that went over usually teaches more than the verdict line does.
Top comments (0)