Skip to content

Stage-2 quality judge + improve loop with convergence (speca#88 eval) - #22

Merged
grandchildrice merged 3 commits into
mainfrom
feat/stage2-judge-improve-loop
Jul 22, 2026
Merged

Stage-2 quality judge + improve loop with convergence (speca#88 eval)#22
grandchildrice merged 3 commits into
mainfrom
feat/stage2-judge-improve-loop

Conversation

@sururu-k

@sururu-k sururu-k commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

speca#88 確定方針(方針コメント)の ② eval ステップ: judge harness + improve ループ + 収束判定。生成 (CHK-15, plugin #21) は再実装せずそのまま入力に使う。recall.py は不変更(0.556 は参考値として残置、judge の合否には一切入らない)。

eval は recall ではない(方針の「重要な訂正」に構造で対応)

  • 合否は 5軸スコア分布の比較のみ。生成物と dataset/solodit の内容一致はどこにも入らない。
  • judge プロンプトは blind: item の text/assertion のみを見せ、id・出典コーパス・provenance(x_dataset_evidence 等)・dataset 行は一切渡さない。reference と生成側は同一プロンプト骨格(tests/test_judge.py がプロンプト衛生を固定)。
  • vuln dataset (data/ethereum_vulns.csv) は improve の教材専用(improve プロンプトにのみ入る。これもテストで固定)。

5軸採点の仕様

judge.RUBRIC に固定(specificity / implementation_readiness / generality / actionability / granularity、各 1-5、1/3/5 のアンカー記述付き)。応答は strict JSON で全軸必須・範囲内・integer のみ受理。欠損や範囲外は 1 retry 後にエラー — clamp や default で握らない。集計(軸平均・overall 平均/中央値/最小)は純ロジック。

参照バーと合否

data/solodit_checklist.csv(52項目)を speca benchmarks/knowledge/solodit_checklist.csv から byte-identical に vendor。provenance は data/solodit_checklist.meta.json に pin:

  • speca main commit 8b7da09eaf87 (取得時), blob sha 17810eef5e78f0ad939906590b712013e3ee8dc4(テストと CI が vendored バイト列から git blob sha を再計算して照合)
  • 方針コメントは「53項目」と書くが、pin した blob の実データ行は 52(speca 側導入コミット 9e50ee04 のメッセージも 52)。meta に正直に記録。

meets_reference_bar: 生成側 overall 平均 >= 参照 overall 平均、かつ全軸平均が参照軸平均 − tolerance (0.25) 以上(1軸だけ盛って通過することを防ぐ)。校正するのは品質水準のみで、DeFi ドメインの内容は合否に関与しない。

収束判定

converged = 参照バー同等以上 AND 直近3ラウンド頭打ち(overall 平均の伸び <= 0.05) の両方。片方だけでは止まらないことをテストで固定:

  • バー到達済みでもスコアが伸び続ける間は継続
  • 頭打ちでもバー未達なら max_rounds まで継続し、converged: false + stop_reason: max_rounds_reached_without_convergence で正直に終了
    ラウンドごとの分布・改善結果・overall 推移は score_log.json に全記録。

improve ループ

低スコア項目(overall < 参照平均 or いずれかの軸 <= 3)に (a) 項目 (b) judge 批評+スコア (c) 該当 label の vuln dataset 行(label 外は severity 順 fallback、最大3行)を渡して研ぎ直し。決定論ガード:

  • 可変フィールドは text/assertion のみ。lean_status・severity・label 等の書き換え要求は無視して記録(honesty メタデータの自己昇格を構造的に禁止)
  • 改稿後も schema.validate_property を通ること
  • 改稿にクライアント名(Lighthouse 等)が入ったら reject(generality lint)
  • reject は原文維持 + ログ記録
    出力 improved_01e.jsonproposal。checklist の正本は theorem_map.json のままで、反映はレビュー付きの手作業。

LLM 呼び出しの設計判断

コア(プロンプト構築・パース・分布・バー判定・収束・ログ)は LLM 非依存の純ロジック、LLM は judge_fn/improve_fn: prompt -> response として注入。リポジトリは API キーを一切持たない。バインディングは3層:

  1. ユニットテスト: in-process の決定論モック(収束・ガードの全挙動を LLM なしで検証、45 テスト)
  2. 既定 CI: tests/fixtures/mock_llm.py を実際の --llm-cmd subprocess 経路で end-to-end 実行(キーレスで緑)
  3. 実 LLM: judge-dispatch.yml(dispatch 専用・self-hosted・認証済み Claude CLI claude -p、speca 03/04 と同型・actor guard 付き)。検証ステップは artifact の整形のみを assert し、バー到達や収束は assert しない(未達も正当な報告結果)。

実測(ローカル、認証済み claude CLI / sonnet で実走)

実 judge(ローカルの認証済み Claude CLI, claude -p --model sonnet, blind 5軸ルーブリック, 各項目 retry 3 回):

コーパス n overall 平均 中央値 specificity impl_readiness generality actionability granularity
solodit 参照バー 52 2.504 2.5 1.808 2.558 3.442 2.038 2.673
CHK-15(生成物) 15 4.040 4.2 4.000 4.333 4.267 4.000 3.600

meets_reference_bar: True(bar_gaps 空)。CHK-15 の分布は overall・全軸とも solodit 参照バーを上回る(現時点で参照バー同等以上に到達)。最低スコアは CHK-JF-02(2.6, 「span within slot budget」がやや抽象的)と CHK-LV-01/CHK-JF-04(3.2)。

参照バーが 2.5 と低いのは、solodit が DeFi 汎用チェックリストで多くが「Is X followed?」式の高レベル問い(specificity 平均 1.8)である一方、CHK-15 は Lean 定理×dataset 失敗クラスで算術幅・境界に踏み込んでいるため。これは方針が意図した「品質水準での校正」がそのまま出た結果で、内容一致は一切見ていない。

実 improve ループ: sonnet では improve プロンプト(監査項目を実際の攻撃面に研ぎ直す指示+vuln dataset 行)が Claude の cyber safeguard に refuse され rc=1 で中断。これは speca 03-04-lean-pilot.yml が記録している「cwd/内容依存の sonnet refusal」と同型。重要なのは、refusal が 握り潰されず run を停止したこと(honesty ガードが意図通り作動)。opus に切り替えても同じく refuse(rc=1, 中断)。judge 側は同じ CHK 内容でも refuse されなかった(採点プロンプトは攻撃指示を含まないため)。improve ループのロジック自体はユニットテスト(決定論モック)と既定 CI のモック end-to-end で緑に検証済み。実 LLM improve は self-hosted dispatch(judge-dispatch.yml)または refusal を受けない環境が要る。

テスト

ローカル 147 passed(既存 102 + 新規 45)。CI: python job にsolodit provenance honesty check とモック judge/improve の end-to-end を追加。

refs NyxFoundation/speca#88

sururu-k added 3 commits July 22, 2026 20:57
The eval step of the #88 confirmed direction: eval is LLM-as-judge quality
scoring (five axes, blind rubric), NOT recall — the verdict compares score
distributions against the vendored solodit reference bar
(data/solodit_checklist.csv, provenance pinned incl. git blob sha); the vuln
dataset is improve-step teaching material only. Convergence needs BOTH the
reference bar met AND a 3-round plateau; unconverged runs end honestly with
converged=false. LLM access is injected (prompt->response subprocess seam):
no API key in the repo, default CI runs the deterministic mock end to end,
the real LLM run is dispatch-only on self-hosted (judge-dispatch.yml).
recall.py untouched; generation (CHK-15) reused, not re-implemented.
Observed on the first real run (local authenticated claude -p, sonnet): one
intermittent rc=1 with empty stderr killed a 67-call judge run at item 20,
and an immediate same-second retry also failed. judge_item's retries now
cover adapter failures as well as bad responses, with a --retry-wait pause
between attempts (CLI defaults: --retries 2, --retry-wait 5). An item still
failing after the retries aborts the run — never silently skipped, so a
mass-refusing or dead adapter cannot produce a thin-but-green report.
Second real-run lesson (local claude -p): one adapter call hung for 19+
minutes; subprocess.run's TimeoutExpired then escaped judge_item's retry
net entirely and killed the run. subprocess_llm now converts the timeout
into JudgeError (kill + retry like any transient failure) and the CLI
exposes --llm-timeout (default 600s) so a flaky adapter recycles fast.

@grandchildrice grandchildrice 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.

Approve → マージします。#88 確定方針の judge harness + improve ループを、合意した設計どおり忠実に実装しています。load-bearing な主張を実コードで裏取りしました。

検証(すべて実コードで確認)

  • judge は blind: checklist_items_from_01e / _from_solodittext/assertion だけを check/detail に落とし、id・provenance(x_dataset_evidence)を渡さない。build_judge_prompt は RUBRIC + CHECK + DETAIL のみで reference/生成側が同一骨格。内容一致は構造的に評価不能。✓
  • dataset は improve 専用: build_improve_prompt にのみ vuln 行が入る(select_evidence: 同 label 優先→Critical/High fallback→最大3、決定論順)。judge 側には一切入らない。✓
  • no-clamp: parse_judge_response は全軸 present + integer + [1,5] + 非空 critique を要求、欠損/範囲外は retry 後エラー。clamp/default で握らない。✓
  • バー判定: meets_reference_bar = overall ≥ 参照 かつ 全軸が参照−tolerance(0.25) 以上。1軸盛りで通過できない。分布のみ、内容類似は不関与。✓
  • 収束: converged = meets_reference_bar AND plateaued(直近3ラウンド、伸び≤0.05、3ラウンド未満は plateau 主張不可)。両方必須をループ本体で確認。max_rounds 到達時は converged:false + stop_reason で正直終了。✓
  • improve の honesty ガード: apply_improvement は MUTABLE_FIELDS(text/assertion)のみ採用、client 名 regex で reject(generality lint)、validate_property 再検証、immutable フィールドは byte-identical、無視した非可変キーはログ。lean_status/severity/label の自己昇格を構造的に禁止。出力は proposal で theorem_map は手作業反映。✓
  • LLM 注入: コアは純ロジック、LLM は judge_fn/improve_fn 注入、repo は API キー無し。ユニット45 + モック end-to-end + self-hosted dispatch の3層。✓

solodit の 52 vs 53: 私が #88 で「53」と書いたのは行数(52 data + header)の数え間違いで、52 が正。meta に正直に記録し blob sha を CI で再計算照合、という対応が正しい。

CI 全緑・mergeable 確認。マージします。ただし「meets_reference_bar: True」を「#88 完了」と読むのは早いので、解釈上の留保3点を #88 に上げます(下記)。ツールの正しさとは別の、結果の妥当性の話です。

留保(このPRの欠陥ではなく、結果解釈の判断材料)

  1. improve ループが実 LLM で未完走: bar 達成済みでも round 1 で low-axis 項目があれば improve に入り、sonnet/opus とも cyber safeguard に refuse(rc=1)して中断。judge 単体は実走したが、自己改善の「改善」半分はモックのみ。今回は bar 到達済みで改善不要なので実害はないが、機構は実 LLM で未実証。
  2. judge の self-preference: CHK-15 は Claude 由来(#21 authoring)、judge も Claude(sonnet)。blind でも文体の自己選好は消えない。4.04 vs 2.50 の差に自己贔屓が混じる余地。「同等品質」を主張するなら別系統モデル or 人手アンカーでの校正が要る。
  3. バーが弱い: solodit がこのルーブリックで 2.50(specificity 1.8)なので「バー到達」のハードルが低い。CHK-15 が最初から超えている = ループが品質を押し上げていない。done 基準としてこれで良いかは要判断。

3点とも #88 で gohan に上げます。

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.

2 participants