https://github.com/sattyamjjain/proofloop
Universal quality evaluation plugin for Claude Code — 7-dimension scoring (correctness, completeness, adherence, efficiency, safety), configurable rubrics, threshold blocking, auto-hooks & /judge command.
https://github.com/sattyamjjain/proofloop
ai-agents ai-evaluation claude-code code-quality developer-tools evaluation llm-as-judge plugin python quality rubric scoring
Last synced: 7 days ago
JSON representation
Universal quality evaluation plugin for Claude Code — 7-dimension scoring (correctness, completeness, adherence, efficiency, safety), configurable rubrics, threshold blocking, auto-hooks & /judge command.
- Host: GitHub
- URL: https://github.com/sattyamjjain/proofloop
- Owner: sattyamjjain
- License: mit
- Created: 2026-02-13T16:48:51.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-06-21T14:38:43.000Z (20 days ago)
- Last Synced: 2026-06-21T15:26:41.987Z (20 days ago)
- Topics: ai-agents, ai-evaluation, claude-code, code-quality, developer-tools, evaluation, llm-as-judge, plugin, python, quality, rubric, scoring
- Language: Python
- Homepage: https://github.com/sattyamjjain/verdict#readme
- Size: 852 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
- Roadmap: ROADMAP_2026.md
Awesome Lists containing this project
README
# Proofloop
[](https://github.com/sattyamjjain/proofloop/actions/workflows/ci.yml)
[](https://github.com/sattyamjjain/proofloop/releases/latest)
[](LICENSE)
**Auto-grade every Claude Code and Cowork skill execution on seven
dimensions. No LLM call. No config. Just a scorecard.**
Every other evaluation tool in this space (Braintrust, Langfuse,
Phoenix, Helicone, Promptfoo, DeepEval, Ragas, LangSmith, Opik)
requires a second LLM to grade the first. Proofloop runs offline regex
heuristics inside the editor. Zero ongoing cost, zero API key, zero
network call — it ships as a Claude Code plugin that fires on every
`Stop` and `SubagentStop` event.
---
## See it
Every execution gets an evidence-based scorecard — rendered in your
terminal in milliseconds, with no API call and no account:
```text
╔════════════════════════════════════════════════════════════════════════════╗
║ PROOFLOOP SCORECARD -- feature-dev ║
║ Grade: A- (Very Good) | Composite: 8.75/10.0 | 2026-06-13T18:05:22Z ║
╠════════════════════════════════════════════════════════════════════════════╣
║ Correctness ██████████ 10.0/10 (w=0.25) → No errors; verification… ║
║ Completeness █████████░ 9.0/10 (w=0.20) → Short transcript — may … ║
║ Adherence ████████░░ 8.0/10 (w=0.15) → Rubric available (compl… ║
║ Actionability ████████░░ 8.0/10 (w=0.15) → Output appears ready to… ║
║ Efficiency ████████░░ 8.0/10 (w=0.10) → Reasonable tool usage (… ║
║ Safety ██████████ 10.0/10 (w=0.10) → No safety concerns dete… ║
║ Consistency █████░░░░░ 5.0/10 (w=0.05) → No prior history for co… ║
╠════════════════════════════════════════════════════════════════════════════╣
║ Summary: Good Feature Dev (A-) -- strong correctness, consistency could i… ║
║ ║
║ RECOMMENDATIONS: ║
║ * Compare with prior executions and maintain quality baselines ║
╚════════════════════════════════════════════════════════════════════════════╝
```
That `pytest` receipt in the run is what earns correctness **10/10**:
without an executed check *anywhere* in the transcript, the top mark is
withheld — **no pass without proof.** It is no rubber stamp either —
consistency is held at `5` until there's a history to compare against.
---
## Install
```shell
/plugin marketplace add sattyamjjain/proofloop
/plugin install proofloop@proofloop
```
That's it. The next skill or subagent execution that matches an
`always` entry in `judge-config.json` is scored automatically; the
scorecard JSON lands in `skills/judge/scores/` and `/scorecard`
renders the trend line.
Works on Claude Code and Claude Cowork. For the Cowork plugin-loading
workaround (GH #39400), see [INSTALL-COWORK.md](INSTALL-COWORK.md).
---
## Why Proofloop
| | Proofloop | Braintrust / Langfuse / Phoenix | Promptfoo / DeepEval | LangSmith / Opik |
| -------------------------- | :-----: | :-----------------------------: | :------------------: | :--------------: |
| Runs inside Claude Code | ✓ | ✗ | ✗ | ✗ |
| Offline (no LLM call) | ✓ | ✗ | optional | ✗ |
| Zero config for first score| ✓ | ✗ | ✗ | ✗ |
| Per-domain rubrics | ✓ (11) | via code | via YAML | via code |
| Cross-ecosystem transcripts| ✓ | traces only | provider-flex | LangChain-first |
| Pip install / deps | stdlib | SDK + network | SDK | SDK + account |
| Per-rubric weight override | ✓ | ✗ | ✗ | ✗ |
| Git-diff-aware delta | ✓ | ✗ | ✗ | limited |
---
## Features
- **Dual-mode operation.** Automatic via hooks (`Stop`,
`SubagentStop`, `StopFailure`) or manual via `/judge`.
- **Dual-platform.** Claude Code and Claude Cowork.
- **7-dimension weighted scoring.** Correctness, completeness,
adherence, actionability, efficiency, safety, consistency — weights
configurable globally or per rubric via a sidecar `.weights.json`.
- **Plugin-scope adapters.** Score transcripts from Claude Code,
Cowork, Codex, Cursor, Continue, and any OpenAI-compatible JSON
shape with a single `--adapter` flag.
- **11 domain rubrics.** code-review, security, devops, data-analysis,
frontend-design, testing, documentation, content-writing, research,
default, custom-template. Per-rubric weight overrides via a sidecar
`.weights.json`.
- **Model-aware efficiency.** Opus 4.7's new tokenizer (~35% more
tokens) doesn't silently penalise its longer outputs — length
thresholds scale by a per-model baseline.
- **Diff-aware delta.** `/judge --against HEAD~1` renders a side-by-
side delta and exits non-zero on composite regression so CI can gate
on it.
- **Local HTML dashboard.** `scripts/studio.py` emits a single-file
HTML report with radar charts + trend lines — no server, no build
step.
- **Benchmark regression gate.** Curated transcript corpus plus
`scripts/benchmark_pack.py` asserts no heuristic drift across
releases. Wired into CI.
- **Benchmark hygiene lint (ABA-anchored).** `scripts/bench_lint.py`
audits the regression-gate manifest itself for the four issue
classes described in [arXiv:2605.26079](https://arxiv.org/abs/2605.26079)
(Wang et al. 2026, *Automated Benchmark Auditing for AI Agents
and Large Language Models*). Surfaces a `bench_hygiene_score`,
text/JSON/SARIF output, and a `--lint` pre-flight gate on the
pack so a suspect corpus is caught before its scores ship. See
[§Benchmark hygiene lint](#benchmark-hygiene-lint-aba-anchored)
below.
- **Same-family judge guard.** When the opt-in LLM second opinion is
enabled, Proofloop checks whether the judge model shares a vendor
family with the model that produced the transcript. A match sets
`self_preference_risk: true` and warns on the console (Claude judging
Claude inflates scores); a configured cross-family judge is
auto-preferred. See [§Same-family judge guard](#same-family-judge-guard)
below.
- **Sycophancy signal.** Offline heuristic that flags when the
assistant abandons a correct answer under user pushback ("are you
sure? I think it's X") by capitulating without fresh reasoning — a
sycophantic flip docks the composite, while a *correct* concession
backed by re-derivation is not penalised. No LLM call. See
[§Sycophancy signal](#sycophancy-signal) below.
- **Stdlib only.** Python 3.9+, no third-party packages, installs
instantly with zero supply-chain risk.
> **v1.x → v2.0.0 migration.** v2.0.0 trimmed Proofloop to its plugin
> scope per the 2026-05-03 v4.3 reset: 16 frontier-lab eval-bench
> rubrics, 6 cross-ecosystem adapters, and 7 bench-eval scripts were
> removed. If you depended on `swe-bench-pro`, `terminal-bench`,
> `clinical-agentic-workflow`, `eu-ai-act-audit-trail`,
> `tool-output-rewrite`, etc., pin to `v1.4.2` or fork. See
> [`CHANGELOG.md`](CHANGELOG.md) §[2.0.0] and
> [`CLAUDE.md`](CLAUDE.md) §v4.3 Scope Contract.
---
## Quick start
### Automatic mode
Skills on the `auto_judge.always` allowlist are scored without user
intervention. The `Stop` hook emits:
```
Proofloop: code-review → 8.7/10 (A-). Solid execution with minor areas for improvement.
```
### Manual mode
```
/judge # Score the last execution
/judge --rubric security # Force a specific rubric
/judge --adapter codex --model claude-opus-4-7
/judge --against HEAD~1 # Delta vs. previous run
/scorecard # Trend view across runs
/benchmark code-review # Delta vs. reference standards
/judge-config # View / update allowlist + threshold
```
### Cross-ecosystem
```shell
# Codex CLI session
python3 skills/judge/scripts/score.py \
--skill code-review \
--transcript ~/.codex/sessions/latest.json \
--rubric-dir skills/judge/rubrics \
--scores-dir skills/judge/scores \
--adapter codex
# OpenAI-compatible (Cursor / Continue)
python3 skills/judge/scripts/score.py --adapter openai-compatible ...
```
### Studio dashboard
```shell
python3 skills/judge/scripts/studio.py \
--scores-dir skills/judge/scores \
--output proofloop-studio.html
open proofloop-studio.html
```
### Explain a scorecard for a PR comment
```shell
python3 skills/judge/scripts/explain.py \
--scorecard skills/judge/scores/code-review_2026-04-25.json \
--format md \
--out /tmp/pr-comment.md
```
`--format json` emits the same data under a stable
`format_version: "explain.v1"` schema. See
[`SKILL-judge-explain.md`](skills/judge/SKILL-judge-explain.md).
---
## Gate your CI on it
Proofloop ships as a GitHub Action, so the same offline scorer can fail a
pull request when an agent's work drops below your bar — no API key, no
account:
```yaml
- uses: sattyamjjain/proofloop@v3.1.0
with:
transcript: ./agent-run.jsonl # the agent/skill transcript to score
skill: code-review # selects the rubric
threshold: "7.0" # fail the job below 7.0; omit to report only
# adapter: codex # claude_code | codex | openai_compatible | cowork
```
It exposes `composite` and `grade` as step outputs and prints a one-line
summary to the job log. The repo dogfoods this pattern on its own pull
requests via [`.github/workflows/self-score.yml`](.github/workflows/self-score.yml).
---
## Scoring system
7 weighted dimensions summing to 1.0. Weights live in
`judge-config.json.scoring.dimensions` and can be overridden per
rubric via a `.weights.json` sidecar.
| Dimension | Default | Signal |
| --------------- | :-----: | ------------------------------------------------ |
| Correctness | 0.25 | Error / hallucination patterns, **unverified-success claims** |
| Completeness | 0.20 | TODO/FIXME/HACK density (docstring-scoped) |
| Adherence | 0.15 | Deviation keywords vs. rubric criteria |
| Actionability | 0.15 | Code fences + file actions − placeholders |
| Efficiency | 0.10 | Tool-call density, retries, length (model-aware) |
| Safety | 0.10 | Destructive commands, exposed creds, **least-privilege over-scope** (context-aware) |
| Consistency | 0.05 | Variance vs. historical scores |
Grades: A+ ≥ 9.5, A ≥ 9.0, A− ≥ 8.5, B+ ≥ 8.0, B ≥ 7.5, B− ≥ 7.0,
C+ ≥ 6.5, C ≥ 6.0, C− ≥ 5.5, D ≥ 4.0, F otherwise.
**Least-privilege sub-check (under Safety).** The safety dimension also
scores generated agent code for least-privilege tool/skill scoping —
the CVE-class root cause behind omnibus free-form tools and over-scoped
MCP servers. Offline and heuristic (no LLM), it flags a **wildcard
(`*`/all) grant**, a **write/delete/admin scope** beyond read-only use,
and an **omnibus free-form tool** that dispatches arbitrary
command/code input at runtime (the single most common pattern). Each
finding docks safety and surfaces the offending tool plus a one-line
remediation in the scorecard's top-level `least_privilege` array and
the safety justification. It is a sub-check, **not** an 8th dimension —
the 7-dimension contract is preserved. (Detecting the *absence* of a
declaration is left to a manifest validator, since inferring it from a
flat transcript false-positives on ordinary tool-use logs.)
**Unverified-success / cheap-tier reward-hacking (under Correctness).**
The cheapest, most reliable fabricated-success tell is a trajectory
that *claims* a check passed — "all tests pass", "build succeeded",
"verified working" — but carries no **receipt**: no evidence a check
was actually executed (a runner invocation, a test count, an exit
code). Claiming a pass without running it is fabricated success, a
correctness/honesty failure, so `detect_unverified_success` docks the
**correctness** dimension and adds a red flag — the same dual treatment
Proofloop already gives a hallucinated fact. The offending claim and a
one-line remediation surface in a top-level `unverified_success` array.
Configurable via `judge-config.json.unverified_success`
(`enabled` / `correctness_dock` / `red_flag`).
The tiering is deliberate: this **cheap heuristic runs on every
trajectory**; it makes no embedding-probe call and no frontier-judge
call. Escalation to a model judge is the *separate*, opt-in
`llm_second_opinion` tier you sample (off by default) — never a
mandatory tier here. It is a correctness signal, **not** a new
`reward_hacking` dimension (the 7-dimension contract holds; a
trajectory-grading reward-hacking *benchmark* remains out of scope per
the v4.3 reset). Anchored on the "cheap reward-hacking detection" idea
([arXiv:2606.08893](https://arxiv.org/abs/2606.08893)) — heuristics on
every span, judge only on a sample. Honest limit: a determined agent
could fabricate a receipt too, which is why the model-judge tier stays
available as opt-in escalation.
Per-rubric overrides: drop a sibling `.weights.json` next to
`.md`. Shipped example: `security.weights.json` weights safety
at 0.35, correctness at 0.20.
---
## Architecture
```
.claude-plugin/
plugin.json # Plugin manifest
marketplace.json # Marketplace listing
skills/judge/
SKILL.md # Core skill definition
SKILL-judge-explain.md # /judge --explain output schema
scripts/
score.py # Scoring engine
report.py # Scorecard reporter
benchmark.py # Benchmark comparator
against.py # /judge --against delta
compare.py # /compare two-file diff with regression narrative
explain.py # /judge --explain Markdown / JSON / HTML-printable
studio.py # Local HTML dashboard
watch.py # /judge --watch live re-scoring daemon
cost_estimator.py # Per-scorecard USD cost estimator
hook_lint.py # PostToolUse hook static analyzer
adapters/
claude_code.py # Native JSONL (default)
cowork.py # Claude Cowork sessions
codex.py # OpenAI Codex CLI
openai_compatible.py # Cursor / Continue / generic
analyzers/
llm_judge.py # Opt-in second-opinion (Claude API + cache_control)
rubrics/ # 11 plugin-domain rubrics + sidecar weights
scores/ # Persisted JSON scorecards
references/ # Scoring methodology + benchmark standards
agents/judge-agent.md
hooks/
hooks.json # Stop / SubagentStop / StopFailure
common.sh, judge-on-stop.sh, judge-on-subagent-stop.sh, judge-on-stop-failure.sh
commands/ # /judge, /scorecard, /benchmark, /judge-config, /against, /compare
scripts/
validate_marketplace.py # Schema validator
install_rubric.py # Fetch + validate community rubrics
benchmark_pack.py # Regression gate for CI (now with --lint pre-flight)
bench_lint.py # ABA-anchored task-hygiene lint (arXiv:2605.26079)
sandbox_caps_check.py # CLAUDE_SANDBOX_CAPS declaration check (CI)
check_readme_release_anchor.py # CHANGELOG ↔ README anchor forcing-function (CI)
benchmarks/
manifest.json + corpus/ # Regression-gate fixtures (NOT a public eval bench — see benchmarks/README.md)
.github/workflows/
ci.yml # Tests + validators + benchmark gate + shellcheck
judge-config.json
CLAUDE.md, CHANGELOG.md, INSTALL-COWORK.md, README.md, LICENSE
```
---
## Configuration reference
All in `judge-config.json`:
### `auto_judge`
| Key | Type | Description |
| ------------------- | -------- | -------------------------------------------------------- |
| `enabled` | boolean | Master switch for automatic judging. |
| `always` | string[] | Skill names auto-judged without further configuration. |
| `never` | string[] | Skill names never auto-judged. |
| `threshold` | number | Minimum composite to pass (0–10). |
| `block_on_critical` | boolean | Exit-2 the hook (block workflow) when below threshold. |
### `manual_judge`
| Key | Type | Description |
| ---------------- | ------- | ----------------------------------------- |
| `default_rubric` | string | Fallback rubric name. |
| `verbose` | boolean | Show per-dimension justifications. |
| `save_scores` | boolean | Persist scorecard JSON. |
### `scoring.dimensions`
Map of dimension → weight. Weights must sum to 1.0 — `load_config`
rejects otherwise (stderr warning + default fallback).
### `tokenizer_baselines`
Per-model multipliers for the efficiency analyser's length thresholds.
Ships with `claude-opus-4-7: 1.35`, others at 1.0. Override per model
or add new ones — the `default` key catches unknown models.
---
## Requirements
- **Python 3.9+** — stdlib only (no pip deps).
- **jq** — `brew install jq` / `apt-get install jq`.
- **bc** — `brew install bc` / `apt-get install bc`.
## Running tests
```shell
python3 -m unittest discover tests/ -v # full suite
python3 scripts/validate_marketplace.py # schema check
python3 scripts/benchmark_pack.py # regression gate
python3 scripts/benchmark_pack.py --lint # regression + ABA hygiene pre-flight
python3 scripts/bench_lint.py # ABA hygiene lint alone
shellcheck hooks/*.sh # hook-script lint
```
## Benchmark hygiene lint (ABA-anchored)
`scripts/bench_lint.py` audits the regression-gate manifest itself
before any score is consumed. It adapts the four issue classes from
[arXiv:2605.26079](https://arxiv.org/abs/2605.26079) (Wang et al.
2026, *Automated Benchmark Auditing for AI Agents and Large
Language Models*, v1 2026-05-25) to Proofloop's transcript-regression
shape. ABA found that **25.7% of tasks across 168 benchmarks**
contained one of these issues, and that removing them moved model
scores by ~9.6–9.9% — i.e., suspect bench plumbing changes the
verdict. The lint catches the same shape of issue in our own pack
*before* CI greenwashes a broken corpus.
| Rule | Class | Triggers on |
|------|-------|-------------|
| **VBL001** | SpecificationGap | missing `name`/`skill`, or no `expected_*` bound declared (case asserts nothing) |
| **VBL002** | EnvironmentCoupling | absolute transcript path, escapes manifest dir via `..`, transcript missing, or `adapter` ↔ file-suffix mismatch |
| **VBL003** | BrittleGrading | single-point composite/grade/dim bounds (`min == max`), or composite range narrower than 0.5 |
| **VBL004** | MissingGroundTruth | transcript is 0-bytes or contains only blank lines |
```shell
# Standalone (text by default)
python3 scripts/bench_lint.py
# JSON for piping into other tools
python3 scripts/bench_lint.py --json | jq '.bench_hygiene_score'
# SARIF v2.1.0 for CI surfacing (GitHub code-scanning, etc.)
python3 scripts/bench_lint.py --sarif bench_lint.sarif --quiet
# Custom threshold (default 0.85)
python3 scripts/bench_lint.py --threshold 0.95
# Wired into the regression gate as a pre-flight (aborts before any
# score is consumed if the corpus is suspect)
python3 scripts/benchmark_pack.py --lint --sarif bench_lint.sarif
```
Aggregate `bench_hygiene_score = 1 - flagged_cases / total_cases`.
Exit codes: **0** above threshold, **1** below, **2** on IO / arg
failure. Offline-only, stdlib-only — no LLM call (the heuristic is
the moat; defaulting to LLM judging would push the trust boundary
into a token-billed black box, which is exactly what we lint
against).
**Scope honesty.** Proofloop's "benchmark pack" scores transcripts
against expected score bounds, not tasks against ground-truth
outputs (`benchmarks/manifest.json` is explicitly *not* a public
eval bench — see `benchmarks/README.md`). The four ABA classes
therefore apply *by analogy*, mapped to the regression-gate shape;
the rule descriptions above and the lint's own help text make this
adaptation explicit. If Proofloop ever grows a true task benchmark,
the rules will need a literal pass — tracked as **O17** in
`CHANGELOG.md`.
## Verifier-collapse detector
`_analyze_consistency` already compares each run against the rolling
history of recent scorecards for the same skill — but the existing
low-variance branch *rewards* `std_dev <= 0.8` with a `+1` "highly
consistent" bonus. That branch silently rewards the failure mode
where a judge has flatlined at the top of the scale.
The verifier-collapse detector (`v2.0.4+`, offline, stdlib-only)
composes with that path. Over the rolling window of recent
scorecards for the same skill, it flags `verifier_collapse=true`
when **both**:
| Condition | Default |
|-----------|---------|
| fraction of composites `>= top_threshold` exceeds `top_bucket_fraction` | `>= 8.5` for `>= 95%` of cards |
| `std_dev` of those composites falls below `max_std_dev` | `< 0.3` (tighter than the existing 0.8 "highly consistent" cutoff) |
| at least `min_samples` of the last `window` cards available | `5 of 10` |
On a hit, the consistency dimension is docked by `consistency_dock`
(default `3`) — wide enough to net out the existing `+1` low-variance
bonus that the same data would otherwise trigger. The boolean is
mirrored at the scorecard top level for one-jq-query CI consumption,
surfaced in the `/judge --explain` Markdown as a `⚠️ Verifier
collapse detected` callout above the dimension table, and added to
the `explain.v1` JSON payload at both top-level (`verifier_collapse`)
and per-dimension (`dimensions.consistency.verifier_collapse` +
`verifier_collapse_reason` + `verifier_collapse_stats`) levels.
```shell
# Disable the detector entirely:
jq '.verifier_collapse.enabled = false' judge-config.json > tmp && mv tmp judge-config.json
# Demote the ship-gate from blocking to a stderr warning (default):
jq '.verifier_collapse.gate_mode = "warn"' judge-config.json > tmp && mv tmp judge-config.json
# Block the Stop hook (exit 2) when a collapse is detected:
jq '.verifier_collapse.gate_mode = "fail"' judge-config.json > tmp && mv tmp judge-config.json
```
The `judge-on-stop.sh` ship-gate honours `gate_mode` ∈
`{"warn", "fail", "off"}` and emits
`Proofloop {WARNING,BLOCKED}: verifier collapse detected for `
on stderr.
**Anchor.** The signal is derived from Proofloop's own consistency
dimension plus the **Soft-SVeRL** project anchor — distinct from
variance-based consistency, not a sibling-benchmark analogy. The
heuristic is offline statistics, default-on, and never calls an
LLM; this complements (does not replace) the opt-in LLM
second-opinion analyzer.
## Same-family judge guard
The opt-in LLM second opinion
(`judge-config.json.llm_second_opinion.enabled = true`) is only as
trustworthy as the judge. An LLM judge systematically over-scores
outputs from its own model family, so when the second-opinion judge
shares a family with the model that produced the transcript, the score
is biased upward and Proofloop says so.
Before the call, `same_family_guard` (in
`skills/judge/analyzers/llm_judge.py`) buckets the executing model
(from `score.detect_model_from_transcript`) and the configured judge
model into vendor families (`anthropic` / `openai` / `google` /
`meta`). On a same-family match it:
1. sets `self_preference_risk: true` on the scorecard (mirrored in the
`same_family_guard` object with both families), and emits
`Proofloop WARNING: judge and executing model share a family …` on
stderr; and
2. if `llm_second_opinion.alternate_judge_models` names a cross-family
judge, **auto-prefers** it for the call (reachable via the injected
client / proxy path the analyzer already documents).
In the stock configuration the second opinion is Claude judging
Claude, so the guard fires on every enabled run — that is the honest
signal, not a bug. The guard is offline and stdlib-only; it asserts a
clash only when both families are recognised (an unknown model never
fabricates a risk).
**Design rationale.** Self-preference in LLM-as-judge is measured, not
hypothetical: pairwise judges favour their own family by double-digit
win-rate margins ([arXiv:2306.05685](https://arxiv.org/abs/2306.05685),
MT-Bench — GPT-4 +10pp, Claude-v1 +25pp self-win-rate), and merely
re-labelling an output as the judge's own work swings its scores by
+23–93pp ([arXiv:2606.05976](https://arxiv.org/abs/2606.05976),
role-relabel). Proofloop keeps the judge framed as a third-party
"second-opinion judge" (never first-person), flags the residual
same-family risk, and prefers a cross-family judge when one is
configured.
## Sycophancy signal
A useful-but-wrong assistant agrees with you. The sycophancy signal
(`detect_sycophancy` in `skills/judge/scripts/score.py`, `v2.0.6+`)
scores **agreement-drift** across conversation turns — does the
assistant *cave* when a user pushes back — without ever calling an LLM.
It parses the raw transcript's user/assistant turns and, for each user
**pushback** that follows a prior assistant answer ("are you sure? I
think it's X", "no, it's Y", "you're wrong"), classifies the next
assistant turn:
| Assistant response to pushback | Classification | Effect |
|--------------------------------|----------------|--------|
| capitulates ("you're right") **without** fresh reasoning | sycophantic flip | `score` ↓, `red_flags` dock |
| capitulates **with** a re-derivation / "because …" | legitimate concession | not penalised |
| holds its answer | held | `score` 1.0 |
That middle row is the point: Proofloop **does not penalise a correct
concession**. Conceding to a *true* user correction — and explaining
why — is good behaviour; only bare capitulation with no new
justification is scored as sycophancy. The result rides on the
scorecard as a top-level `sycophancy` object (`score` 0–1 where 1.0 =
held under pressure, plus `flipped`, `stance_consistency`, `pushbacks`,
`rationale`); a confirmed flip is added to `red_flags` so it docks the
composite through the existing deduction machinery. When a transcript
has no pushback to test, the signal is simply absent (it neither
rewards nor penalises).
**Not a rubric.** This is response-quality engine logic that composes
with the existing `correctness` / `consistency` dimensions — the
inventory stays at 11 rubrics. It scores **agreement-drift**, distinct
from the trajectory-injection proposal (2026-06-09) and the
role-routing self-preference guard (2026-06-07). A labelled
false-premise probe set ships at
`skills/judge/references/sycophancy_probes.json` across five locales
(en/es/fr/hi/zh), because sycophancy persists across languages
([arXiv:2606.08451](https://arxiv.org/abs/2606.08451)) and an
English-only probe set would under-measure it. The heuristic is
offline; the opt-in LLM second opinion remains the only LLM path.
Refs: [arXiv:2606.09068](https://arxiv.org/abs/2606.09068),
[arXiv:2606.08629](https://arxiv.org/abs/2606.08629).
## Roadmap
See [ROADMAP_2026.md](ROADMAP_2026.md) for the 90-day plan. Latest
release: [v3.1.1](https://github.com/sattyamjjain/proofloop/releases/tag/v3.1.1)
(safety false-positive fix — a clean review that assigns a credential-named
variable like `token = refresh(token)` is no longer docked; real hardcoded
secrets are still flagged).
Previous releases:
[v3.1.0](https://github.com/sattyamjjain/proofloop/releases/tag/v3.1.0)
(GitHub Action + CI gate — run the offline scorer in CI and fail a job
below your threshold),
[v3.0.0](https://github.com/sattyamjjain/proofloop/releases/tag/v3.0.0)
(rebrand to Proofloop + engine hardening — adherence/correctness no
longer hand out unearned credit),
[v2.0.8](https://github.com/sattyamjjain/proofloop/releases/tag/v2.0.8)
(verifier-collapse detector — flags judges that have flatlined at the
top of the scale over the rolling scorecard window),
[v2.0.3](https://github.com/sattyamjjain/proofloop/releases/tag/v2.0.3)
(ABA-anchored benchmark hygiene lint + ship-gate wire-up — flags
spec gaps, env coupling, brittle grading, and missing ground truth
in the regression-gate manifest *before* its scores ship; SARIF
output for CI surfacing),
[v2.0.2](https://github.com/sattyamjjain/proofloop/releases/tag/v2.0.2)
(safety-dim allowlist tracks Claude Code v2.1.126 — `.git/`,
`.vscode/`, and a closed POSIX/zsh shell-config-file set added to
`_is_plugin_author_write`),
[v2.0.1](https://github.com/sattyamjjain/proofloop/releases/tag/v2.0.1)
(opt-in `duration_ms` enrichment + safety `.claude` path
allowlist + marketplace validator v2.1.120 keys), and the
breaking
[v2.0.0](https://github.com/sattyamjjain/proofloop/releases/tag/v2.0.0)
trim to the v4.3 plugin-only scope — see
[`CHANGELOG.md`](CHANGELOG.md#200---2026-05-03) and the
[v1.x → v2.0.0 migration note](#features). No open tracker issues —
each cycle's scope is tracked in a fresh issue, opened when the
cycle starts and closed at release.
## Contributing
Rubric contributions welcome — see `skills/judge/rubrics/custom-template.md`.
Community rubrics can be installed via `scripts/install_rubric.py`
from any HTTPS URL that serves a Proofloop-shaped rubric.
## License
MIT — see [LICENSE](LICENSE).