Zero drafts at 07:00: build the gate, not the prompt

The 29 June cron fired at 07:00 UTC. Triage ran. Ground stage ran. One cluster survived to claim-extraction. Haiku processed it. Zero drafts. No exception. No flag. The pipeline logged a clean exit and stopped.

The day’s two publishes — the 60% tax trap pillar and the Mortgage Guarantee Scheme 2025 — came from the operator path. Sourced, structured, published by hand. The cron had produced nothing.

That was the problem in miniature.

The silence is the problem

An LLM stage that returns fewer results than expected has two possible interpretations. Either the input genuinely didn’t warrant output — the model examined the source material and correctly found nothing to extract. Or the model underfired: the task was tractable but the call returned less than it should have.

The 29 June cluster had cleared ground stage because it had enough signal. It was the only cluster to make the cut. Haiku worked through claim-extraction and returned fewer assertions than the drafting stage needed. The pipeline had nothing to draft against, exited cleanly, and logged a success.

B-076 was the underlying defect. Still open on the 29th.

The architectural problem is that a pipeline stage which exits cleanly after underfiring is indistinguishable from one that correctly determined there was nothing to extract. Both produce the same downstream state: zero drafts. Both log the same exit condition. The difference is only recoverable if you know what the input was and what a correct run on that input should have returned.

This failure mode isn’t specific to that cluster or that cron. Any pipeline stage that calls a model and accepts a variable-length return has it available. The model doesn’t error when it underproduces. It returns fewer items. If the receiving stage accepts the shortfall without checking, the run completes.

Why I didn’t reach for the prompt first

The standard response to an LLM stage returning less than expected is to revise the prompt. More explicit instructions, added examples, tighter framing. This is the right response when the model is misunderstanding the task — producing the wrong shape of output, confusing entity types, extracting assertions that don’t fit the schema.

The 29 June case wasn’t a comprehension failure. Haiku understood claim-extraction. The shape of its output was correct whenever it produced anything. The problem was count: a situational underfire driven by that cluster’s source density, not a misunderstanding of what a claim is.

Tuning the prompt to be more insistent doesn’t address count. What it does is modify extraction behaviour across every cluster, including the ones that were returning the right number of claims. The adjustment is unattributable: you can’t isolate its effect on the underfire case from its effect on the cases you didn’t intend to touch.

A corrective stage operates differently. It fires only when the count check fails. It applies targeted parameters — constrained source window, directive framing — to the specific case that fell short. The rest of the pipeline runs unchanged.

The corrective stage

The gate sits between claim-extraction and drafting. It checks the claim count against a threshold relative to the cluster’s source volume. If the count is below threshold, the stage reruns extraction with narrower parameters aimed at the underfire case: reduced source window, more constrained call.

If the rerun also falls short, the run is marked failed with a structured event: cluster ID, source count, expected claim count, actual claim count, timestamp. The cluster stays in the queue. The run doesn’t complete.

That last part is what changes the operational picture. A completed-with-zero-output run looks clean in a log scan. A failed run at claim-extraction is an event — visible, queryable, attributable. The 29 June run, under the corrective stage, would have been a claim-extraction failure with a structured record, not a clean exit with no output.

Five days

The subsequent runs make the pattern visible.

30 June. Cron at 07:00 UTC produced one draft: mortgage-abandoned-about-above (the slug was a product of the title-mangling bug, still mid-fix at the time). The draft had 10 of 13 sentences passing verification. Rescued with manual edits and published. The Marriage Allowance pillar was a fresh operator-path publish alongside it.

1 July. Cron at 08:21 BST produced one draft on the same mortgage rate-spike cluster from the previous day — same BBC, Zoopla, Moneyfacts, and BoE ground, same US-Iran framing, £232/£66 regional figures. Already published. Draft discarded. The Personal Savings Allowance and HICBC pillars were both fresh gov.uk single-source operator publishes.

2 July. Cron at 08:22 BST ran triage across 150 clusters. Top candidate by calculator score: mortgage at rank 20. Candidates fetched. Ground ran. Claim-extraction underfired again. Zero drafts — the second zero-drafts firing in the current streak. CGT annual exemption and Dividend Allowance were both fresh operator publishes.

3 July. ISA allowance and student loan repayment thresholds published before the morning cron, drafted overnight in the same session as other work. The ISA allowance pillar ran to 754 words with 15 hand-curated claim blocks drawn from three gov.uk pages.

Across those five days: the cron produced one rescued partial draft and two clean zero-draft exits. The operator path produced six pillars.

Why the six published

The corrective stage doesn’t generate articles. What it generates is actionable failure events.

A zero-draft run that exits cleanly tells you the pipeline ran and completed. It doesn’t tell you where the shortfall occurred or what was in the queue when it stopped. A failed run at claim-extraction with a structured event tells you the cluster had enough signal to clear ground, extraction fell short by count, and the cluster is still in the queue waiting for another pass.

That information is what makes operator intervention tractable. The ISA allowance pillar — 15 hand-curated claim blocks across three gov.uk pages, 754 words — is what structured operator extraction looks like when the model failure is legible. The pathway from “cron produced zero drafts” to a published pillar is shorter when you know which stage failed and why.

Six pillars across three days isn’t a cron output. It’s an operator working through a backlog the corrective stage made visible rather than leaving invisible.

What this doesn’t fix

B-076 is still open. The corrective stage is a structural response to the class of failure B-076 represents — it makes underfires recoverable rather than silent. It doesn’t reduce their frequency. That’s the defect fix’s job.

Both are worth doing. The corrective stage ships first because it generalises: any extraction stage that accepts a variable-length model return has this failure mode, and the same gate pattern addresses all of them. The defect fix reduces how often the gate fires. They compound.

Out of scope from this sprint: the title-mangling bug that produced mortgage-abandoned-about-above, cadence rules for the operator path, and any changes to ground-stage cluster selection. Separate tickets.

The full run log is in docs/devlog.md in the UK Calculators repository, dated 2026-06-29 through 2026-07-03.

All writing →