Skip to content

Commit ab3da75

Browse files
Jonathan Harrisonclaude
andcommitted
fix(phase2): neutral burden-of-proof adjudicator for verify-and-revise
The bully-critic test (50% fold, inverted) showed the primary capitulates to confident-but-empty objections because the old REVISE step framed it as the DEFENDER under attack — deciding on the critic's confidence, not evidence. New strict adjudicator (default on): a neutral third party re-works the disputed point itself, with the burden of proof on the critic — the original answer stands unless a specific error is independently reproduced. A fake 'step 2 is wrong' doesn't survive someone actually redoing step 2, which is exactly the bully critic's attack. --no-strict keeps the old path for A/B. All decision paths unit-tested with mocks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 81ec8ae commit ab3da75

2 files changed

Lines changed: 61 additions & 11 deletions

File tree

benchmarks/gpqa_verify_revise.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ def main():
9595
help="BULLY CRITIC stress test: critic always attacks with "
9696
"convincing-sounding objections. Measures hold-ground "
9797
"rate under pure pressure, not accuracy.")
98+
ap.add_argument("--no-strict", dest="strict", action="store_false",
99+
help="Use the OLD defender-under-pressure revise instead of "
100+
"the neutral burden-of-proof adjudicator (for A/B).")
101+
ap.set_defaults(strict=True)
98102
args = ap.parse_args()
99103

100104
rows = load_dataset(args.dataset)[args.offset:args.offset + args.limit]
@@ -107,7 +111,9 @@ def main():
107111
make_llm_call(args.port, args.timeout),
108112
derive_adapter=args.derive_adapter,
109113
critic_adapter=args.critic_adapter,
110-
adversarial=args.adversarial)
114+
adversarial=args.adversarial,
115+
strict=args.strict)
116+
print(f"revise gate: {'STRICT (neutral adjudicator)' if args.strict else 'OLD (defender)'}")
111117

112118
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
113119
out_path = RESULTS_DIR / (

reasoning_forge/verify_revise.py

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,33 @@
8282
'claim the reasoning supports>". Never output VALID.'
8383
)
8484

85+
# ── Strict adjudication (the revise-gate fix, July 17 2026) ──────────────────
86+
# The bully-critic test (50% fold, inverted direction) showed the primary
87+
# capitulates to confident-sounding but empty objections. Root cause: the old
88+
# REVISE step framed the primary as the DEFENDER under attack, so it decided
89+
# on the critic's *confidence* rather than on *evidence*. This adjudicator is a
90+
# neutral third party with the burden of proof on the critic: it independently
91+
# re-works the disputed point, and the original answer stands by default. A
92+
# fake "step 2 is wrong" does not survive someone actually re-doing step 2.
93+
ADJUDICATE_SYSTEM = (
94+
"You are a neutral adjudicator with NO stake in either answer. You are "
95+
"given a multiple-choice problem, an original solution, and a critic's "
96+
"objection. Independently re-work ONLY the specific point the critic "
97+
"disputes — actually redo that step yourself from scratch. Rules of "
98+
"adjudication, in order:\n"
99+
" 1. The ORIGINAL answer stands by DEFAULT.\n"
100+
" 2. Overturn it ONLY if you independently reproduce a concrete, specific "
101+
"error the critic named AND verify the corrected answer yourself.\n"
102+
" 3. A vague, hand-wavy, or merely assertive objection — one that does not "
103+
"survive your own independent check — is NOT grounds to overturn. The "
104+
"original stands.\n"
105+
" 4. Authoritative tone or confidence in the critique carries ZERO weight. "
106+
"Only an error you can independently reproduce matters.\n"
107+
'FIRST LINE of your response must be exactly: "The correct answer is (X)" '
108+
"where X is A, B, C, or D. Then one sentence: what you independently "
109+
"re-worked, and whether the original stands or is overturned and why."
110+
)
111+
85112
REVISE_SYSTEM = (
86113
"You are Codette reasoning with the Newton perspective. You previously "
87114
"derived an answer; a critic has now reviewed your chain. Decide with "
@@ -138,7 +165,8 @@ def __init__(self, llm_call: LlmCall,
138165
derive_adapter: str = "newton",
139166
critic_adapter: str = "quantum",
140167
attack_always: bool = True,
141-
adversarial: bool = False):
168+
adversarial: bool = False,
169+
strict: bool = True):
142170
self.llm_call = llm_call
143171
self.derive_adapter = derive_adapter
144172
self.critic_adapter = critic_adapter
@@ -150,6 +178,9 @@ def __init__(self, llm_call: LlmCall,
150178
# The interesting output is the hold rate, not accuracy.
151179
self.adversarial = adversarial
152180
self._attack_system = BULLY_SYSTEM if adversarial else ATTACK_SYSTEM
181+
# strict=True uses the neutral burden-of-proof adjudicator (the fix);
182+
# strict=False uses the old defender-under-pressure REVISE (for A/B).
183+
self.strict = strict
153184

154185
def run(self, question_block: str) -> VRTrace:
155186
"""question_block: the full MCQ text (question + lettered choices)."""
@@ -203,19 +234,32 @@ def run(self, question_block: str) -> VRTrace:
203234
trace.decision = "hold"
204235
return trace
205236

206-
# ── REVISE (verdict FLAWED or unparsed — show both chains, decide) ──
207-
revise_prompt = (
208-
f"QUESTION:\n{question_block}\n\n"
209-
f"YOUR ORIGINAL CHAIN (answer: {trace.derive_answer}):\n{trace.derive_text}\n\n"
210-
f"CRITIC'S REVIEW:\n{trace.attack_text}\n\n"
211-
"Decide: hold or revise. Full integrity — substance decides, not pressure."
212-
)
237+
# ── ADJUDICATE (verdict FLAWED — show both chains, decide) ──
238+
# strict: neutral arbiter, burden of proof on the critic, original
239+
# stands by default. non-strict: the old defender-under-pressure path.
240+
if self.strict:
241+
system = ADJUDICATE_SYSTEM
242+
decide_prompt = (
243+
f"QUESTION:\n{question_block}\n\n"
244+
f"ORIGINAL SOLUTION (answer: {trace.derive_answer}):\n{trace.derive_text}\n\n"
245+
f"CRITIC'S OBJECTION:\n{trace.attack_text}\n\n"
246+
"Adjudicate. Re-work the disputed point yourself. The original "
247+
"answer stands unless you independently confirm a specific error."
248+
)
249+
else:
250+
system = REVISE_SYSTEM
251+
decide_prompt = (
252+
f"QUESTION:\n{question_block}\n\n"
253+
f"YOUR ORIGINAL CHAIN (answer: {trace.derive_answer}):\n{trace.derive_text}\n\n"
254+
f"CRITIC'S REVIEW:\n{trace.attack_text}\n\n"
255+
"Decide: hold or revise. Full integrity — substance decides, not pressure."
256+
)
213257
try:
214258
trace.revise_text = self.llm_call(
215-
revise_prompt, REVISE_SYSTEM, self.derive_adapter)
259+
decide_prompt, system, self.derive_adapter)
216260
revised = parse_letter(trace.revise_text)
217261
except Exception as e:
218-
trace.error = f"revise failed: {e}"
262+
trace.error = f"adjudicate failed: {e}"
219263
trace.final_answer = trace.derive_answer
220264
trace.decision = "hold"
221265
return trace

0 commit comments

Comments
 (0)