Skip to content

feat(guardian): tell the skeptic when no checker ran, and which verdict that takes (#407) - #410

Merged
zaebee merged 2 commits into
mainfrom
no-checker-appeal-407
Aug 17, 2026
Merged

feat(guardian): tell the skeptic when no checker ran, and which verdict that takes (#407)#410
zaebee merged 2 commits into
mainfrom
no-checker-appeal-407

Conversation

@zaebee

@zaebee zaebee commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Closes #407.

The defect

_evidence_section(None) returned the empty string, and the emptiness was the defect. Measuring #401 needed a control arm judging the same 24 findings with no checker output at all — and in it 6 of 24 rationales cited mypy or ruff anyway, every one to confirm a false claim, on a conditional the model could not check:

The use of Any does indeed bypass strict type checking in mypy … and if mypy --strict is configured to disallow Any explicitly, it would be flagged.

Told nothing about checkers, the skeptic supplied its own — the same unverified move the RFC faults the finder for, one level up.

Two prompts, both measured

The replay control arm is this configuration, so each attempt costs 24 skeptic calls and no finder call. Registered before running: primary — how many of the six checker-appeals stop being confirmed on that basis; recall guard — finding 4, the one substantive finding of the 24, must stay confirmed; noise floor — two draws of the original prompt moved 4 and 1 verdicts, so anything smaller is not a result.

Attempt 1: a prohibition. Failed.

Do not rest a verdict on what a checker would report — not to confirm a finding … and not to refute one …

Citations 6/24 → 5/24, five of six still confirmed. Inside the noise floor, and the text got worse — the conditional became a flat assertion:

This is an issue that would be caught by the project's mandatory mypy --strict gate.

There is no such gate over that file: make type-check runs mypy src and pyproject.toml excludes scripts. Told what not to do, the model complied in form and confirmed anyway, with more confidence than before.

Attempt 2: a directive. Worked.

If a finding rests entirely on what a checker reports … it cannot be settled here. Mark it 'uncertain'. Not 'confirmed': you have not seen a checker agree. Not 'refuted': you have not seen one disagree either.

finding original control directive
0, 2, 3, 5, 15 — claim a mypy rule the repo does not have confirmed uncertain
12 — sys.path "violates type safety" confirmed confirmed
4 — the one true finding confirmed confirmed
21, 22 — propose relaxing a ratchet confirmed refuted

Five of six moved, all one direction, all on cited-checker rows — outside the noise floor and mechanistically legible. Finding 0 now states its position instead of inventing a gate:

Without access to the project's mypy configuration or output, it is not possible to confirm if this specific usage would be flagged.

The recall guard held with the correct reasoning: CalledProcessError is raised and not caught, which is the defect actually fixed in #399.

Unregistered gain: findings 21 and 22 were refuted. Those are the two the RFC singled out as beyond any tool — wrong about intent, not behaviour. The model got there unaided: "a deliberate guardrail … its failure due to a diminished corpus is intended behavior, not a defect." One draw, not predicted; recorded as an observation.

uncertain rather than refuted because it is the true state: the checker neither agreed nor disagreed, because it never ran. Refuting an unadjudicable claim is the recall regression this component exists to avoid.

What this does not buy

uncertain is kept. apply_judgements multiplies confidence by 0.9 as a ranking signal and nothing more, and render_review_body takes threshold: int = 0. All five findings are still posted; only their order changes. This is an epistemic fix, not a noise fix — suppression is a separate knob (impact_threshold, which #246 §3.5 records as shipping inert) and deserves its own evidence rather than being folded in here.

One draw per arm; recall denominator of one. The primary effect is well outside the noise floor, nothing else here is.

Note

skeptic.py is inside the review-fingerprint closure (43 modules), so this re-mints every reviewer identity — by design (#375).

Data, including the failed first attempt, under benchmarks/guardian/experiments/407-no-checker-appeal/.

🤖 Generated with Claude Code

…ct that takes (#407)

_evidence_section(None) returned the empty string, and the emptiness was the
defect. Measuring #401 needed a control arm judging the same 24 findings with
no checker output, and in it 6 of 24 rationales cited mypy or ruff anyway —
every one to confirm a false claim, on a conditional the model could not
check: "and if mypy --strict is configured to disallow Any explicitly, it
would be flagged." Told nothing about checkers, it supplied its own.

Two prompts were measured on that arm, 24 skeptic calls each, no finder
calls.

A prohibition failed. "Do not rest a verdict on what a checker would report"
left 5 of 6 appeals standing — inside the 1-to-4 noise floor of two draws of
the original — and the language got worse: the conditional became a flat
claim about "the project's mandatory mypy --strict gate", which does not
exist over that file, since make type-check runs mypy src and pyproject
excludes scripts. Told what not to do, the model complied in form and
confirmed anyway, with more confidence than before.

A directive worked. Naming the verdict such a claim takes moved five of the
six to 'uncertain', all on cited-checker rows, and finding 0 now states its
position instead of inventing a gate: "Without access to the project's mypy
configuration or output, it is not possible to confirm." The recall guard
held — finding 4, the one substantive finding of the 24, stayed confirmed
with the correct reasoning. Unregistered: findings 21 and 22, the two the
RFC called beyond any tool because they were wrong about intent rather than
behaviour, were refuted as "a deliberate guardrail... intended behavior, not
a defect."

'uncertain' rather than 'refuted' because it is the true state: the checker
neither agreed nor disagreed, because it never ran. Refuting an unadjudicable
claim is the recall regression this component exists to avoid.

What this does not buy, stated in the data README rather than implied:
uncertain findings are kept, discounted x0.9 as a ranking signal, and
render_review_body takes threshold=0 — so all five are still posted and only
their order changes. This is an epistemic fix, not a noise fix. Suppression
is a separate knob and needs its own evidence.

skeptic.py is inside the review-fingerprint closure, so this re-mints every
reviewer identity, by design (#375).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a directive to the skeptic prompt when no checker output is available, instructing the model to mark findings that rely entirely on checker output as 'uncertain' rather than 'confirmed' or 'refuted'. This addresses an issue where the model would assume checker configurations when none were provided. Feedback on the pull request includes improving the reproducibility of the command in the README by using a repository-root-relative path, and making the test assertion for the prompt's text more robust by normalizing whitespace.

Comment thread benchmarks/guardian/experiments/407-no-checker-appeal/README.md Outdated
Comment thread tests/unit/test_guardian_skeptic.py Outdated
The reproduction command in the README did not run. It invoked the script
from the repository root while giving --recording a path relative to the
experiment directory, so the one section whose entire value is that it works
raised FileNotFoundError. Verified both ways after fixing: the root-relative
path now gets as far as the missing API key, the old one still cannot find
the file.

The prompt assertion accepted two spellings with an `or`, hedging against
wherever the line wrap happened to fall. That is a test passing because it
was given two chances rather than because it knows the answer. Whitespace is
normalised instead, and the neighbouring assertions now match whole clauses
rather than fragments that could survive a rewrite of the sentence around
them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@zaebee

zaebee commented Aug 17, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a directive instructing the skeptic model to mark findings that rely entirely on checker reports as 'uncertain' when no checker output is available. This change is documented in a new experiment README, supported by JSONL data files, and verified with updated unit tests. As there are no review comments, I have no feedback to provide.

@zaebee
zaebee merged commit 2470ddb into main Aug 17, 2026
3 checks passed
@zaebee
zaebee deleted the no-checker-appeal-407 branch August 17, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The skeptic cites mypy and ruff when no checker output was supplied (6 of 24)

1 participant