💭

自分で書いたプロンプトは自分で評価できない — Empirical Prompt Tuning 実践記

に公開

TL;DR

mizchi氏の empirical-prompt-tuning スキルを使って、自分の Claude Code スキル 6 本を最適化した。まず中核 3 本(bounded-context, minerua-codegen, tdd-cycle)で手法を確立し、その後 3 本(jj-workspace, living-documentation, minerua-fill)に展開。37 のサブエージェントをディスパッチし、全スキルの skill-quality unclear を 0 に収束させた。最大の発見: accuracy 100% でも品質の穴は見える。そして 自分の fix が新しいバグを生む瞬間をバイアスフリーの executor が捕まえた


動機: なぜスキルのテストが必要なのか

Claude Code のスキル(SKILL.md)は、エージェントへの「業務指示書」だ。人間がドキュメントを書くのと同じで、書いた本人は「明瞭に書けた」と思い込む。しかし別の人が読むと解釈がブレる。

エージェントも同じだ。自分が書いたスキルを自分で読み返しても、バイアスが入って「ちゃんと書けている」と感じてしまう。empirical-prompt-tuning の核心はシンプル:

書いた本人ではなく、何も知らない新しいエージェントに実際に実行させ、二面評価する。


セットアップ

2段階のアプローチ

全体を2段階で進めた:

  1. Phase 1(深掘り): 中核 3 スキルで手法のすべての機能を検証(Iteration 1-3 + variant exploration + hold-out)
  2. Phase 2(展開): 残り 3 スキルにバッチ適用 + Hook による自動化

Phase 1 対象スキル

スキル 用途 行数
bounded-context Wittgenstein 言語ゲーム + DDD による境界分析 535行
minerua-codegen 自然言語仕様からコード生成ワークスペースを作成 153行
tdd-cycle t-wada式 Red-Green-Refactor ワークフロー 698行

Phase 2 対象スキル(後半で追加)

スキル 用途 選定理由
jj-workspace Jujutsu VCS 操作 毎回使用、高頻度
living-documentation ドキュメント生成・レビュー 複数モード、高複雑度
minerua-fill codegen スケルトンの関数充填 minerua-codegen と対

シナリオ設計

各スキルに 2 シナリオ(median + edge)を用意。Phase 1 のシナリオ:

スキル Scenario A (median) Scenario B (edge)
bounded-context フードデリバリー「注文」の多義性 IoT — 技術コンポーネントから Actor を発見できるか
minerua-codegen 認証モジュール生成 既存型参照 (--project-context)
tdd-cycle パリンドローム判定の TDD セグフォルトバグの修正

各シナリオに [critical] 付きの要件チェックリスト(3-6 項目)を設定。
Phase 2 のシナリオは「スケールアウト」セクションで紹介する。


Iteration 0: 静的整合性チェック

サブエージェントを動かす前に、description と body の gap を確認する。

スキル Gap
bounded-context 軽微 — description にコード接地が未記載
minerua-codegen 要注意 — "IG inference" と謳うが実際は Claude Code が JSON 生成
tdd-cycle 軽微 — description が body の豊富さに対して控えめ

この静的チェックを飛ばすと、サブエージェントが body を description に合わせて「再解釈」し、accuracy が不当に高く出る(false positive)。


Iteration 1: Baseline

6 つのサブエージェント(Sonnet)を並行ディスパッチ。

結果

Scenario Success Accuracy Steps Duration Unclear pts
bc-A 100% 1 133.8s 0
bc-B 100% 1 174.1s 2
mc-A 100% 4 71.8s 3
mc-B 100% 3 69.2s 3
tdd-A 100% 1 99.1s 2
tdd-B 100% 1 67.7s 0

全シナリオ accuracy 100%。しかし unclear points は計 10 件。

発見 1: accuracy 100% でも品質の穴は見える

定量メトリクス(accuracy, steps, duration)だけを見ていたら「完璧」で終わっていた。しかし executor の self-report から:

  • minerua-codegen の JSON スキーマに imports フィールドがない(生成ルールは import を要求するのに)
  • skeleton stub に discard を使っている(Error honesty 原則に違反)
  • tdd-cycle で「既にパスするテストを書くな」と「Obvious Implementation 後の回帰ガード」が矛盾

