DEV Community

Cover image for The Model Wrote the Right Rule and My Replay Rejected It: The Extraction-vs-Replay Split
Debashish Ghosal
Debashish Ghosal

Posted on AI-assisted

The Model Wrote the Right Rule and My Replay Rejected It: The Extraction-vs-Replay Split

Update — v0.3.0 released. CauterRule is now live on GitHub and PyPI. It turns repeated agent failures into permanent standing rules — extract, replay-test, promote. pip install cauterule gives you the full CLI, framework adapters, rule lifecycle, pack ecosystem, and official rule packs. The v0.3.0 field test report evaluated 2 cloud models across 40 corpora and 4,768 trajectory-runs and is the source for every number below. Release notes · Changelog


CauterRule is an open-source sidecar that learns standing rules from repeated agent failures. It extracts lessons from trajectories, replay-tests them, and tries to separate reusable guidance from noisy overgeneralization.

For two releases we treated "the pass rate is low" as one problem and reached for the extractor. It turned out to be two problems we had fused into a single number. This is the split, and it changed where we point next.

The assumption: one metric, two questions

The pipeline has two halves that answer two different questions:

Extraction: given a failure, does the model produce the right rule?
Replay / evaluation: given a rule, can we verify it against history?

We measured only the second, and we read its score as a verdict on the first. The replay gate that decides promotion computes:

precision = prevented / (prevented + broken)
recall    = prevented / total_failures
Enter fullscreen mode Exit fullscreen mode

where prevented and broken come from simulate(), which calls rule_matches() — a text matcher. So prevented means "the trigger's prose reached a similarity threshold against a reference failure's prose," and broken means "…against a reference success's prose." Nothing in that path asks whether applying the rule's directive would have changed the trajectory's outcome. The gate is a lexical resemblance check wearing a validation costume.

The F-001 example: the rule was right, the grader said no

In the failures/positive corpus, trajectory F-001 is the canonical git case:

expected_rule (ground truth) "when git push fails with non-fast-forward, pull latest changes before pushing"
extracted when "when git push fails with non-fast-forward"
extracted do "pull latest changes before pushing"

The model reproduced the reference rule almost verbatim. Extraction did its job. Then replay scored it:

failures_prevented = 5   successes_broken = 3   near_misses = 1
precision = 0.625        recall = 0.625         verdict = INCONCLUSIVE
Enter fullscreen mode Exit fullscreen mode

Three "broken" successes were S-022-git-pull, S-023-git-status, and NM-044-git-commit-hook — a successful git status counted as broken by a git-push rule, because the two share the token git. A correct rule was demoted to inconclusive by three unrelated successes it merely rhymes with. The model wrote the right rule. The grader rejected it on the prose.

The assumption we made (and where it broke)

Assumption: the replay verdict tells us whether the extracted rule is good.

It tells us whether the extracted trigger's surface form resembles stored reference surface forms. Those are different claims. A correct rule phrased differently scores low; a wrong rule that happens to share vocabulary scores high. The metric is a text-similarity proxy, and it is the gate.

The telling part: the undercount is not unique to replay. The corpus carries a ground-truth rule (expected_rule) on 23/50 failures/positive trajectories and 288 reference-expansion trajectories — a ready-made extraction-accuracy instrument we were never scoring against. When we do score it naively (token-F1 of the extracted rule vs expected_rule), we get only ~0.58 (llama-3.1-8b) / ~0.50 (gpt-4o-mini). Not because the model is wrong — F-001 is near-verbatim — but because it rewords, and a token comparator can't see through rewording. The same paraphrase gap that breaks replay also undercounts extraction. We had one brittle text-matching lens in two places.

The data

Cloud models, failures/positive (23 trajectories carry expected_rule):

Metric gpt-4o-mini llama-3.1-8b What it measures
Replay pass rate 8% 10% trigger prose vs reference prose
Extraction token-F1 vs expected_rule 0.50 0.58 extracted rule vs ground truth (text)
F-001 extracted vs expected_rule near-verbatim near-verbatim a correct rule
F-001 replay verdict inconclusive inconclusive the grader, not the rule

The two left-column numbers look bad and point at the model. The right two columns say the model was fine and the measurements are the weak link. The spread is the whole story: extraction is substantively right but lexically variable, and every metric we ship is lexical.

