Engineering notes

A Fix Nobody Checked Is Just a Change

An agent that rewrites its own failing routine also resets what "normal" means for it — so it can never learn whether it helped, and reports the routine healthy either way. The loop built instead measures every repair against how things worked before they broke.


29 August 2026·12 min read

Agents that improve themselves are a live ambition of ours — run, score, diagnose, rewrite the prompt, run again. We have just built our version of that loop, and most of the design is a series of refusals to close it the obvious way.

Here is the squeeze. An unattended job — a routine that sweeps an inbox every morning, a weekly report that assembles itself — fails in two ways, and only one of them is visible. Hard failure announces itself: an error, a red mark, a run that never finished. Drift does not. The connection to an account quietly narrows what it returns, a source goes stale, and the run keeps succeeding all the way down — thinner every week, read by nobody, because not having to read it was the point of setting it up. Do nothing and the decay is invisible for months. But the obvious remedy — detect the decline and let the agent rewrite its own instructions — turns out to make the system less trustworthy, for a structural reason we only saw clearly once we had built the detector: the repair destroys the evidence that would judge it. Every change to a job's definition resets what "normal" means for that job, so a naive self-repair loop silences its own alarm at the exact moment it acts, then re-baselines onto whatever the repaired behaviour happens to be. It cannot tell whether it helped, and it reports the routine healthy either way. Self-improvement, built carelessly, is self-congratulation with the instrument unplugged.

This entry is about the loop we built instead: detection that measures against a frozen reference, diagnosis that runs checks before it runs a model, repair as a reviewed proposal that may only ever narrow, and verification against how the routine worked before it broke. The interesting engineering is not in any one stage — it is in what each stage is forbidden to do.

Succeeding is not the same as working

Every routine and workflow run now gets read twice. The first reading is free: how long it took, how many tools it called, how many of those errored, and how much it actually produced. That last one carries more weight than it looks like it should. Run output is truncated for display — a short summary is what makes cheap scoring viable at all — but a truncated field is a useless size proxy, so the length is captured before the cap. A collapse from four thousand characters to three hundred is the single strongest signal for the failure this system exists to catch, and it is invisible if you measure after truncation. The metric you can afford to store and the metric that carries the signal are not automatically the same field.

The second reading — an LLM-as-a-judge pass — costs a model call, so it is bounded hard: a small model reads the routine's stated purpose, the run summary, and what was produced — never the transcript, which would cost around a hundred and sixty times as much and would make someone, eventually, feed it in "for accuracy." It answers two questions that are deliberately never blended. Given what this run had to work with, was it a good run? And is the routine delivering the thing it was set up to deliver? A brand-new routine whose first run asks three sensible questions and requests access is an excellent run of a routine delivering nothing yet — both facts are true at once, and any single blended score would make a legitimate cold start look broken.

Keeping the axes separate also closes the obvious con. An agent can write a glowing summary of a run that achieved nothing — the content reading is gameable in a way the mechanics reading is not, because duration and output size are measured, not narrated. So when the two disagree, the disagreement is not averaged away; it surfaces as its own flag. A confident account of a collapsed run is more informative than either number alone.

Normal is an anchor, not an average

The first design question with real consequences: what is a run compared against? A fixed rubric fails immediately — there is no such thing as a perfect run of "sweep my inbox," and nobody is going to author one per routine. Comparing against recent runs fails more subtly, and this failure is the highest-value case in the whole feature: a rolling average of a declining routine declines with it. Each run sits close enough to the recent past to pass, the baseline walks downhill alongside the decay, and a routine that loses a few per cent a week never trips anything. The reference that detects gradual decline has to be one the decline cannot drag along.

So the reference is frozen. A routine's early runs are treated as settling in and can trigger nothing at all — its page says it is still learning what normal looks like, which is simply true. Once five runs have succeeded, their statistics are frozen as that routine's anchor, and every later run is read against the anchor rather than against last week. One odd night is noise a working process produces on its own; a sustained deviation from the anchor is assignable, and worth a sentence in someone's inbox. We have a unit test that drives a slow monotonic decline through both designs — the rolling window provably never fires, the anchor does.

When you deliberately change what a routine does, the anchor re-arms: edits to the definition are fingerprinted, and a changed fingerprint sends the routine back to settling in rather than alarming against a reference that no longer applies. And "this is the new normal, stop flagging it" is a legitimate thing to mean, so a manual re-anchor is one tap on the routine's own page.

The instruction is the last suspect

Detection alone trains people to ignore it. The card our detector raises ends with an offer — would you like me to look into it, or is this the new normal? — and for a short, uncomfortable moment only the second branch was served. A card the user cannot act on is the card that teaches them the feature is noise; a detection feature's half-life is set by how actionable its output is, not by its accuracy. So the deterioration verdict now triggers a diagnosis, and the diagnosis starts from a fact the fingerprint gives us for free: a routine that reached a deterioration verdict is, by construction, one whose definition has not changed — any edit would have re-anchored it. Whatever went wrong, the instruction is the one component known to be the same as when it worked. The self-improvement reflex reaches for a prompt rewrite first. The evidence says to reach for it last.

