Make the developer's own wrong account one of the options - #23
Merged
Conversation
On `explained_it_back` — and only there — stage 3 now has to build one distractor from the mechanism as the developer stated it. Every other signal leaves it guessing at what a half-understanding looks like, from the taxonomy of wrongness in the prompt. Rank 0 already has one in the transcript, wrong, and the design puts the whole burden of multiple choice on the distractors: an option someone actually believed is not a guess at that. Paraphrased, never quoted — grask does not show a developer their own mistake, which is why the hypothesis is internal — and required to be false. Rank 0 covers accounts that were incomplete as well as wrong, and an incomplete account is often true as far as it goes, which is the probe stage 4 discards for having two true options. Those are sharpened into the claim they would imply if taken as the whole mechanism rather than restated. Prompt-only, and not a fifth structural gate: the four gates are mechanically decidable, and "is this option their stated mechanism" is a semantic judgement that could only be checked with a model call in the capture path. Triage now records every rejected moment rather than only the rejections that emptied a session. `demoted_from_ask` says a session lost *all* of its moments; it names no gate and says nothing about a session that kept one and rejected another, which is most of them. `triage_run` reports surviving moments by signal — every ranked signal, including those at zero, since a signal absent from a report reads as "not measured" rather than "never fired" — beside a tally of rejections by reason with the `turn N:` prefix stripped. Rare signal and over-strict gate were indistinguishable before and need opposite fixes. The corpus holds zero `explained_it_back` seeds, so whether rank 0 fires at all is the prior question, and this is the instrument for it. Also repairs the end-to-end test, broken twice over and invisibly, since it is deselected by default: the autouse `no_real_model_calls` guard that arrived with stage 4 refused the `calibration` run too, and underneath it the test asserted `probes.criteria` was non-empty — a NOT NULL column left from the judge design that `add_probe` writes `[]` into on every row. It now asserts the answer key that replaced criteria. A real run passes at $0.73. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A paired A/B over the three real rank-0 seeds — one seed each, stage 3 forked twice, the frame text the only difference — recovered the misconception 3 of 3 with the block and 2 of 3 without it, the third a near-miss rather than nothing. At n=3 that detects nothing. The design doc claimed the rule was doing work; it now says the effect was not observed and the block is kept on cost, which is the verdict the distractor-shape block already carries on the same evidence budget. Records what the same run found upstream: rank 0 fires on 4 of 38 kept sessions, threading `shows` is unnecessary because stage 2 restates an omission as a falsifiable belief, and every gate rejection in 168 sessions was rank 0 on a quote that asks rather than explains. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ery demotion a bad quote Two review findings, both wording that asserted something untrue. The frame told stage 3 the developer's account was "in the quotes below". Nothing guarantees that: `seed.verified_quotes` keeps any claimed quote that appears in some developer turn, and stage 2 chooses 1-3 freely — the selected moment's quote need not be among them. On a seed where it chose others, the instruction pointed at nothing. It now points at the rendered session, which always has the account, because that is where triage found it. No threading of `shows`; only the wording was wrong. The report called `demoted_from_ask` sessions "demoted for a bad quote". That flag means every moment was rejected, by any of five gates, and only one of them is about a bad quote. It misdescribed real data on the first full-corpus run: all three demotions were the `explained_it_back` question gate, reported as bad quotes. In a report built to diagnose why rank 0 is scarce, that is the kind of mislabel that sends you at the wrong fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…riage_run's spend Three findings from the corpus run, plus the spend gate it should have had. A question labelled `explained_it_back` is now demoted to `asked_why`. That gate was the only one firing across 168 sessions — 3 of 7 rank-0 proposals — and each rejection emptied its session, because those sessions had no other moment. A question is exactly what `asked_why`'s own gate requires, so the evidence was there and only the label was wrong; dropping threw away a probe rank 1 would have kept. Demoted and never promoted, since rank 0 is the claim the quote failed to support. An empty `shows` stays a rejection: no weaker signal accepts it. `Moment.relabelled_from` keeps the mislabel rate visible rather than folding it into rank 1. `triage_run` refused to dry-run. `capture_run` and `reprobe` both gate on `--go` and this one is the most expensive of the three — one call per session, across the corpus, billed on invocation. Stage 1 is told that pasted prose is not the developer's account. A rank-0 moment died at a stage-2 decline on a session whose turn 0 was 2,173 characters of pasted agent report against turns of 3-34 characters elsewhere: verbatim in a developer turn, so the evidence rule passed, but not something the developer believed. No structural fix exists — Claude Code inlines pasted text with no marker, and a length heuristic would fire on anyone who writes long prompts — so it is prompt-only and recorded as unmeasured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
design.md had started absorbing PR bodies: the method of each A/B, the branch that was built and reverted, the bug that motivated a gate. Those can never disagree with the code, so they accumulate instead of being corrected — the doc grew 1015 → 1230 lines in four commits. Where a measurement is why a claim is weak, the number stays and the run goes. Deletes the third copy of the rank-0 mislabel and pasted-prose findings, which each already read once under Structural gates. States the rule in the header so the next change has something to be held to. No claim about the current system is dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
On
explained_it_back— and only there — stage 3 now has to build one distractor from the mechanism as the developer stated it.Every other signal leaves stage 3 guessing at what a half-understanding looks like, working from the taxonomy of wrongness in the prompt. Rank 0 already has one in the transcript, stated by the developer and known to be wrong. The design puts the whole burden of multiple choice on the distractors — "the dangerous failure is a fluent answer describing a different mechanism, and the distractors are the only place left to catch it" — and an option someone actually believed is not a guess at that.
Two constraints keep it from backfiring:
Prompt-only, and deliberately not a fifth structural gate: the four gates are all mechanically decidable, and "is this option the developer's stated mechanism" is a semantic judgement that could only be checked with a model call in the capture path — a judge by another name.
Why not thread
showsthroughMoment.showsnames the wrong part and is the strictest-gated field in triage (triage.py:319rejects a rank-0 moment without it), but it reaches nothing downstream — it is not in stage 2's prompt, not onSeed, not in theseedstable. Threading it end-to-end was the alternative, and it is deferred: stage 3 already receives the developer's verified quotes and the full dialogue, so the misconception is in front of it either way. If rank 0 fires and the distractor still comes out invented rather than recovered, that is the evidence for threadingshows— and not before.Measuring it
The corpus holds zero
explained_it_backseeds:The signal shipped in #22, after every stored seed was captured, so "inspect the next N rank-0 probes" currently has N=0. The prior question is whether rank 0 fires at all — and the old report could not answer it, because a rare signal and an over-strictly gated one looked identical. Those need opposite fixes.
So triage now records every rejected moment, not only the rejections that emptied a session.
demoted_from_askanswers "did this session lose all of its moments"; it names no gate and says nothing about a session that kept one moment and threw another away — which is most of them. Those rejections were computed and dropped.triage_runreports:turn N:prefix stripped, or one gate firing on four turns reads as four unrelated one-offs.Run it over the whole corpus, not
--limit N: that flag takes the newest sessions, which measures the deployment window rather than the signal.Repairs the end-to-end test
test_capture_smokeis the only test that proves the four stages compose rather than each working alone, and it had been failing since stage 4 landed — broken twice over, invisibly, because calibration is deselected by default:no_real_model_callsguard that arrived with stage 4 refused thecalibrationrun too, so the one test meant to spend money could not.probes.criteriawas non-empty — a NOT NULL column left from the judge design thatadd_probewrites[]into on every row (storage.py:847, andtest_storage.py:181asserts exactly that). No probe has satisfied it since multiple choice replaced the judge.It now asserts the answer key that replaced criteria: option count, no duplicates, an index naming a real option, a non-empty explanation.
Verification
478 passedon 3.12 and on the 3.8 floor; ruff and mypy clean; coverage 87.5% (gate 84%).ask→ seed → probe → verify, valid key, $0.73.Version bumped
0.1.0-rc7→0.1.0-rc8inpyproject.tomland.claude-plugin/plugin.json.🤖 Generated with Claude Code