What worked

  • Naming the two halves separately stopped the misattribution. "Low pass rate" is a symptom; "extraction accuracy" and "replay fidelity" are the two variables. Once separated, the model stops taking the blame for the grader.
  • The ground truth was already in the corpus. expected_rule needs no new labeling on failures/positive — it was being parsed out and dropped. Picking it up is the cheapest high-signal metric we have.
  • One worked example did more than any average. F-001 — right rule, inconclusive verdict, three token-sharing successes — is the argument in one line. Averages hid it; the single case exposed the mechanism.

What didn't work

  • We had a ground-truth rule and never scored against it. expected_rule is dropped at parse (Trajectory has no such field), so the only quality number was the replay verdict. We graded the homework with the wrong rubric for a full release.
  • The replay metric masquerades as validation. "Tested against history before promotion" is the product's core promise, but the test is lexical resemblance. A reader who believes the promise and inspects the gate finds prose matching.
  • Token-F1 undercounts extraction too. Reaching only ~0.5–0.6 against known-good rules, a token-F1 extraction score would also wrongly suggest the model is mediocre. The comparator needs a semantic or signature signal, or it repeats the replay's error on the extraction side.
  • Both metrics share one brittle primitive. Because replay and the naive extraction check both lean on the same token matcher, fixing one without the other leaves the split half-measured.

Questions we still can't answer

  • Should replay mean "does this rule prevent this class of failure" (behavioral) or "does it match this history" (lexical)? We have been shipping the second and calling it the first.
  • Can we validate by applying the directive to the reference trajectory and checking the outcome flips — turning prevented from "text matched" into "outcome changed"?
  • Is expected_rule dense enough to backfill onto golden (currently null) so extraction quality has a clean per-corpus number everywhere?
  • At what point does a rule that is right-but-reworded deserve credit, and who decides — a threshold, an embedding floor, or a human?

What I learned

A pass rate is a composite; decompose it before you optimize it. "8–10% pass" fused extraction and evaluation. The moment we split the number, the model exonerated itself and the grader took the hit.

Ground truth you don't score against is a decoration. The corpus already knew the answer (expected_rule). Not wiring it into a metric meant we optimized the wrong half while the right half went unmeasured.

If your "validation" only reads words, it can't validate meaning. The replay gate grades whether the trigger's prose rhymes with stored prose. That is a retrieval property, not a correctness property — and it is the gate.

One crisp failure case beats a dozen averages. F-001 is the entire diagnosis. When a metric makes a known-good rule fail, the metric is the bug, full stop.

The broader lesson

If you build a loop that produces an artifact and then scores it, keep the two measurements separate and keep them honest. A score that grades surface form instead of behavior will make good outputs look bad and bad outputs that share vocabulary look fine — in both halves of the loop at once.

CauterRule spent two releases tuning an extractor that was already writing the right rule, because the number it was chasing was really a prose-similarity score. The fix is not a better model. It is (a) scoring extraction against the expected_rule we already have, with a comparator that can see past rewording, and (b) making the replay gate check whether the directive changes the outcome, not whether the trigger rhymes. Same model. Same corpus. Two honest metrics instead of one flattering lie.

References


CauterRule v0.3.0 is released. The replay and extraction data are in the field test report. The repo is public. Install with pip install cauterule. Changelog · Release notes

Top comments (7)

Collapse
 
reidmarlow profile image
Reid Marlow

The F-001 breakdown hits the nail on the head. Using lexical resemblance as a proxy for behavioural prevention always penalizes semantic variance while giving false passes to shared keywords like git.

To turn replay from prose-matching into outcome-testing, we ended up running the candidate rule through a synthetic mutation pass: inject the extracted directive into the prompt or tool harness of the historical failure trajectory, re-execute the isolated sub-step in a sandbox, and assert whether the terminal exit code flips from 1 to 0. It costs real compute compared to string comparisons, but it completely eliminates the paraphrase gap.

Collapse
 
debashish_ghosal profile image
Debashish Ghosal

