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 cauterulegives 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
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
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_ruleneeds no new labeling onfailures/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_ruleis dropped at parse (Trajectoryhas 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
preventedfrom "text matched" into "outcome changed"? - Is
expected_ruledense enough to backfill ontogolden(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 release notes
- v0.3.0 field test report
- Corpus format spec (
expected_rule, domain labels) - PRD — success metrics
- PRD — architecture
- User guide · Changelog
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)
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.
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?
"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.triggerof "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 throughsimulate()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
doby 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 surfacedogets 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.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.
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:
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.
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.