これらは 定性的評価(unclear points / discretionary fill-ins)が主、定量が補 という設計思想の正しさを裏付ける。

発見 2: tool_uses のスキュー

minerua-codegen の steps(3-4)が他スキル(1)の 3-4 倍。これはスキルの 自己完結性の低さ を示す構造的シグナル。CLI バイナリ確認やファイル I/O が多く、executor が外部参照に追われている。


Iteration 2: Fix 適用と再評価

Fix propagation patterns — 適用前に予測する

empirical-prompt-tuning の独自機能。fix を当てる前に「この fix はどう伝播するか」を予測する:

Fix 予測 実測
JSON スキーマに imports 追加 Overshoot Overshoot ✓ — steps 4→1
project-context エラーパス追加 Conservative Conservative
skeleton stub を raise に変更 Overshoot Zero-shoot
tdd 回帰ガード例外明記 Conservative Conservative
bc 入力分類ステップ追加 Conservative Conservative

5 つ中 4 つ的中。しかし 1 つが Zero-shoot — fix がまったく効かないどころか、新しいバグを生んだ。

発見 3: 自分の fix がバグを生む(Zero-shoot)

discardraise newException(NotImplementedDefect, ...) に変更した。一見正しい。しかしバイアスフリーの executor が即座に指摘:

  1. NotImplementedDefect は Nim stdlib に存在しない
  2. {.raises: [].}raise が矛盾する(Exception 系は effect tracking される)

自分で書いた fix を自分でレビューしても、この矛盾には気づけなかっただろう。「raise にしたから OK」という確証バイアスが働くからだ。

Hold-out シナリオ(overfitting check)

収束判定の前に、これまで使っていない新シナリオで検証:

Skill Hold-out scenario Accuracy Overfitting?
bounded-context 病院「処方」4 Actor 100% No
minerua-codegen Blog CRUD ~96% Minor — デフォルト値未対応
tdd-cycle Stack[T] 実装 100% No

minerua-codegen だけ minor signal。パラメータのデフォルト値 (limit: int = 10) を JSON スキーマで表現できない問題が浮上。


Iteration 3: Variant Exploration

minerua-codegen の skeleton stub 問題が未収束。局所最適から脱出するため、2 variant を並行テスト

Variant A Variant B
方式 doAssert false, "stub" type StubDefect = object of Defect + raise
利点 追加型不要、Nim 標準 grep 可能、明確なエラー名

4 エージェント(2 variant × 2 scenario)を並行ディスパッチ。

比較(客観軸のみ)

Metric Variant A Variant B
mc-A accuracy 90% 100%
mc-B accuracy 100% 100%
Total steps 8 10
Unclear points 3 3

Variant B を採用。 accuracy が高い方を選ぶ。tie なら steps が少ない方を選ぶ — というルールが決まっているので、主観的な「どちらが好きか」は入らない。

最終収束状態

Skill Iterations Skill-quality unclear Status
bounded-context 2 0 収束
tdd-cycle 2 0 収束
minerua-codegen 3 (variant 含む) 0 収束

数字で見る全体像

指標
ディスパッチしたサブエージェント 19
総 iteration 3 + variant exploration
iter1 の unclear points 10
最終 unclear points (skill-quality) 0
発見した fix バグ 1 (Zero-shoot)
Hold-out で発見した新問題 2
使用した全機能 14/14 (100%)

学んだこと

1. 「書いた本人が読み返す」は構造的に無意味

これが empirical-prompt-tuning の根本思想であり、実践して身に染みた。自分の fix にバグがあることに気づけなかったのが最大の証拠。

2. accuracy 100% は安心材料にならない

Iteration 1 で全シナリオ 100% だったのに、10 件の unclear points が出た。メトリクスだけ見て「完璧」と判断するのは危険。定性評価(self-report)が主。

3. Fix propagation patterns で「効く fix」と「効かない fix」を予測できる

5 つ中 4 つ的中。外した 1 つが Zero-shoot で、これが最も学びが大きかった。fix を当てる前に「これはどの判定文言を満たすか」を明示する習慣が、空振りを防ぐ。

4. tool_uses のスキューは構造的欠陥のシグナル