Thanks, Reid — that's the right direction, and it's where we landed too: simulate_outcome() now grounds the directive against the trajectory's actual state instead of matching prose. Your version is the stronger form — re-executing the sub-step in a sandbox and asserting the exit code flips 1→0 is the real behavioral test. We haven't gone that far because of cost/flakiness, so ours is more of a static grounding check. Curious how you handle non-deterministic steps — do you retry, or pin the sandbox to a deterministic fixture?

Collapse
 
vinhnguyenthanhdn profile image
Vinh Nguyen

"Nothing in that path asks whether applying the rule's directive would have changed the outcome" is measurable as a negative control, and I ran it against 0.3.0 off PyPI rather than reasoning about it. Four candidate rules, identical when.trigger of "when git push fails with non-fast-forward", only the directive varying: "pull latest changes before pushing", "force push with --force to overwrite the remote branch", "delete the remote branch and recreate it from scratch", and "water the office plants". Same three trajectories through simulate() each time.

All four returned ['prevented', 'broken', 'no_effect'] and precision 0.5. Not close - identical, including the one that is actively destructive advice for that failure and the one that is not about software.

So the gate is not merely a weak proxy for rule quality, it has zero discriminating power over do by construction, and a promotion pipeline built on it would promote the force-push rule on exactly the evidence that promotes the correct one. Worth having that arm in the suite as a standing test, because it is the kind of thing that stays true silently after a matcher refactor.

One thing I noticed while building it: RuleDo.__post_init__ raises on a blank directive, so non-blankness is the entire validation surface do gets anywhere in the path. And my boundary - these were three synthetic trajectories I wrote, not your corpus, so my absolute counts are not your 5/3/1. What transfers is the invariance across directives, not the numbers.

Collapse
 
debashish_ghosal profile image
Debashish Ghosal

This is the best kind of comment — you ran it instead of arguing about it. That invariance is the whole finding, and you were right that it would stay true silently after a matcher refactor, so we turned it into a standing test: tests/replay/test_directive_invariance.py (#762), same trigger and directive vectors including "water the office plants". The text verdict is intentionally still directive-invariant; what changed is a separate Phase-1 simulate_outcome signal that does separate correct from destructive/nonsense. And yes — RuleDo.post_init only checks non-blankness, which is exactly the validation surface you'd expect to be a gap. Respecting your boundary: it's the invariance that transfers, not your absolute counts. Thanks for the rigor.

Collapse
 
bayu911 profile image
bayu priatno

This is a really important distinction, especially for agent systems.

What stood out to me is that the failure wasn't actually in extraction — it was in the boundary between producing a rule and establishing that the rule is correct.

F-001 is a great example: the model produced the correct rule, but the replay mechanism rejected it because lexical similarity was being used as a proxy for behavioral correctness. In other words, the system was measuring whether the rule looked related to the history, not whether applying the rule would actually prevent the failure.

This maps closely to a problem I'm exploring with NAEOS:

Model output → claim
Evaluation → verification attempt
Observed outcome → evidence

Those shouldn't collapse into one metric.

I especially like your question about whether replay should mean “does this rule match this history?” or “does this rule actually change the outcome?” I think the second is the more interesting test for agent governance.

A rule that matches the vocabulary of a failure is not necessarily a useful rule. The stronger test is counterfactual or behavioral:

If this rule had been enforced at the relevant decision point, would the trajectory have produced a different outcome?

That also suggests a useful separation between extraction accuracy, replay fidelity, and behavioral effectiveness rather than trying to make one score represent all three.

And there's an interesting NAEOS connection here: independent verification should not simply ask whether the agent's reasoning is plausible. It should establish evidence that the expected constraint or outcome actually held.

Really good example of how a validation layer can look rigorous while still measuring the wrong thing.

Collapse
 
debashish_ghosal profile image
Debashish Ghosal

Thanks, bayu — "the system was measuring whether the rule looked related to the history, not whether applying it would prevent the failure" is the article in one sentence. Your three-layer separation is exactly what we ended up instrumenting: extraction accuracy (right rule?), replay fidelity (does it match history?), and behavioral effectiveness (would it have changed the outcome?). Collapsing them into one score is what hid the F-001 bug for two releases. The NAEOS framing of output→claim / eval→verification / outcome→evidence maps onto that cleanly — thanks for putting words to it.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.