What actually changed is almost always the world around the routine, and most of the candidates are checkable without inference. Has the credential behind a granted connection expired? Does the file the instruction names still exist, and if not, is there exactly one file with that name somewhere else now? Is the dashboard it feeds still bound to a source? Did volume move with no errors — five times the mail, none of it failing? These probes run first, and a cause they establish is authoritative: the model is never asked to overrule them. Partly that is cost and reliability — but the real argument is that a deterministic cause links to a fix. "The connection expired on Tuesday" comes with a reconnect button. A modelled cause is prose.

Only when the probes come up empty does a model read the bounded evidence, and its confidence is not decoration. Below a floor, the cause is forced to unknown, and the card says, in as many words, something changed and I could not work out why. An honest non-answer, in its own voice — never a low-confidence guess dressed in the same voice as a solid one. A wrong diagnosis delivered confidently is worse than no diagnosis, because it spends the trust the next correct one needs.

A repair may narrow. It may never widen

When diagnosis does implicate something fixable, the fix is a proposal: a stored, reviewable diff — what it says now, what it would say, what was found and why — that becomes real only when a person taps apply. Nothing about that is novel. The part worth writing down is the pair of invariants enforced where proposals are written, because this producer has an unusual threat shape: its evidence is the output of unattended runs, which is to say text an attacker can influence, and its payoff is a persistent change to an instruction that will execute forever after. Prompt injection against a self-repair loop is not a hypothetical — it is the design's natural failure mode.

First: a repair may narrow, clarify, or repoint an input — and may never touch where output goes, who receives it, what is granted, or when it runs. The asymmetry is the point. Repointing where a routine reads leaves its reach unchanged; repointing where it writes is the exfiltration shape itself. Second: when a repair repoints a path, the new value must be the resolved result of a probe — there is no code path by which model-generated text becomes a destination, and a unit test proves it, including the injection shape where a genuine probe finding is attached as cover for a destination the model chose. The model classifies and explains. It never names where anything goes.

Both invariants live at the write boundary, in a pure function with no dependencies, checked at creation and again at apply. They are indifferent to what the model read, which is what makes the later escalation safe: when bounded evidence is inconclusive, one pass may read the run's transcript — explicitly fenced as untrusted description of events — and however compromised that text might be, it can widen what the diagnosis believes, never what a proposal is permitted to do.

The fix must not destroy the evidence

Which returns us to the trap the whole design bends around. Applying a repair changes the routine's definition, and a changed definition re-anchors the control — that is the correct behaviour for a human edit. Let it happen on a repair and the sequence is: alarm fires, patch applies, anchor resets, detector goes structurally silent, and five runs later a new normal freezes around the repaired behaviour, whatever its quality. So a repair is the one kind of change that deliberately keeps the old reference point. The fingerprint moves; the frozen anchor from before the deterioration survives; and the next few runs are measured against how the routine worked before any of this started. Re-anchoring at apply time would define success as "no worse than broken."

Verification then resolves to one of three honest outcomes. Recovered — three consecutive runs back within range of the old anchor; the card closes. Regressed — still deviating at the window's end; the user gets a one-tap revert, and the same cause is suppressed from being re-proposed, because a second guess after a failed first guess is how a repair loop becomes a random walk through instruction space. Inconclusive — not enough runs inside the time cap; it says so, and never claims success by timeout.

And the loop is restorative, not ameliorative — it ends at the anchor, on purpose. "Make it better than it has ever been" has no reference to verify against: the control is deliberately insensitive to improvement, the judge reads the agent's account of its own work, and an optimiser scored by the agent's own account of itself optimises the account. Every change would reset the baseline, so an ameliorative loop is a machine for generating unfalsifiable changes forever. Genuine improvement needs a signal from outside the loop — did the human keep the output, act on it, edit it — and that is a different feature, gated behind this one earning trust.

The metric we will actually watch is the counter-metric: the share of applied repairs that verify as regressed. A rise there means the diagnosis is confident and wrong — which is worse than being unsure, and is a stop signal for proposing repairs at all, not a prompt-tuning exercise.


Postscript, 2026-08-29. This entry is written the day the phase closed, so it is the one post in the log whose verification data does not exist yet — the recovered-versus-regressed ledger starts accumulating tonight, one routine at a time. What the day did already supply is the two test-caught corrections above, and one rule we expect to keep: before letting a system change itself, decide what evidence would prove the change wrong — and make sure the change cannot delete it.

One letter a month, when there is something worth saying.

What we shipped, what we learned, and the occasional thing that did not work. No drip sequence, no launch countdowns.

Unsubscribe any time. Or take the RSS feed instead.