accuracy が同じでも、あるシナリオだけ steps が 3-4 倍なら、そのスキルは自己完結性が低い。「accuracy だけで切る」と構造的欠陥を見逃す。

5. Variant exploration は「迷ったら両方試す」の形式化

doAssert vs StubDefect で迷ったとき、主観で選ばず並行テストして客観軸で比較した。これは個人のプロンプト改善でも組織のプロンプト運用でも使えるパターン。

6. Hold-out シナリオは overfitting の安全弁

2 シナリオだけでチューニングすると、そのシナリオに特化した改善になりがち。hold-out で検証したら minerua-codegen にだけ minor signal が出た(デフォルト値問題)。


スケールアウト: 残り 3 スキルのバッチチューニング

最初の 3 スキルで手法を確立した後、残りの高優先度スキル(jj-workspace, living-documentation, minerua-fill)をバッチで回した。

自動化: PostToolUse Hook

SKILL.md を編集するたびに empirical-prompt-tuning を提案する hook を settings.json に追加:

# ~/.claude/hooks/suggest-empirical-tuning.sh
FILE_PATH=$(jq -r '.tool_input.file_path // .tool_response.filePath // ""')
if echo "$FILE_PATH" | grep -q 'skills/.*SKILL\.md$'; then
  SKILL_NAME=$(echo "$FILE_PATH" | sed -E 's|.*/skills/([^/]+)/SKILL\.md$|\1|')
  echo "{\"hookSpecificOutput\": {\"hookEventName\": \"PostToolUse\",
    \"additionalContext\": \"SKILL.md for '${SKILL_NAME}' was modified.
    Consider running /empirical-prompt-tuning.\"}}"
fi

実際に SKILL.md を Edit した瞬間、即座に提案が表示された。ワークフローに組み込まれた。

バッチ結果

Skill iter1 unclear iter2 unclear Status
jj-workspace 0 即収束(1 iteration)
living-documentation 2 0 2 iteration で収束
minerua-fill 3 0 2 iteration で収束

jj-workspace は Iteration 1 で both scenarios unclear 0 — 最も成熟したスキルだった。

living-documentation の fix は 2 件:

  • ADR の自動採番ルール(docs/adr/ 内の最大番号 + 1)
  • 出力言語の決定ルール(既存 ADR の言語に合わせる)

minerua-fill の fix は 3 件:

  • --functions で部分充填した場合の stages.fill 状態遷移(ssInProgress
  • 共有ファイルの deploy 時に未充填 stub を含む警告表示
  • skeleton-relative → project-relative の import パス書き換えアルゴリズム

最も印象的だった出力: living-documentation の ADR 生成

executor が実際のコードベース(types.nim, evaluation.nim, operators.nim, 既存 ADR-0001〜0088)を読み込み、ADR-0089 として既存の ADR-0001 を Supersede する形で生成した。スキルの「ADR を更新するな — 新しい ADR で Supersede する」ルールにも正確に従っていた。


最終スコアカード

Skill Iterations Agents Final unclear
bounded-context 2 8 0
tdd-cycle 2 8 0
minerua-codegen 3 (+ variant) 11 0
jj-workspace 1 2 0
living-documentation 2 4 0
minerua-fill 2 4 0
合計 37 0

まとめ

empirical-prompt-tuning は プロンプトの TDD だ。

  • テストリスト = 評価シナリオ + 要件チェックリスト
  • RED = unclear points が出る
  • GREEN = fix を当てて unclear が消える
  • REFACTOR = variant exploration で構造を改善

書いたプロンプトを「良いはず」と信じるのではなく、バイアスフリーの executor に実行させて二面評価する。これを反復する。それだけで、スキルの品質は確実に上がる。

そして PostToolUse hook で自動化すれば、スキルを書くたびにテストが走る — コードの CI と同じ発想がプロンプトにも適用できる。


Credits

  • empirical-prompt-tuning by @mizchi
  • 評価対象スキル: bounded-context, minerua-codegen, tdd-cycle, jj-workspace, living-documentation, minerua-fill
  • 実行環境: Claude Code (Opus 4.6) + Sonnet subagents
  • 総サブエージェント: 37
  • 総 iteration: 12 (variant exploration 含む)

Discussion