A frame built wrong
IMPORTANT
This step
- Teaches: the discrimination check as a check on the frame's author, and why a frame gets a new version instead of an edit.
- Frame:
10c-incident-both.json, kept as it ran, beside10c2. - After: Cumulative.
10c2 is a second version. The first, 10c, differs by its name and its control:
$ git diff --no-index examples/10c-incident-both.json \
examples/10c2-incident-both.json
diff --git a/examples/10c-incident-both.json b/examples/10c2-incident-both.json
index 19265ed..1fd822f 100644
--- a/examples/10c-incident-both.json
+++ b/examples/10c2-incident-both.json
@@ -1,5 +1,5 @@
{
- "name": "incident-both",
+ "name": "incident-both-v2",
"relation": "the answer 'database' rests on the rollback that left the latency as it was",
"locus": "the incident timeline the on-call engineer reads",
"role": "the on-call engineer for the API",
@@ -123,7 +123,10 @@
},
"control": [
"deploy",
- "failover"
+ "failover",
+ "cache",
+ "slowq",
+ "rollback"
]
},
"allowed": {10c's control cut the deploy and the failover. It kept the two metric lines, which name the database's new primary, so it left a line pointing at the database.
What Gemma answered
Every call's label on 10c-incident-both.json, trial by trial, over all the kept runs:
$ uv run python examples/read.py \
examples/10c-incident-both.json
incident-both: 3 kept runs, read by google/gemma-4-26B-A4B-it-qat-q4_0-gguf@d1c082be
trial debug fix optimize alert
baseline database 15 database 15 database 15 database 15
repeat database 15 database 15 database 15 database 15
ablation database 15 database 15 database 15 database 15
cumulative api 13, unclear 2 api 10, database 5 api 15 api 14, database 1
sham-1 database 15 database 15 database 15 database 15
sham-2 database 15 database 15 database 15 database 15
presentation database 15 database 15 database 15 database 15
control database 15 database 15 database 15 database 15
run 1: unresolved (debug unresolved, fix unresolved, optimize unresolved, alert unresolved)
run 2: unresolved (debug unresolved, fix unresolved, optimize unresolved, alert unresolved)
run 3: unresolved (debug unresolved, fix unresolved, optimize unresolved, alert unresolved)What Gadfly read
Run 1's verdict for debug on 10c-incident-both.json, in the words gadfly report uses:
$ uv run python examples/read.py \
examples/10c-incident-both.json --verdict 1 \
--input debug
debug: unresolved
levels: baseline database, repeat database, ablation database, cumulative api, sham-1 database, sham-2 database, presentation database, control database
evidence: ablation left it at database, and so did the control; the scorer cannot tell
evidence: the cumulative ablation moved it to api; carried elsewhere too
open: no hidden fallback (no bypass trial was declared)
open: discrimination (the control left it at database too)The ablation left it at database, and so did the control. A reader that never moves would give exactly these levels, so Gadfly does not call it cooled: "the scorer cannot tell", with discrimination open. The cumulative still moved, and Gadfly says so either way, since that is where else the decision may rest.
The reader was fine. The control did not cut what any reader needs, and the check that exists to catch a reader that cannot move caught the frame's author instead.
A new version, never an edit
The fix was not to change 10c's control. Every record names the hash of the whole frame it was made under, so an edited frame refuses its old records (a record belongs to another frame), and an edit made quietly after the runs would leave nothing to show that it happened. So:
- 10c stays as it ran, with its runs.
- The corrected frame is a new file, 10c2, with its own name and hash.
- The reason and the new predictions were written in
examples/PREDICTIONS.md("Why a new version") before 10c2's first call.
A reader of the examples sees both versions and which came first.
The same shape in 10b
10b's control has the same shape, found only after its runs: it cuts the deploy and the failover and keeps its anchors, the same two metric lines, so it left database on every call of every task. It did not matter there, because 10b's verdict, coupled but misaligned, rests on its ablation, which moved to api, and that reading never asks the control. Had the ablation held, Gadfly would have refused cooled as it did for 10c, so 10b stays as it ran, with this note beside it.
What to take from it
- A control is a claim about the task: cut it and any reader must move. Check that it cuts everything that points to the answer.
- When a check fails, ask first whether the frame asked the right question.
- Correct a frame with a new version and its reason, and keep the old one.
Try next
Copy 10c to a file of your own, change one word of its crossing, and report a kept run against it: uv run gadfly report my-10c.json examples/runs/10c-incident-both/1.jsonl. Gadfly refuses the records: a record belongs to another frame.