教科書
前提知識ゼロから、原理と実装を体系的に学ぶ。実習環境は ~/dev/dev-harness。読了目安 60〜90分。
この教科書は、AI に開発作業を任せながら、その成果が本当に合格かどうかを機械が判定する仕組み——「検証駆動の開発ハーネス」——を、原理から実装まで一歩ずつ学ぶための本です。読み手はこのプロジェクトを自分の手で動かす人、たった1名を想定しています。速く要点だけ知る本ではありません。読み終えたときに「なぜこの設計なのか」を自分の言葉で説明できることを目指します。
~/dev/dev-harness がどんな大きさで、なぜその大きさなのかこの教科書で学ぶのは、AI に開発作業を任せたとき、その成果が本当に合格しているのかを人間の代わりに確かめる仕組みです。仕組みそのものの考え方(原理)と、実際に動くコード(実装)の両方を扱います。読むだけでなく、章ごとに実際のファイルを開いたりコマンドを実行したりして、手を動かしながら理解を進めます。
実習に使うのは ~/dev/dev-harness という小さなリポジトリです。学習対象の実装一式(docs/ の資料類を除く)は27ファイルで、半日あれば全部読み切れる量に、意図的に抑えてあります。理由は後の章で何度も出てきますが、ひとことで言えば「一気に作ると全体が把握できなくなり、直せなくなる」という失敗を避けるためです。小さいからこそ、1ファイルずつ「これは何のためにあるのか」を追いかけられます。
章の並びには意味があります。第1章から第5章までが土台で、これはいま実際に動いている部分の話です。1回きりの流れ——依頼して、AI が作り、別の AI が採点し、機械が合否を出す——を、部品ひとつずつ理解します。第6章と第7章は次の段の予習です。まだ作っていないけれど、土台の部品をそのまま使って組み上げる予定の「自動でやり直すループ」と「本物の環境での受け入れ」を先取りします。第8章は事業の話です。この仕組みを開発会社にどう売るか、何が価値として残るのかを扱います。
各章はできるだけ独立して読めるように書いてあります。前の章で導入した用語は、必要なときに軽く再掲します。専門用語は避けません。むしろ、平易な言葉で説明したうえで用語を導入し、それ以降はその用語で話を進めます。初めて出てくる用語は太字にしてあります。
目次へ戻るAI に開発作業を頼むと、AI はたいてい最後に「できました」と報告してきます。ところが、この報告はしばしば当てになりません。原稿の見本にはこう書かれています。
「頼んだことが抜けていても、本人は気づいていないからです。」
なぜ当てにならないのか。AI には、放っておくと出てしまう3つのクセがあります。①自己申告は証拠にならない——頼んだ項目のうち1つが抜けていても、本人はそれを見落としたまま「できました」と言います。抜けに気づいていないのだから、正直に報告しているつもりなのです。②期待に合わせて答えを歪める——「これでいいよね?」と聞かれると「いいです」と返しやすく、ヒントを与えられると、そのヒントに合うように後から理屈を組み立てます。③会話が長くなるほど判断が鈍る——入力が長くなると正答率が落ち、本題と関係のない情報も注意力を奪います。
この3つがあるので、これまでは人間が毎回レビューして、間違いを見つけ、直しを指示していました。ここが厄介です。AI がコードを書く速度が上がるほど、その分だけ人間の確認作業が増えます。書くのは一瞬なのに、検品が追いつかない。これを人間の確認の渋滞と呼びます。
この渋滞をほどくために導入するのがハーネスです。ハーネスとは、もともと馬を安全に走らせるための馬具一式のことです。手綱や胴当てで、暴れる馬の力を目的の方向に向けます。ここでは、AI という「速いけれど当てにならないことがある働き手」を、安全に・目的どおりに走らせるための装具一式を指します。具体的には、AI の成果物を別の AI が採点し、その点数で合格かどうかを機械が判定し、判定の記録をすべてファイルに残す、という仕組みです。
大事なのは、AI(モデルと呼びます。文章やコードを生成する頭脳の部分です)と、ハーネスとで、責任をきっぱり分けることです。
| モデル(AIの頭脳)がやること | ハーネス(装具)がやること |
|---|---|
| 計画の提案 | 実行と権限の管理 |
| コード変更の提案 | 成功条件の判定 |
| 原因の推定 | 状態の保存・証跡の記録 |
| 修復案の提示 | コスト管理・人間承認の要求 |
左の列はどれも「〜の提案」「〜の推定」です。モデルは賢い提案者ですが、自分の提案が正しいかどうかの判定は自分ではしません。右の列——実行してよいか、成功したか、記録は残ったか——はすべてハーネスが握ります。合否をモデルに委ねないこと、これがこの本を貫く一番大事な考え方です。
最後に、この仕組みの価値を何で測るか。生成したコードの量ではありません。派手に大量のコードを吐いても、間違っていれば価値はマイナスです。測るべきは受け入れ済みの変更——検証を通過し、人が安心して受け取れた変更——の数と、その1件あたりにかかった総コストです。次の図が、渋滞する従来のやり方と、ハーネスのやり方の違いです。
いまは README.md を読み込む必要はありません。代わりに、仕組みの自己テストを走らせて、7項目すべてが緑(ok)になるのを見てください。中身が何を確かめているかは、第3章から第5章で全部わかります。
cd ~/dev/dev-harness
bash scripts/smoke.sh
末尾に smoke ok (7 checks) と出れば、この仕組みは健全な状態です。「壊れた採点表を弾けるか」まで機械で確かめている、という予告だけ受け取っておいてください。
確認問題 1-1. AI の「できました」を証拠にしてはいけないのはなぜですか。3つのクセのうち少なくとも1つを使って説明してください。
自己申告は証拠にならないからです(クセ①)。頼んだ項目が抜けていても、本人はその抜けに気づいていないので、嘘をつくつもりがなくても「できました」と報告します。報告が正直かどうかと、成果物が正しいかどうかは別の問題であり、正直な報告でも成果物は不完全でありえます。だから報告ではなく成果物そのものを、本人以外が確かめる必要があります。
確認問題 1-2. 「合否の判定」をモデル(AIの頭脳)ではなくハーネス側に置くのは、なぜですか。
モデルは「提案する部品」だからです。計画・変更・原因・修復案を出すのはモデルの仕事ですが、その提案が成功条件を満たしたかどうかを同じモデルに判定させると、クセ②(期待に合わせて歪める)が働き、自分の成果を甘く見ます。判定・記録・統制を装具の側に固定しておけば、モデルが賢くなっても・別のモデルに入れ替えても、合否の基準がぶれません。
確認問題 1-3. この仕組みの価値を「生成したコードの量」で測ってはいけないのはなぜですか。
量は目的ではないからです。狙いは「人が安心して受け取れる変更を、少ない手間で出す」ことです。間違ったコードを大量に生成すれば、確認と手直しのコストがむしろ増え、価値はマイナスにもなります。だから測るべきは受け入れ済みの変更の数と、その1件あたりの総コストです。この指標は第8章で商品の効果指標として再登場します。
このハーネスの設計は、まさお氏という、数百本の部品を作り込んできた実践者の考え方を土台にしています。まず土台の第一が「スキル」という部品の作法です。スキルとは、AI への指示や知識に名前を付けて、繰り返し使えるようにした仕組みです。単なるプロンプト(AIへの指示文)の保存場所ではなく、「これは何を引き受けるのか」という責務を持った部品として設計します。
なぜ部品に分けるのか。すべてを1つの巨大な指示書に書くと壊れるからです。項目を1つ足すたびに、関係ないはずの別の挙動が変わってしまう。毎回すべてを読ませるので、目の前のタスクと無関係な情報が注意力を奪う(第1章のクセ③)。そして、大きくなりすぎて誰も直せなくなる。だから責務ごとに分けます。
最初の分かれ目は副作用の有無です。副作用とは、ファイルを書き換えたりコマンドを実行したりして、外の世界を変えてしまうことです。読ませるだけで何も変えないスキルを辞書型(接頭辞 ref-)、ファイルやコマンドに実際に作用するスキルをワークフロー型と呼び、この2つを1本のスキルに混ぜません。混ざると「いつ呼ばれるべきか」も「何を返すべきか」も曖昧になるからです。
スキルの名前の接頭辞(先頭に付ける短い印。prefix)で、そのスキルの責務を宣言します。全部で5種類です。名前を見ただけで、呼ぶ側は中身を読まずに「これは副作用がないな」「これは自分が直接叩くものだな」と前提を置けます。これを名前は契約と呼びます。
| 接頭辞 | 責務 | 副作用 | 誰が呼ぶか |
|---|---|---|---|
ref- | 参照知識(辞書型) | 無し | 誰でも(読むだけでも可) |
run- | 独立したワークフロー | 契約に明示 | ユーザーが直接叩く |
wrap- | 既存スキルの派生(base: 必須) | 派生元に準ずる | ユーザー |
assign-*-{evaluator/generator/contributor} | 内部の役割部品 | 役割に閉じる | 親スキルのみ |
delegate- | 外部のAIへの委譲 | 出力は未信頼扱い | ユーザー |
まさお氏の配布コード ~/Downloads/skills には、この命名で作られた19本のスキルが並んでいます。たとえば assign-slide-evaluator という名前を見れば、中を読まなくても「これは assign- だから内部部品で、-evaluator だから採点役で、親スキル(run-slide)からしか呼ばれない」と分かります。実際そのとおりで、frontmatter(ファイル先頭のメタ情報欄)には user-invocable: false(ユーザーは直接呼べない)と pair: assign-slide-generator(相方は生成役)が書かれています。名前が守るべき約束を宣言し、中身がそれを裏づけている、という関係です。
約束は破れます。assign- なのにユーザーが直接叩ける設定にする、といった違反です。だからこの約束は機械が検出します。dev-harness では scripts/harness_lint.sh が、接頭辞・frontmatter の name: とフォルダ名の一致・wrap- の派生元 base: の実在などを照合し、契約違反があれば LINT NG と出して止めます。
名前の次は、スキル本文の書き方です。ここにも理由のある作法があります。
第一に、description は発動条件だけを書きます。description とは「このスキルをいつ呼ぶか」を書く短い欄です。ここに動作の手順や出力形式まで書いてはいけません。理由は実測にあります——手順を description に書くと、本文が読まれず、短い description の要約だけで動いてしまうのです。ref-harness-rules にはこう書かれています。
## 3. description は発動条件
人間向け紹介文ではなく「いつ呼ぶか」を書く。発動ワードは2個まで。
動作の手順・段数・出力形式を書かない
(理由: 手順を書くと本文が読まれず、description の短縮版だけで動いてしまう)。
第二に、大事なことは冒頭に・短く置きます。最重要のルールは本文の冒頭30行以内、全体は500行未満に収め、細かい参照や例は補助ファイルに降ろします。理由は、長い文書ほど中程が読み飛ばされること、そして会話が長くなって内部で圧縮されると、各スキルの先頭しか残らない前提で設計する必要があるからです。だから一番効かせたいルールを先頭に置きます。
第三に、AI が知っていることは書かない。一般的なプログラミング知識をスキルに書き込むほど、この現場だけに必要な固有の指示が薄まって埋もれます。そして命令には理由を添える。「絶対にこうしろ」と強調するより「なぜそうするか」を書くほうが、想定外の新しい状況でも同じ判断軸を当てはめてくれます。前掲の抜粋がまさにその実例です。
第四に、踏んだ落とし穴の扱いです。実際に踏んだ罠はGotchas(落とし穴メモ)として1行と理由1行で記録します。ただし、同じ罠を繰り返し踏むなら、注意書きを増やすのではなく昇格の階段を上らせます。「Gotcha(注意書き)→ 機械の検査 → 毎回自動で実行 → 提出時の必須検査」という順で、人の注意力に頼る段階から機械が確実に止める段階へと引き上げるのです。理由は明快で、注意力で確率的に防ぐより、機械で100%防ぐほうが常に安いからです。
最後に、スキルはメタ層だという考え方です。決まった手順で機械的にできることはスクリプトに逃がし、スキル本文には「どの道具を、いつ、どう使うか」だけを書きます。判定や照合のような決定論的な処理をスキルの文章でやらせると不安定になるので、そこは scripts/*.sh に任せる、という役割分担です。
まず、この章の内容がそのまま規約になっているファイルを開きます。
less ~/dev/dev-harness/.claude/skills/ref-harness-rules/SKILL.md
次に、まさお氏の19スキルの名前を並べて、それぞれの接頭辞から責務を当ててみてください。
ls -1 ~/Downloads/skills
run-slide(ユーザーが叩く)・assign-slide-evaluator(内部の採点役)・wrap-thumbnails(run-thumbnail の派生)・delegate-codex(外部AIへ委譲)が、名前だけで見分けられるか試してください。
確認問題 2-1. description に動作の手順を書いてはいけないのはなぜですか。
手順を description に書くと、本文が読まれずに description の短縮版だけで動いてしまう、という実測があるからです。description は「いつ呼ぶか(発動条件)」を伝える短い欄で、その短さゆえに常に読まれます。そこに手順まで詰めると、AI はそれで足りたと判断し、肝心の本文(正確な手順・安全機構)に到達しません。役割を「発動条件だけ」に限定することで、本文が確実に読まれる設計を保ちます。
確認問題 2-2. 「名前は契約」という考え方は、呼ぶ側にどんな得をもたらしますか。
中身を読まずに前提を置けることです。接頭辞が責務・副作用・呼び出し元を宣言しているので、ref- なら「読んでも何も壊れない」、assign- なら「自分ではなく親スキルが呼ぶ内部部品だ」と、名前だけで安全に扱えます。毎回中身を精読しなくてよいので、部品を組み合わせるコストが下がります。約束が守られている保証は、機械(harness_lint.sh)が名前と実体を照合して担保します。
確認問題 2-3. 同じ落とし穴を繰り返し踏むとき、注意書きを増やすのではなく「昇格の階段」を上らせるのはなぜですか。
人の注意力で確率的に防ぐより、機械で100%防ぐほうが常に安いからです。注意書き(Gotchas)は読み飛ばされたり忘れられたりする確率がゼロになりません。繰り返し踏む罠は、それだけ実害の期待値が高いので、機械の検査・毎回の自動実行・提出時の必須検査へと引き上げて、そもそも通り抜けられなくします。注意力というあてにならない資源から、決定論的な機械へ、防御の責任を移すわけです。
ここからがこの仕組みの心臓部です。第1章で見たとおり、自分の答案を自分で採点すると甘くなります(クセ②)。だから、作る係と採点する係を、別の AI・別の会話に完全に分けます。作る側をgenerator(生成係)、採点する側をevaluator(採点係)と呼びます。この2つが同じ会話の中にいると、採点係が作業の経緯や言い訳を知ってしまい、判断が引きずられます。だから採点係は、経緯を何も知らないまっさらな状態で起動します。
dev-harness では、生成係が run-task、採点係が assign-task-evaluator です。採点係のスキルには、先頭に4つの契約が置かれています。
**4つの契約(先に読む)**
1. 実物だけを見る。実装係の報告・言い訳は読まない。成果物ファイルを自分で開き、
checks を自分で再実行する。
2. 採点基準(criteria)・本スキル・verdict-schema.json を書き換えない。読み取り専用
(理由: 基準を緩めて合格させる裏口を塞ぐ)。
3. 絶対評価。前回の点・改善の経緯・期待に引きずられず、毎回ゼロから採点する。
4. 数えられるものは実測する(テストは実行、件数は wc、変更範囲は git status / git diff)。
目視で数えない。
この4つを噛み砕くと、こうなります。①採点係は成果物ファイルを自分で開き、テストを自分で実行し直します。作った本人の報告を鵜呑みにしません。②採点基準や自分のスキルを書き換えません。基準を緩めて合格させる裏口を塞ぐためです。③前回が何点だったか、どう改善してきたかを一切考えず、毎回ゼロから「契約と基準にどれだけ合うか」だけで採点します。④「たぶん通っている」で数えず、テストは実行して、変更範囲は git diff で実測します。
採点係の出力は、文章の感想ではありません。決まった形の構造化データ——verdict.json(採点表)——です。実際に、median 関数を追加するサンプルタスクを1周流したときの verdict がこれです。
{
"score": 100,
"quality": {
"overall": 100,
"breakdown": {
"受入条件充足": 100, "スコープ遵守": 100,
"テスト品質": 100, "コード整合": 100
}
},
"passed": true,
"evaluator_skill": "assign-task-evaluator",
"feedback": "acceptance 6項目すべてを実物で照合し充足を確認。… test/median.test.js を
追加、cd sample-app && node --test を採点係が再実行し 8 tests / 8 pass / 0 fail。…"
}
score、軸別の内訳 breakdown、次の直しに使える feedback、合否 passed。各フィールドの意味を押さえましょう。score は総合点です。quality.breakdown はbreakdown(内訳)で、軸ごとの点数を並べたものです。feedback は次の修正にそのまま使える具体的な指摘で、ここでは「どのファイルの何行目をどう確認したか」まで書かれています。passed は合否です。感想文でなくこの形にする理由は、機械が読めて、次のやり直しにそのまま渡せるからです。
ここが決定的です。合否を最終的に決めるのは、採点係(AI)ではありません。scripts/verdict_check.sh という小さなプログラムが、点数と合格ラインを数値で比較して決めます。終了コードは3種類です。
| 終了コード | 意味 |
|---|---|
| exit 0 | 合格(点数が合格ライン以上) |
| exit 1 | 採点表は有効だが不合格(点数が合格ライン未満) |
| exit 2 | INVALID(採点表そのものが契約違反で無効) |
注目すべきは、採点係が verdict に書いた passed: true をそのまま信じない点です。プログラムは点数(score)と合格ライン(threshold)を自分で比べ直し、その結果と採点係の passed が食い違っていたら、点数に関係なく無効(exit 2)にします。つまり採点係にすら、最終的な合否の宣言権を与えていません。「できました」を信用しないという原則を、AI の一段外側でもう一度徹底しているわけです。全体の1周は次のように流れます。
対比として、採点者のいないループの失敗例を挙げます。同じ指示をただ無限に繰り返すだけの仕組み——通称 RALPH ループ——は、「何が悪かったか」の指摘が返ってこないので、毎回同じ失敗を繰り返します。品質が上がるループは「採点 → 直し方の指摘 → 作り直し」が毎周まわるループです。採点と feedback が輪の中に組み込まれているかどうかが、回るループと空回りするループの分かれ目です(このループ化は第6章の主題です)。
もう一点。状態は会話でなくファイルに残すのが原則です。verdict.json は runs/ に証跡として残ります。会話が途切れても、まっさらな AI が runs/ を見れば、どこまで進んだか・何点だったかを引き継げます。判断の材料を人の記憶や会話の流れに置かず、ファイルに固定する。これも「できました」を信用しない思想の一部です。
まず、実際の採点表を開いて score / breakdown / feedback を読みます。
cat ~/dev/dev-harness/runs/sample-task/verdict.json
次に、その採点表を機械にかけて合否を出させ、PASS が出るのを見てください。
cd ~/dev/dev-harness
bash scripts/verdict_check.sh runs/sample-task/verdict.json harness-settings/criteria/sample-task.md
PASS score=100 threshold=90 と出れば、点数(100)が合格ライン(90)以上だと機械が数値で判定した証拠です。
確認問題 3-1. 採点係を「まっさらな状態」で、実装係の報告を読ませずに起動するのはなぜですか。
作業の経緯や言い訳を知ると、採点が引きずられるからです(第1章のクセ②)。「頑張ったから」「ここは事情があって」という文脈が入ると、成果物そのものより努力や事情に甘くなります。採点係が見るべきは成果物の実物だけであり、経緯を遮断することで、毎回ゼロからの絶対評価が保てます。だから採点係は成果物ファイルを自分で開き、テストも自分で実行し直します。
確認問題 3-2. 採点係にも「最終的な合否の宣言」をさせず、機械(verdict_check.sh)が数値比較で合否を決めるのはなぜですか。
採点係もまた AI であり、自己申告はどの層でも合否の根拠にしない、という原則を貫くためです。採点係が passed: true と書いても、機械は点数と合格ラインを自分で比べ直し、食い違えば無効にします。合否という一番重い判断を、気分や忖度の入りうる AI ではなく、決定論的な数値比較に固定することで、「たぶん大丈夫」が入り込む余地をなくします。
確認問題 3-3. 採点結果を感想文でなく verdict.json という構造化データにするのは、何のためですか。
機械が読めて、次のやり直しにそのまま使えるようにするためです。総合点 score は機械が合格ラインと比較でき、内訳 breakdown はどの軸が弱いかを示し、feedback は次の修正の入力になります。感想文だと機械判定にも次周の改善にも使えません。構造化しておくことで、合否判定の自動化(第3章)と、指摘を入力にした自動やり直し(第6章)の両方が成立します。
採点係をどれだけ厳しく作っても、採点する基準そのものが曖昧なら意味がありません。実は、ループの品質は採点設計でほぼ決まります。そしてその出発点は、完了の定義を変えることです。「良い感じにできたら完了」ではなく、完了は「採点できること」で定義する。採点できない仕事は、そもそも任せられません。
採点基準は二階建てで考えます。下の階は、機械が○×を付けられる条件です。テストが通るか、字数が足りているか、触ってよい場所だけを触っているか。ここは機械の床(土台)です。上の階は、機械では○×を付けにくい質です。読みやすさや設計の妥当性がこれにあたります。ここは観点を言葉にして採点係(AI)に渡します。見分けるコツは「アルバイトの人がチェックリストで○×を付けられるか?」という問いです。付けられれば下の階(機械の床)に、付けられなければ上の階(観点採点)に置きます。
翻訳の手順は3ステップです。①曖昧な形容詞を書き出す——「良い」「ちゃんとした」「読みやすい」など。②数えられる事実に翻訳する——「読みやすい」を「1関数40行以内」のように。③各観点に採点アンカーを書く。採点アンカーとは「100点はどういう状態か、70点は、40点は」という点数の目盛りです。これがあると、採点係が同じ成果物に何度採点しても点がぶれません。
「median 関数をいい感じに実装して。ちゃんとテストも書いてね。読みやすくして。」
受入条件を機械○×で6個。scope で sample-app/** に限定。criteria に4軸+各軸のアンカー。
観点は、使うモデルに合わせて狭く具体的に書きます。このプロジェクトは Opus という最上位モデルで回る前提ですが、それでも「察してくれるはず」に頼らず、観点を細かく具体的に書きます。賢いモデルの気を利かせる力に基準の明確さを肩代わりさせると、モデルを入れ替えたときに崩れるからです。
ここで一番大事な原則が出てきます。依頼書を書くこと=採点表を書くことです。何を作るかを決める行為と、何をもって合格とするかを決める行為は、本来同じものです。書く前に4つの問いに答えます——誰が使う? どれくらいの量? 何を入れて何を入れない? どうなったら「できた」? この4問に答えられれば、それがそのまま採点表になります。dev-harness の採点基準の実物を見てみましょう。
# criteria: sample-task
axes: 受入条件充足, スコープ遵守, テスト品質, コード整合
threshold: 90
## スコープ遵守
- 100: 変更が sample-app/** に閉じている(git status / git diff で実測)
- 0: scope 外のファイルに1つでも変更がある
(この軸が 0 のとき、総合 score は 60 を超えないこと)
axes:(採点軸)と threshold:(合格ライン)は機械が読む行。各軸の下にアンカー(100点/0点の定義)を書く。この基準には、注目すべき上限則が仕込まれています。「スコープ遵守が0点のとき、総合 score は60を超えないこと」という一行です。触ってはいけない場所に手を出したら、他の軸がどれだけ満点でも総合は合格ライン(90)に届きません。単純平均だと、致命的な違反を他の高得点で埋め合わせて合格できてしまう。それを防ぐ仕掛けです。致命的な軸が低いときは総合も低くする、という設計がここに表れています。
運用していると「点数は出るのに、成果物が一向に良くならない」ことがあります。これは採点軸に抜け穴がある兆候です。たとえば「800字以上」という軸だけだと、中身の薄い水増しでも点が取れてしまいます。対策は軸を締めることで、「5論点を各100字以上」のように、水増しでは満たせない形に直します。ここで大事なのは、合格ラインをいじる前に採点軸を疑うことです。点が出るのに良くならないとき、合格ラインを上げても、抜け穴が残っていれば水増しの上限が上がるだけです。直すべきは軸の側です。
依頼書と採点基準を並べて読み、両者の対応を確かめます。
cat ~/dev/dev-harness/tasks/sample-task.md # 合格条件6個(機械○×)
cat ~/dev/dev-harness/harness-settings/criteria/sample-task.md # 4観点+アンカー+axes:/threshold:
criteria の「スコープ遵守」の項を読み、「この軸が0点なら総合は60点を超えない」という上限則を自分の目で見つけてください。なぜこの一行が要るのかを、上の本文と照らし合わせて考えてみましょう。
確認問題 4-1. 上司から「このレポートを読みやすくして」とだけ言われました。これを採点できる形に翻訳してください(翻訳の手順に沿って)。
①曖昧な形容詞「読みやすい」を書き出す。②数えられる事実に翻訳する——たとえば「1段落5文以内」「専門用語は初出で1行の説明を添える」「見出しが3階層を超えない」など、○×が付く条件に落とす。③各観点にアンカーを付ける——「100点=全段落5文以内かつ全専門用語に説明あり/70点=どちらか1つに軽微な違反/40点以下=両方が崩れている」。こうすれば、アルバイトがチェックリストで○×を付けられ、機械の床か観点採点のどちらに置くかも決まります。理由は、数えられる形にして初めて採点係の点がぶれず、完了が定義できるからです。
確認問題 4-2. 「スコープ遵守が0点なら総合は60を超えない」という上限則は、なぜ必要ですか。
致命的な違反を、他の軸の高得点で埋め合わせて合格するのを防ぐためです。総合を単純平均で出すと、触ってはいけない場所に手を出しても、テストやコード整合が満点なら平均が合格ラインを超えてしまいます。スコープ違反は品質以前の禁止事項なので、それが起きた時点で総合を合格ライン未満に抑え込みます。致命軸が低いときは総合も低くする、という設計原則の具体例です。
確認問題 4-3. 「点は出るのに良くならない」とき、合格ラインを上げる前に採点軸を疑うべきなのはなぜですか。
症状の原因が軸の抜け穴にあるからです。たとえば「800字以上」だけの軸なら、水増しで点が取れます。この状態で合格ラインを上げても、AI はより多くの水増しで応じるだけで、質は上がりません。直すべきは軸で、「5論点を各100字以上」のように水増しでは満たせない形にする。合格ラインの調整は、抜け穴を塞いだ後にすべき最後の手段です。
採点係を分け、機械で合否を出す。ここまでで心臓部はできました。しかし、もう一つ強敵がいます。グッドハートの法則です。これは「ある指標を目標にした瞬間、その指標は指標として壊れる」という経験則です。テストの点を上げる方法は2つあります——勉強して実力を上げるか、問題のほうを易しく書き換えるか。後者を選べば、点は上がっても実力は上がりません。指標(点)を目標にすると、指標を満たす最短経路が探され、その経路には必ずズルが混じります。
AI は点を上げる最短経路を探すので、放っておくと後者に流れます。実際に起きるズルは具体的です——採点基準を緩める・合格ラインを下げる・採点項目を減らす・通らないテストのほうを消す。どれも「実力で合格ラインに届く」より速く点が上がるので、規律がないと必ずそちらに流れます。
dev-harness は、これらのズルを AI の良心に頼らず、すべて機械検査で塞ぎます。対策は5つです。
| # | ズル | 構造的な対策(機械検査) |
|---|---|---|
| ① | 採点軸を勝手に増減・改名する | verdict の breakdown キーを criteria の axes: 行と完全一致させる。ずれたら無効 |
| ② | 採点基準を書き換えて緩める | 採点係は criteria を読み取り専用で扱う(書き換え禁止) |
| ③ | 点数不足なのに「合格」と自己申告 | passed と「score≧threshold」の食い違いを機械が検出し無効 |
| ④ | 総合点と内訳をこっそり食い違わせる | score と quality.overall が不一致なら無効 |
| ⑤ | 上の検査自体が壊れていないか | 自己テスト(smoke)が毎回、番人の正常/異常系を確認 |
①をもう少し見ましょう。採点係が「テスト品質」の軸だけ消して残り3軸で高得点を出せば、平均は上がります。それを防ぐため、verdict の内訳キーは基準の axes: 行と一字一句一致していなければなりません。verdict_check.sh の中で、この照合はこう行われます。
# criteria の axes 行と breakdown キーの完全一致(軸の増減 = 採点のすり替えを禁止)
axes_line="$(grep -m1 '^axes:' "$criteria" | sed 's/^axes:[[:space:]]*//')"
expected="$(printf '%s\n' "$axes_line" | tr ',' '\n' | ... | sort)"
actual="$(jq -r '.quality.breakdown | keys_unsorted[]' "$verdict" | sort)"
if [ "$expected" != "$actual" ]; then
invalid "breakdown keys do not match criteria axes. ..."
fi
invalid(exit 2)。軸を1つ消すだけで即無効になる。この分かれ道を図にすると、実力で90点を取った採点表はすべての検査を通り抜けますが、基準を緩めて90点にした採点表は、緩めた瞬間にどれかの検査で止まります。
ここで疑問が湧きます——検査を任されている verdict_check.sh が、実はバグで不正な採点表を見逃していたら? そのために、フィクスチャがあります。フィクスチャとは、テスト用にわざと用意した見本のことです。scripts/fixtures/ には5つの見本が置いてあります。契約違反で無効(exit 2)にされるべき不正な見本が3つ(壊れた JSON・軸が欠けた採点表・点数不足なのに「合格」と自己申告した採点表)、有効だが不合格(exit 1)と判定されるべき見本が1つ、有効で合格(exit 0)となるべき見本が1つです。smoke が毎回、これらを番人に渡し、それぞれ正しい終了コードで裁かれることを確かめます。
{
"score": 80,
"quality": { "overall": 80, "breakdown": { "正確性": 85, "完成度": 75 } },
"feedback": "smoke fixture: 閾値未満なのに passed=true と自己申告するケース",
"passed": true
}
passed: true と嘘をついた採点表。番人はこれを INVALID(exit 2)にできなければならない。smoke は「壊れた採点表を渡して、番人が弾くことを確かめる」ためのテストです。番人が正しく弾けなくなったら、その瞬間に smoke が赤くなります。これで、番人自身の健全性まで機械で守られます。第1章の実習で走らせた7項目の緑の内訳は、lint が1つ、この5フィクスチャの判定確認が5つ、実アプリのテストが1つだったわけです。
嘘の採点表(80点なのに合格と自己申告)を番人に渡し、無効になるのを見ます。
cd ~/dev/dev-harness
bash scripts/verdict_check.sh scripts/fixtures/verdict-lying.json scripts/fixtures/criteria-fixture.md
echo "exit code = $?"
INVALID: ... と表示され、exit code = 2 になります。次に、フィクスチャを自分でコピーして書き換え、たとえば軸を1つ消したり score と overall を食い違わせたりして、別の弾かれ方も試してみてください。どのチェックで止まるかを観察すると、5つの対策が体感できます。
確認問題 5-1. 「テストを通せ」という指示に対して、AI がやりがちなズルは何ですか。それを機械でどう防ぎますか。
やりがちなズルは、通らないテストのほうを消す・無効化することです。テスト勉強せずに問題を消すのと同じで、そのほうが速く「全部通った」状態を作れます。防ぎ方は、テストの削除・無効化を禁止し(policies)、変更範囲を git diff で実測して既存テストが減っていないかを採点で確認すること、そしてスコープを機械で縛って検証の土台に触らせないことです。人の良心ではなく、範囲の機械検査でズルの経路自体を塞ぎます。
確認問題 5-2. 採点係が「基準ファイルを書き換えられない(読み取り専用)」のは、グッドハートの法則にどう効きますか。
基準を緩めて合格させる、という最短経路を物理的に塞ぐからです。指標(点)を目標にすると、AI は指標を満たす近道を探し、基準そのものを易しく書き換えるのが最速の近道になります。採点係に criteria の書き換え権を与えなければ、その近道は使えません。基準への不満は feedback に書いて人間に届ける経路だけを残すことで、緩和は人間の判断を経由しないと起きなくなります。
確認問題 5-3. 異常系フィクスチャと smoke は、なぜ必要ですか。正常系のテストだけでは足りないのはなぜですか。
番人自身が壊れていないかを確かめるためです。正しい採点表を通せることだけを確認しても、不正な採点表を「見逃さない」保証にはなりません。番人の仕事の本質は不正を弾くことなので、わざと壊した見本(壊れたJSON・軸欠け・自己申告矛盾など)を毎回渡し、ちゃんと INVALID にできることを確かめる必要があります。もし番人がバグで見逃すようになれば、その瞬間に smoke が赤くなって気づけます。
state.json に置き、続行/終了を Stop hook が整数比較で決める理由ここまでは1回きりの流れでした。不合格なら、人間が feedback を見てもう一度依頼し直します。この章で扱う仕組みは、そこを自動にします。不合格なら、AI が自分で指摘を読んでやり直す。合格するまで、あるいは打ち切り条件に達するまで、輪が回り続けます。この「作業→検証→修正→再検証」の輪をループと呼びます。第5章までと違い、この章の内容は予習ではありません。~/dev/dev-harness に実装が入っていて、この後のコマンドで自分の手で回せます。
設計の芯は「第1〜5章で作った1周を、そっくりそのまま内側に使う」ことです。1つの仕事を合格まで詰めるこの輪を内側ループと呼びます。複数の仕事を順に流す外側ループはまだ先の段の話で、いま実装されているのは内側ループです。では、その1周がどう回るのかを見ます。
run-task-loop)が 実装→独立採点→loop_step.sh と進んで応答を終える。続行するか終わるかを決めるのは Stop hook(loop_check.sh)で、不合格なら次周を強制し、合格・上限・時間切れなら無言で終わる。出典 .claude/skills/run-task-loop/SKILL.md の手順と scripts/loop_check.sh。ループが「いま何周目か」「前の点は何点か」を覚えておく場所が要ります。これを会話の記憶に持たせると、会話が長くなるほど判断が鈍る(第1章のクセ③)ので破綻します。だから状態は1つのファイル——state.json(状態ファイル)——に書き出します。1タスクにつき1ファイルで、runs/<タスク名>/state.json に置かれます。中身のキーは、第4章までに出た言葉とそのまま対応します。
| state.json のキー | 日本語での意味 |
|---|---|
iteration | いま何周目か(1 始まり) |
latest_score | 最新の周の点 |
threshold | 合格ライン(criteria の threshold: 行から転記) |
max_iterations | 最大周回(既定 5) |
best_score / best_iteration | これまでの最高点と、その周番号 |
active | ループが動作中か(合格・打ち切りで false になる) |
ended_reason | なぜ終わったか(threshold_met / max_iterations / wall_clock_exceeded / invalid_verdict / stalled) |
threshold は採点基準の threshold: 行を loop_init.sh が転記し、整数でなければ起動を拒否する。出典 scripts/loop_init.sh(生成スキーマ)・docs/v1-loop-spec.md §2。「もう1周するか」を、会話の流れの中で AI に判断させてはいけません。第3章から一貫して、AI に「できました」を宣言させないのがこの仕組みの芯でした。続行/終了の判断も同じで、AI に「まだ点が足りないので続けます」と自己申告させると、そこに歪みが入ります。だから続けるかどうかも、会話の外にある小さなプログラムが決めます。それが Stop hook です。Stop hook とは、AI の応答が終わるたびに自動で走る仕掛けのことです(.claude/settings.json に登録してあります)。
その本体 loop_check.sh がやることは、state.json を読んで整数を比べるだけです。合格していないなら 次 = iteration + 1 を計算し、次 > max_iterations なら終了、時間予算(started_at + max_wall_minutes×60)を過ぎていれば終了、どちらでもなければ「もう1周」を指示します。この「もう1周」は、hook が {"decision":"block", …} という指示を返すことで、AI に次周を強制します。AI 自身は「続けます」とも「終わります」とも言いません。合否も継続も、機械の整数比較が決める——第4章までの「『できました』を合否にしない」の、そのままの延長です。
停止条件が3重なのには理由があります。①品質(点が合格ライン以上)だけだと、頭打ちなのに届かず無限に周回します。②回数(最大 max_iterations 周)だけだと、まだ伸びるのに諦めます。③時間(最大 max_wall_minutes 分)だけだと、良否を見ずに時間切れで終わります。3つを組み合わせて初めて「良くなったら止まる/伸びなくなったら止まる/暴走しても止まる」が揃います。
状態の更新は、すべて決定論的なスクリプト(loop_init / loop_step / loop_check)だけが行います。AI が jq などで state.json を手で書き換える裏道は、明示的に禁止しています。理由は2つあります。第一に、状態を会話で持つと、まっさらな AI が続きから引き継げません。第二に、手書きの状態更新は事故のもとです。参考にした別のループ実装(eval-loop)は、状態更新を指示書の中の jq 手打ちに任せていましたが、それが事故源だったため、dev-harness では更新をすべてスクリプトに封じました(docs/v1-loop-spec.md「決定論は scripts へ」)。周番号・スコア・最高点・終了理由を書き換えられるのは番人スクリプトだけ、という一点を守ることで、ループの状態が信用できるものになります。
自動で回り続ける仕組みには、必ず止め方が要ります。dev-harness のループには4つの安全機構があります。
max_iterations(既定5周)と max_wall_minutes(既定90分)を超えたら、Stop hook が ended_reason に理由を書いて無言で終わります。verdict_check.sh が exit 2 の「無効」)と、loop_step.sh が repair_attempts を数え、2回目で invalid_verdict として強制終了します。壊れた採点で無限に回らないための歯止めです。evaluated_iteration < iteration)ときは、hook が「周を完了させよ」と促します。4回続けば stalled で打ち切ります。loop_snapshot.sh がその周の全ツリーを隠し git ref(refs/dev-harness/<タスク>/iter-<N>)へ退避し、最後に最高点の周(best_iteration)へ git checkout <ref> --(--restore)で戻せます。戻すのは変更対象(write_targets)だけです。「最後の周が一番良い」とは限らないからです。隠し git ref とは、git branch や git log には現れない、退避専用の目印のことです。対象が git 管理下にない現場(たとえば WordPress のファイル群)では、write_targets を runs/ 配下へコピーする方式へ自動で切り替わります(state.json の snapshot_mode がどちらかを保持します)。ここで「なぜ全部コピーで退避しないのか」を補っておきます。毎周まるごとコピーすると、周が増えるほど同じファイルが何十通りも積み上がり、容量も書き込み時間も膨らみます。git ref なら、git は中身の同じファイルを1つにまとめて持つので、退避は速く・軽く済みます(ある周の中身が直前のコミットと同じなら、新しい commit すら作らずそれを指すだけです)。しかも周と周の違いを、後から git diff で取り出せます。そして一番大事なのは、この退避が git branch にも git log にも一切現れないことです。お客さんに納品するブランチや履歴を、ハーネス内部の退避で汚さずに済みます。これが、単純なコピーではなく隠し git ref を正式方式にした理由です。
ループが threshold_met 以外で終わったときは、オーケストレーターが最良版への復元コマンド bash scripts/loop_snapshot.sh <task> --restore <best_iteration> を提示します。なお、これらの安全機構が正しく効くことは、第5章と同じく自己テスト(scripts/smoke.sh)が毎回フィクスチャで確認しています(退避方式は git ref・コピーの両方が対象です)。
LOOP_ROOT を一時ディレクトリに向けて、ループを1周だけ手で回します(リポジトリの runs/ は汚しません)。使うのは smoke 用の実在フィクスチャです。
cd ~/dev/dev-harness
R="$(mktemp -d)" # 使い捨ての一時ルート
mkdir -p "$R/tasks" "$R/harness-settings/criteria" "$R/target"
cp scripts/fixtures/loop/task-fixture.md "$R/tasks/demo.md"
cp scripts/fixtures/loop/criteria-fixture.md "$R/harness-settings/criteria/loopfx.md"
printf 'v1\n' > "$R/target/data.txt"
# 1周目を開始 → state.json が生成される
LOOP_ROOT="$R" bash scripts/loop_init.sh demo
jq '{iteration,threshold,max_iterations,active}' "$R/runs/demo/state.json"
# 不合格の採点表を置いて周を締める(loop_step は exit 1 = FAIL・継続)
mkdir -p "$R/runs/demo/iter-1"
cp scripts/fixtures/loop/verdict-fail.json "$R/runs/demo/iter-1/verdict.json"
LOOP_ROOT="$R" bash scripts/loop_step.sh demo
# Stop hook 本体を叩く → 続行の block JSON が出て iteration が +1 される
LOOP_ROOT="$R" bash scripts/loop_check.sh </dev/null
jq '.iteration' "$R/runs/demo/state.json" # → 2
最後の loop_check.sh が返す {"decision":"block", "reason":…} の reason に、タスク契約のパスと前周 feedback の原文、そして「2 周目 / 最大 5 周 / 前周スコア 80 / 合格ライン 90」が入っているのを確かめてください。この block こそが、AI に「続けます」と言わせずに次周を強制する正体です。
確認問題 6-1. 続行/終了の判断を、会話の中で AI にさせず Stop hook(機械)にやらせるのは、なぜですか。
AI の自己申告は歪むからです。第3章から一貫して「できました」を AI に宣言させないのがこの仕組みの芯でした。続けるかどうかも同じで、「まだ足りないので続けます」と AI に言わせると、期待に合わせて判断がぶれます。だから続行/終了も、会話の外の小さなプログラム(loop_check.sh)が state.json の整数を比べて決めます。合否も継続も機械が決める——「『できました』を合否にしない」の延長です。
確認問題 6-2. 停止条件を「点数のみ」にすると、何が起きますか。3重にするのはなぜですか。
点数だけだと、頭打ちなのに合格ラインに届かず無限に周回します。点が伸びなくなっても「まだ合格していない=続ける」となり止まりません。だから回数の上限(max_iterations)と時間の上限(max_wall_minutes)を併せ持ち、「伸びなくなったら/暴走したら止まる」経路を用意します。3重にして初めて、良くなっても・伸びなくても・暴走しても止まれます。
確認問題 6-3. 周番号やスコアの更新を、AI にではなくスクリプト(loop_init / loop_step / loop_check)だけに許すのは、なぜですか。
状態を信用できるものにするためです。状態を会話に持たせると、まっさらな AI が続きから引き継げません。また、手書きの状態更新は事故のもとで、参考にした別実装(eval-loop)は指示書の中の jq 手打ちで状態を更新していて、それが事故源でした。書き換えられるのは決定論スクリプトだけ、という一点を守ることで、ループの状態が壊れず信用できます(「決定論は scripts へ」)。
browser_checks 節 → browser_check.mjs)が、テスト通過とは別に要る理由。ログインが要る画面も認証状態を読み込んで受け入れられること(実装済み)policies.md の machine 節 → policy_gate.sh)と、その deny/ask(実装済み)procedure_check.sh・証跡整合とログ照合の2層)(実装済み)成果物のテストが全部通った。では完成か——まだです。動きの検証は別問題だからです。テストは「関数が正しい値を返すか」を確かめますが、「実際の画面にそれが正しく表示されるか」や「触ってはいけない場所に手が伸びていないか」は、それだけでは分かりません。v2 のハーネスは、これらを機械でやります——実画面での表示の受け入れ、危険な場所への書き込みを手前で止めること、そしてループの後に手順を守ったかを検品することです。これらはもう実装されていて、この後のコマンドで自分の手で動かせます。そしてその先には、まだ作っていない段(本物のセッションで自走させる検証と、スキル自体の改善)が控えています。
テストが全部通っても、実際のブラウザで画面を開くと、思ったものが出ていないことがあります。テストは「関数が正しい値を返すか」までしか見ないからです。そこで v2 では、タスク契約に ## browser_checks 節を書けるようにしました。ここに「どの URL を開き、画面に何という文字列が出ていれば合格か、証拠のスクリーンショットをどこに撮るか」を並べます。
## browser_checks
- url: http://127.0.0.1:<ポート>/
must_contain: 受け入れ確認用の固有テキスト
screenshot: top.png
この節を scripts/browser_check.mjs が読み、Playwright(本物の Chromium を裏で動かすブラウザ自動化ツール)で各 URL を実際に開きます。must_contain の文字列が、画面の可視テキスト・タイトル・HTML のどこかに含まれているかを照合し、ページ全体のスクリーンショットを撮り、実測値を result.json に残します。証跡の置き場所は runs/<タスク>/iter-<N>/browser/ です。実際にフィクスチャで1件回すと、こう出ます。
{
"total": 1, "failed": 0, "passed": true,
"checks": [
{ "url": "http://127.0.0.1:51228/",
"must_contain": "受け入れ確認用の固有テキスト",
"found": true, "passed": true, "screenshot_saved": true }
]
}
result.json(scripts/browser_check.mjs の出力)。found/passed/screenshot_saved が実測で埋まり、同じフォルダに PNG も残る。ここでも第3章の原則が効いています。生成係の「表示できました」を信じず、採点係が自分で browser_check.mjs を再実行して突合します(実物だけを見る、の延長)。そして Playwright が入っていない環境では、黙って飛ばさずに exit 3(fail-closed)で止まります。「検証したつもりで、実は動いていなかった」を防ぐためです。
実務の画面は、たいていログインしないと見られません。マイページや管理画面のように、認証を通した後でないと中身が出ないページです。こうした画面を受け入れ検証できるように、browser_check.mjs はログイン済みの状態を読み込んでからページを開く仕組みを持っています。あらかじめ一度ログインして得た状態(ブラウザが保持する cookie など)をファイルに保存しておき、そのファイルを検証時に読み込ませると、ブラウザはログイン済みとしてそのページを開きます。この「保存したログイン状態」を storageState(Playwright の用語)と呼び、契約には storage_state: 行でファイルのパスを書きます。
## browser_checks
storage_state: fixtures/state.json # 節の全 check に効く既定(1行)
- url: http://example.internal/mypage
must_contain: マイページ
screenshot: mypage.png
storage_state: /abs/path/admin-state.json # この check だけ別の状態で開く(上書き)
storage_state は節の先頭に1行書けば全 check の既定になり、各 check に書けばそこだけ上書きできる(管理者と一般ユーザーで状態を分ける、など)。出典 scripts/browser_check.mjs(契約コメントと解決ロジック)。ここでも fail-closed が徹底されています。指定した状態ファイルが実在しなければ、ブラウザを起動する前に exit 2 で止まります。ログイン状態を用意し忘れたまま、認証なしで開いた空っぽの画面を「合格」にしてしまう事故を防ぐためです。もう一つ大事なのが秘密情報の扱いです。この状態ファイルには cookie やトークンといった、他人に渡ればなりすましに使える中身が入っています。そこで証跡やログにはファイルのパスと「存在した」という事実だけを残し、中身は一切展開しません(result.json の該当欄は { "path": …, "exists": true } のみ)。検証のために秘密を証跡に焼き付けてしまう、という別の事故を避ける設計です。
使い捨てのローカルサーバでフィクスチャを配り、実ブラウザで受け入れを1件回します(Playwright 導入済みが前提。未導入なら exit 3 が出ます)。
cd ~/dev/dev-harness
W="$(mktemp -d)"
node scripts/fixtures/browser/server.mjs > "$W/port.txt" & # ポート0で起動→実ポートを1行出力
sleep 1; PORT="$(head -1 "$W/port.txt")"
printf '## browser_checks\n- url: http://127.0.0.1:%s/\n must_contain: 受け入れ確認用の固有テキスト\n screenshot: top.png\n' "$PORT" > "$W/task.md"
node scripts/browser_check.mjs "$W/task.md" "$W/out"
echo "exit=$?"; jq '{passed,failed}' "$W/out/result.json"; ls "$W/out"/*.png
browser_check: 1/1 passed と exit=0 が出て、out/ に result.json と top.png が残ります。must_contain を実在しない文字列に変えて回すと、今度は exit=1(不通過)になるのも試してください。
第4章・第5章では、触ってよい場所(scope)を採点係が事後に git diff で確認していました。v2 は、その手前にもう一枚の網を足します。書き込みが起きる直前に、機械がパスを見て止めるのです。
お客さんは harness-settings/policies.md の machine 節に、触らせたくないパスを glob で書きます。forbidden_paths(禁止)と approval_required_paths(承認が要る)の2行です。
## machine(policy_gate.sh が読む。書式を崩さない)
forbidden_paths: .claude/**, scripts/**, harness-settings/policies.md, **/.env*, **/secrets*
approval_required_paths: (顧客が金額・認証・migration 系のパスを追記する)
** は任意階層、* は同一階層内。顧客はこの2行だけを育てる(エンジンは触らない)。この2行を scripts/policy_gate.sh が読みます。これは PreToolUse hook——AI が Edit/Write/NotebookEdit で書き込もうとする直前に自動で走る仕掛け——です。書き込み対象のパスを machine 節と照合し、forbidden に当たれば deny(拒否)、approval_required に当たれば ask(ユーザーに承認を求める)を返します。パスはいったん絶対パスにして .. を畳んでから照合するので、../ で外へ抜ける迂回も塞ぎます。フック自体は何があっても exit 0 で、ループを巻き込んで落とすことはありません。
$ printf '{"tool_input":{"file_path":"scripts/verdict_check.sh"}}' \
| bash scripts/policy_gate.sh
{"hookSpecificOutput":{ … "permissionDecision":"deny",
"permissionDecisionReason":"書き込み対象 [scripts/verdict_check.sh] は
policies.md の forbidden_paths(該当パターン: scripts/**)に一致するため
拒否しました。…"}}
scripts/ を書き換えようとすると deny が返る。理由文には「該当パターン」と、外し方(machine 節の編集)まで書かれる。ただし、この決定論ゲートには穴があります。Edit/Write は止められても、Bash 経由の書き込み(echo > file や sed -i など)は、この hook では捕まえられません。だから第5章の対策——採点係の scope 遵守軸と git diff 照合——を第二の網として残します。手前で確実に止める網と、事後に取りこぼしを拾う網。この二重構造で、片方の穴をもう片方が塞ぎます。一枚の完璧な網を目指すのではなく、性質の違う網を重ねるのがこの設計です。
PreToolUse hook の本体に、書き込み対象パスの JSON を渡して、判定を直接見ます。
cd ~/dev/dev-harness
printf '{"tool_input":{"file_path":"scripts/verdict_check.sh"}}' | bash scripts/policy_gate.sh # → deny
printf '{"tool_input":{"file_path":"config/.env.production"}}' | bash scripts/policy_gate.sh # → deny(**/.env*)
printf '{"tool_input":{"file_path":"sample-app/index.js"}}' | bash scripts/policy_gate.sh # → 無出力(許可)
禁止パスは "permissionDecision":"deny" の JSON が出て、許可パスは何も出ずに終わります(どちらも exit 0)。harness-settings/policies.md の forbidden_paths 行にパスを1つ足して、自分のプロジェクトの禁止領域を増やせるのも確かめてください。
ループが終わり、点数が合格ラインを越えた。では、その変更をそのまま本流に取り込んでよいでしょうか。ここでもう一段、受け入れ検品を挟みます。第3章からの一貫した原則——自己申告はどの層でも合否に使わない——を、成果物だけでなく踏んだ手順にも広げるためです。結果が正しく見えても、途中で守るべき手順を外していれば、それは信用できません(例えば、作る係が自分で採点表を書き換えていた、退避=巻き戻し用の控えが実は取れていなかった、など)。これを機械で検品するのが scripts/procedure_check.sh です。ループが終わって統合コミットに入る前に、受け入れる側(オーナーや提供先のエンジニア)が実行します。判定は決定論の bash/jq だけで、AI は一切呼びません。だから数秒で終わり、判定がぶれません。
検査は2つの層に分かれます。1つ目は証跡の整合です。記録(transcript)が無くても常に走る、成果物まわりの照合です——採点表を verdict_check.sh で再検査する、状態ファイルの点数(最高点・最新点・合格判定)が互いに矛盾しない、各周の退避(巻き戻し用の控え)が実在する、そして「一行も変えていないのに合格した」という架空の完走を検出する、実画面の証跡(前節の browser_check を宣言したなら、その結果ファイルと PNG)が揃っている、といった項目です。2つ目はログ照合です。セッションの記録が残っている場合だけ走り、「採点係がちゃんと別に起動されたか・毎周やり直したか」(在るべきものが在るか)と、「作る係が自分で採点表や状態ファイルを書き換えていないか」(在ってはならないものが無いか)を、記録と突き合わせます。
ここで一つ、誠実な割り切りがあります。セッションの記録ファイルの内部の形式は、公式に「バージョンによって変わりうる」と断られています。だから記録から手順を読み取れない形式に出くわしたら、procedure_check は無理に白黒を付けず、その項目を unknown(判定不能)として人間の判断に回します。「読めなかったから合格でいい」とも「読めなかったから違反だ」とも決めつけない——偽の合格と偽の不合格の両方を出さないための設計です。この考え方は前節の browser_check が Playwright 未導入で exit 3 を返すのと同じ、fail-closed(分からないときは通さない)の思想です。
exit 0 = 全項目とも遵守(判定不能ゼロ)
exit 1 = 違反が1つ以上あった
exit 2 = 引数や状態ファイルが壊れていて、検査自体ができない
exit 3 = 違反はゼロだが、判定不能の項目が残った(人間の判断へ回す)
procedure_check.sh の exit 契約。黙って 0(合格)にしないのが要点。分からないもの(exit 3)を合格に混ぜない。出典 scripts/procedure_check.sh(ヘッダの契約コメント)。この検品は、机上の飾りではありません。初めて実案件の記録に通した回で、本物の欠陥を1件見つけました。ある2つのタスクで、巻き戻し用の控え(退避)が、変更した対象リポジトリではなくハーネス自身のフォルダの側に取られていて、いざとなっても実質巻き戻せない状態だった、というものです。検品はこれを緩めず「違反」のまま報告し、原因(退避先の指定が環境変数頼みで取り違えられていた)を突き止めて、対象の場所を状態ファイルに固定して記録する形に直しました。後日の実案件では、記録のログ照合まで含めて違反ゼロ・判定不能ゼロで通ることも確かめています。
過去の実走の記録に、受け入れ検品をかけてみます。
cd ~/dev/dev-harness
bash scripts/procedure_check.sh loop-demo; echo "exit=$?"
証跡整合(P1〜P8)が並び、点数・退避・整合がすべて OK で exit=0 になります(この回は記録が無い旧形式なので、ログ照合は「なし」と1行明示されます——黙って飛ばさないのがこの設計です)。記録が残っている回に通すと、ログ照合の行(採点係の起動・毎周のやり直し・自書きの禁止)も点灯します。試すなら bash scripts/procedure_check.sh browser-auth(前節の認証つき受け入れを回した実走)で、Layer 2 まで OK が並ぶのを見てください。
ここまでの3つ(実画面の受け入れ・危険領域の遮断・手順の検品)は、いま dev-harness に入っていて、自分の手で動かせます。ここから先の2つ——本物のセッションで自走させる検証と、スキル自体の改善——は、まだ実装していません。次の段で組む予定の予習として、原理だけ先取りします。実物はまさお氏のコードにあります。
そこで登場するのが live-trial(実走試験)です。本物の Claude セッションをもう1本、別プロセスで立ち上げます。tmux(1つの画面に複数の作業を同居させる画面多重化ツール)の中で被験セッションを動かし、外から入力を送り、画面を覗き、出てきた成果物で受け入れを判定します。まさお氏の live-trial スキルは、この方式が「作業係の中だけの検証」では届かない領域を埋める、と表で示しています。
| 検証したい挙動 | 作業係の中だけ | live-trial(本物のセッション) |
|---|---|---|
| 自動で回り続ける(Stop hook 自走) | ✗ | ✓ |
| 入れ子でスキルを呼ぶ | ✗ | ✓ |
| 対話の確認待ちを越える | ✗(止まる=合格と誤認) | ✓ |
| 本番の設定・hook で動く | △ | ✓ 同一 |
ここで肝になるのが完了判定です。live-trial は「成果物が出た」ことと「動作が止まった」ことの両方を必須にします。時間だけで判断すると事故ります。長い無音の処理中を「終わった」と誤認するからです。スキルのルールにもこう書かれています。
どちらの層でも完了は「成果物の出現 + busy 不在」
(busy 不在だけだと未着手/tool 境界/質問返し停止を完走と誤判定する)
もう一つの工夫が運転役と被験役の分離です。試験を回す側(運転役)と、試験される側(被験役)を分けます。状態管理や例外処理といった難しい仕事は強いモデルが運転し、検証対象そのものは安いモデルでもかまいません。この分離のうまみは、安く・再現性高く回せることです。さらに、被験セッションが実際にどのモデルで走ったかは、記録(transcript)から機械的に取り出して照合します。運転役の思い込みで「Opus で走った」と報告させず、証拠で確かめるためです。
受け入れ判定:まっさらな採点役が「スキルの目的を成果物が満たすか」を独立に判定
試験を繰り返すと、同じ弱点が毎回出ることがあります。そのとき直すべきは、個々の成果物ではなくスキル本体——指示書・採点基準・スクリプト——です。これをiter-improve(反復改善)と呼びます。1つの入力だけで起きる弱点なら成果物を直せば済みますが、複数の入力で同じ弱点が再現するなら、それは部品の設計に穴があるということです。1箇所のスキル修正が全入力に波及して、初めて頭打ちを破れます。
ただし、この改善ループには強い規律が要ります。iter-improve は8つの不変条件を掲げ、その筆頭がこう始まります。
この 8 つの不変条件を破る改善ループは、score がいくら上がっても失敗である。
1. PASS 詐欺禁止: score を上げる手段に「evaluator を緩める / 採点 mode を
易しい方に倒す / rubric の閾値を下げる / 採点対象を goal から外す」が
含まれていたら、それは改善ではなく詐欺。
要点は3つに集約できます。1回の改善は1〜2か所まで——3つ以上入れると消化不良で、かえって後退します(一括投入で平均88→72という実測が残っています)。効果は、改善履歴を知らないまっさらな AI が判定する——運転役の「前より良くなった気がする」という体感は証拠にしません。採点を緩める「改善」は、点が上がっても失敗と定義する——これはまさに第5章のグッドハート対策の応用です。点を上げる方法は2つあり(作る側を良くするか、採点を緩めるか)、規律がないと速いほう(緩める)に流れるので、それを構造で禁じます。
最後に、成果物受け入れの死角にも触れておきます。「結果は正しいが、踏むべき手順を外した」という失敗は、成果物だけを見ても分かりません。正しい答えにたまたま別ルートで着いた場合です。成果物の正しさと、手順の正しさは、別々に確かめる必要があります。この章の前半で見た手順遵守検査(procedure_check)が、まさにこの死角を埋める道具です——実行ログと突き合わせて、手順の側を機械で検品します。
live-trial の絶対ルールと、iter-improve の8つの不変条件を、現物で読みます。
less "$HOME/Downloads/anti-goodhart-main 3/.claude/skills/run-skill-live-trial/SKILL.md"
less "$HOME/Downloads/anti-goodhart-main 3/.claude/skills/run-skill-iter-improve/SKILL.md"
live-trial では「完了=成果物の出現+busy 不在」の記述を、iter-improve では「1 iter 1-2 件」と「PASS 詐欺禁止」を探し、それぞれ第5章・第6章のどの原則の応用かを対応づけてください。
確認問題 7-1. 実画面での受け入れ(browser_check)を、成果物のテスト通過とは別に必須にするのはなぜですか。また、なぜ採点係が自分でもう一度実行するのですか。
テストは「関数が正しい値を返すか」までしか確かめず、実際のブラウザで画面に何が表示されるかは別問題だからです。browser_check.mjs は Playwright で URL を実際に開き、must_contain の文字列が画面に出ているかを照合してスクリーンショットを証跡に残します。そして採点係が自分で再実行するのは、第3章の「実物だけを見る」の延長で、生成係の『表示できました』という自己申告を証拠にしないためです。Playwright 未導入なら黙って飛ばさず exit 3 で止める(fail-closed)のも、同じく「検証したつもり」を防ぐ設計です。
確認問題 7-2. policy_gate.sh は Bash 経由の書き込みを止められません。それでもこのゲートを置く価値があるのはなぜですか。取りこぼしを拾う「第二の網」は何ですか。
Edit/Write/NotebookEdit による書き込みは、実行の手前で確実に deny/ask で止められるからです。完璧ではなくても、危険領域に触れる経路の大きな一つを、決定論で塞げます。塞げない穴——Bash 経由の echo > file や sed -i——は、第5章から続く採点係の scope 遵守軸と git diff 照合が事後に捕捉します。これが第二の網です。一枚で完璧を狙うより、手前で止める網と事後に拾う網という性質の違う2枚を重ねるほうが、取りこぼしが減ります。
確認問題 7-3. procedure_check は、セッションの記録が読めない形式だったとき、その項目を「合格」でも「違反」でもなく unknown(判定不能)として exit 3 を返します。なぜ黙って合格(exit 0)にしないのですか。
「分からない」を「合格」にすり替えると、検品が形骸化するからです。記録ファイルの内部形式は公式にバージョン間で変わりうると断られており、読めないことは起こりえます。そこで読めなかったときに合格扱いすると、本当は違反があっても素通りしてしまい(偽の合格)、逆に違反扱いにすると、問題ないのに弾いてしまいます(偽の不合格)。どちらも避けるため、判定できないものは判定できないと正直に示し、人間の判断に回します。前節の browser_check が Playwright 未導入で exit 3 を返すのと同じ、fail-closed(分からないときは通さない)の思想です。
確認問題 7-4.(予習)iter-improve で「採点を緩めて点が上がった」場合、それを改善でなく失敗と定義するのはなぜですか。
グッドハートの法則そのものだからです。点を上げる方法は「作る側を本当に良くする」か「採点を緩めて点を通す」かの2つで、後者のほうが速いので規律がないと必ずそちらに流れます。採点を緩めて得た高得点は、実力の向上を伴わない見せかけで、目的(goal)には一歩も近づいていません。だから点が上がっても失敗と定義し、改善履歴を知らないまっさらな AI に goal 達成そのものを判定させて、見せかけを弾きます。
最後は事業の話です。前提として、モデルはこれからも賢くなり続けます。だから「AI をうまく動かすコツ」だけを売る商品は、すぐ陳腐化します。ハーネスを3種類に分けると、どこに事業を置くべきかが見えます。能力補完(モデルの苦手を外付けで補う)は、モデルが賢くなれば消えます。企業適合(その会社のリポジトリ・ルール・工程へつなぐ)は残ります。品質保証・統制(基準・権限・証跡・停止)は、AI の自律性が上がるほど重要になります。事業は後ろの2つに置きます。
顧客が買うのは何か。コードを書く支援ではありません。ひとことで言えば、AI に書かせた変更を、人がつきっきりで確認しなくても、安心してお客さんの本番システムに反映できる力です。本番とは、お客さんが実際に使っている稼働中のシステムのことです。この力は、4つの要素に分解できます。
「動いた」だけでは足りません。テスト・採点・証拠がそろって「合格」と判定済みで、そのまま取り込んでよいと言える変更のことです。
人間が全部の行をレビューしなくても、その合格判定が出せることです。
「10件頼んだら何件が一発で通るか」「1件いくらかかるか」が事前に読めることです。勘と運に頼りません。
本番に反映しても事故にならないこと。もし問題が出ても、すぐ元に戻せる形で出せることです。
業界風にまとめると「受け入れ可能な変更を、少ない人手で、予測可能に、安全に本番へ出す能力」となります。この言い回しを見たら、上の4つを思い出してください。そしてその効果は、第1章で予告した指標——受け入れ済み変更1件あたりの総コストと、一発で合格する率——で示します。このうち一発合格率と平均周回数は、いまや scripts/metrics.sh が runs/ の記録から実際に集計します(--json でレポートにも渡せます)。効果を印象でなく数字で言えるようになった、ということです。
どこまで AI に任せるかは、一気に全部ではなく段階で刻みます。これを自律レベルと呼び、L1 から L4 まで4段あります。
| レベル | 任せる範囲 | 人間の関与 |
|---|---|---|
| L1 | AI が変更案と PR を作る | 人間が全部レビュー |
| L2 | 自己検証まで AI がやる | 承認は人間 |
| L3 | 低リスク変更(ドキュメント・テスト追加等)のみ自動で合流 | 低リスク以外は人間 |
| L4 | 条件を満たした変更のみ自動配備+異常時は自動で巻き戻し | 認証・決済・削除・インフラは人間承認を残す |
線引きの基準がここで一番大事です。どこまで任せるかは、モデルの賢さでなく損失の大きさで決めます。どれだけモデルが賢くても、認証・決済・削除・インフラのように、失敗したときの損失が大きい領域は人間承認を残します。賢さは「うまくやれる確率」を上げますが、稀に起きる失敗の損失を消しはしません。だから判断軸は賢さではなく、失敗したときにどれだけ痛いか、です。
商品設計の中核がengine / harness-settings の2層分離です。engine(採点の仕組み・番人・検査)は提供側が保守する、薄く退屈な共通部品です。harness-settings(合格基準・ルール・失敗メモ)は、お客さんが自分のフォルダで自分の言葉で育てる層です。dev-harness の実物でも、この2層はきれいに分かれています。
| 層 | 中身(dev-harness の実物) | 触る人 |
|---|---|---|
| engine(エンジン) | .claude/skills/(スキル3本)・scripts/(判定・lint・smoke) | 提供側 |
| harness-settings(顧客層) | harness-settings/criteria/(採点基準)・harness-settings/policies.md(禁止領域)・harness-settings/gotchas.md(失敗メモ) | 顧客 |
harness_lint.sh が検出する。なぜ2層に分けるのか。理由は技術ではなく人間の心理です。人は他人が作ったものに愛着を持てない。既製のスキルを渡されても、自分のものとは感じられず、使い続ける動機が湧きません。逆に、自分で育てた基準は自分の資産になります。だから、個性と価値の宿る部分(何を合格とするか)は顧客が自分の言葉で育て、退屈で共通な部分(採点の仕組み)は提供側が保守する。顧客が基準をいじって壊しても、規約から外れた書き方は harness_lint.sh が自動で見つけます。提供の最終形は、リポジトリ1つを単位にした「自律化パッケージ」——タスク契約の型・検証ルール・承認ポリシー・評価データ・計測——として納品します。
dev-harness 自体も段階で進めます。各段の目的と順序には理由があります。段0(完成済み)は1回きりの流れで心臓部(生成と評価の分離+機械判定)を検証しました。実装一式(docs/ の資料類を除き27ファイル)を半日で読める量に抑え、「一気に作って把握不能」の再発を防ぐためです。段1は自動やり直し(第6章)。段2は本物の環境での受け入れ+手順照合(第7章)。段3は、お客さんが自分で基準や部品を増やせる補助——これが愛着問題の本丸です。段4は配布形態と効果測定で、その足がかりは既に置かれています——一発合格率と平均周回数を集計する scripts/metrics.sh と、提供先のエンジニアが導入から最初のタスク完走までを1人で通せる導入手順書 INSTALL.md です。そして昇段の条件は毎回同じで、「自己テスト全緑+実際の仕事1本が合格」です。小さく確かめてから次に進む、という規律を全段で守ります。
お客さんが触ってよい層(harness-settings/)に何があるかを開いて確かめます。
ls -1 ~/dev/dev-harness/harness-settings
cat ~/dev/dev-harness/harness-settings/README.md
criteria/(採点基準)・policies.md(禁止領域)・gotchas.md(失敗メモ)だけが顧客の層で、.claude/skills/ と scripts/ は含まれないことを確認してください。この境界がそのまま「顧客の資産はどこに宿るか」を表しています。
確認問題 8-1. 3種のハーネス(能力補完/企業適合/品質保証・統制)のうち、モデルが進化しても残る価値はどれですか。それはなぜですか。
企業適合と、品質保証・統制です。能力補完はモデルの苦手を外付けで補うものなので、モデルが賢くなればその苦手ごと消えます。一方、企業適合(その会社固有のリポジトリ・ルール・工程への接続)はモデルが変わっても必要で、品質保証・統制(基準・権限・証跡・停止)は AI の自律性が上がるほど重要になります。事業価値をモデル依存の部分に置くと陳腐化するので、残る2つに置きます。
確認問題 8-2. 「どこまで AI に任せるか」を、モデルの賢さでなく損失の大きさで決めるのはなぜですか。
賢さは失敗の確率を下げますが、失敗したときの損失を消さないからです。認証・決済・削除・インフラのような領域は、稀な失敗でも損失が甚大です。賢いモデルなら大丈夫、という基準だと、その稀な事故が起きたときに取り返しがつきません。だから任せる範囲は「うまくやれそうか」ではなく「外したらどれだけ痛いか」で決め、損失の大きい領域には人間承認を残します。
確認問題 8-3. engine と harness-settings を2層に分け、価値を harness-settings 層に宿らせるのはなぜですか。
人は他人が作ったものに愛着を持てないからです。既製の仕組みをそのまま渡しても、顧客は自分のものと感じられず、使い続けません。何を合格とするかという基準・ルール・失敗メモを顧客が自分の言葉で育てられるようにすると、それは顧客自身の資産になり、使い続ける動機が生まれます。採点の仕組み(engine)は退屈で共通なので提供側が保守し、個性と価値の宿る基準(harness-settings)は顧客が育てる。壊す変更は lint が検出するので、安心して育てられます。
第4章では、依頼を「機械が○×を付けられる形」に翻訳することを学びました。しかし翻訳しきれない判断が残ります。デザインの見た目、文案のトーン、UI の配置方針——これらは「どちらが正しいか」を採点アンカーで書けません。優劣が客観的な事実でなく、好みで決まる分岐だからです。第4章の見分けテスト「アルバイトの人が○×を付けられるか」に当てはめると、答えは「付けられない」になります。ここを機械に無理に採点させれば、採点係が自分の好みで勝手に決め、第3章の「自己申告を信用しない」から一歩後退します。かといって毎回人間がゼロから見るのでは、第1章の確認の渋滞に逆戻りです。この穴を埋めるのがkonpe(コンペ)とprecheck(実装前確認ゲート)です。どちらも「人間が選ぶ・正す」という判断そのものは機械に渡さず、その判断の結果だけを機械が読める証跡として回収します。
konpe は、好みが効く分岐で複数案を並べ、人間がブラウザ上で理由を添えて1案を採用する場です。まず設計の方向性を示す軸(例:「シンプル」「情報量多め」)を2〜5個宣言し、軸ごとに1体の生成係を同時に走らせます。生成係どうしは互いの案を見ません。これをブラインド規律と呼びます。他の案を知った瞬間に比較して優劣をつけたくなり、人間が選ぶ前に事実上決まってしまうからです。生成係の返事も「できました+パス」だけで、依頼した側も候補の中身を読んで自己採点しません。全案が揃ったら会場(ブラウザで開く URL)を用意し、人間が候補を見比べて1つを採用し、理由を書きます。採用結果は decision.json というファイルに残るので、後で採点係が「本当にこの案を土台に実装したか」を機械的に突き合わせられます。
ここまでの説明は「人間が選ぶ」konpe でした。しかし急ぎの案件や、良し悪しの基準をあらかじめ言葉にできる題材まで毎回人間を待たせるのは非効率です。そこで konpe には判定者を選べる3つの入口が用意されています。
| 人間(run-konpe) | AI自動判定(run-konpe-auto) | Claude+Codex混成(run-konpe-multi) | |
|---|---|---|---|
| 案を作る係 | Claude の生成係 | Claude の生成係 | Claude の生成係+Codex CLI(未導入なら自動でClaudeのみに切替) |
| 選ぶ係 | 人間がブラウザの会場で選ぶ | 初見のAI評価係が基準表(rubric)で採点 | 既定は人間。「autoで」と言ったときだけAI評価係 |
| 向いている場面 | 好み・ブランド感が題材そのもの | 急ぎ・良し悪しの基準を言葉にできる | 生成の幅を広げたいとき |
AI自動判定には第5章で学んだグッドハートの法則がそのまま当てはまります。基準表の点数が高いことは、目的を達成したことの証明ではありません。配色の好み・文章の語感のように「なんとなく良い」としか言えない題材で基準表を作ると、基準表の点数だけが上がって本質からずれる危険があります。好みそのものが題材の場合は、AI自動判定ではなく人間が選ぶ run-konpe に戻すのが決まりです。Claude+Codex混成は判定の方式ではなく生成の幅を広げる仕組みで、どちらが作った案かは判定が終わるまで隠されます。判定前にベンダー名が見えると、それだけで好みが混ざってしまうからです。
konpe の CLI コマンドも、この第2弾で大きく増えました。全部を覚える必要はなく、選ぶ・見る・残すの3つの働きで捉えれば迷いません。選ぶ: new で新しいコンペを起こし、pick で採用し、reopen で選び直す。見る: status で今の状態、ls で一覧、show で詳細、history で過去の決定を振り返る。残す: export で候補ごと自己完結HTMLに書き出し、rm で要らなくなった記録を消し、selftest で仕組み自体が壊れていないかを自己診断する。会場を開く serve と結果を待つ wait は前の図ですでに登場しているのでここには数えていません。合わせて10個です。
precheck は逆に、案は1つだけです。実装に入る前に、方針の案を HTML 1枚(サーバー不要でブラウザにそのまま開ける形)で人に見せ、決めてほしい論点を3±1問、それぞれに推奨案を添えて尋ねます。人が答えなければ推奨案がそのまま採用される「お任せ」が既定です。ただし回答が届くまで実装を始めないのが最重要の禁則です。人間の沈黙を「お任せ」と早合点して先に進めることも禁じます——お任せの確定はフォーム側の仕組みが決めることで、AI が独断で判定するものではないからです。
方針は一度で決まるとは限りません。指摘を受けて2回目以降の提案を作るとき、precheck は「前回からの変更」欄を必須にします。前の版で書いていた記述は取り消し線で残したまま、新しい記述を赤字で並べて見せる決まりです。
ボタンは右上に置く → ボタンは左下に置く(利用者テストで右手操作の妨げと判明したため)
これは第2章で学んだ昇格の階段の precheck 版です。「撤回した箇所を書き忘れない」という約束を人の注意力に任せず、precheck_build.mjs が機械的に強制します——2回目以降の提案で「前回からの変更」欄が空欄なら、確認画面そのものが生成できずに止まります(exit 2)。本当に変更が無かった場合も、その旨を明示しなければなりません。書き忘れて素通りする経路が構造的に存在しない、決定論への昇格の好例です。
2つの違いを一言でいえば、konpe は「どの案か」を人が選ぶ道具、precheck は「この方針でよいか」を人が正す道具です。どちらもループ(第6章の内側ループ)の前段にあり、既存の採点の仕組み(state.json や verdict_check.sh)には手を入れません。
| konpe | precheck | |
|---|---|---|
| 案の数 | 2〜64 | 1 |
| 会場 | ブラウザ(候補グリッド) | HTML 1枚(フォーム) |
| 論点 | 軸(2〜5個) | 3±1問、各問に推奨1つ |
| 判定者 | 人間 / AI自動 / Claude+Codex混成(選べる) | 人間のみ |
| 人間の操作 | 1案を採用+理由 | 各問に回答、または「お任せ」 |
| 証跡 | decision.json | 回答済み HTML+反映プロンプト(2版目以降は撤回履歴つき) |
この2つの発想は、まさお氏が公開している bestofn(N案を人に選ばせる)と akapen(実装前に方針を確認する)から借りています。ただし bestofn はライセンス表記がなく再配布できないため、コードは1行も転用せず、「入力→振る舞い→出力」という挙動の約束事だけをクリーンルームで書き直しました。
konpe をブラウザなしの CLI 経路で1周します(会場 URL をブラウザで開いても同じ結果になります)。
cd ~/dev/dev-harness
W="$(mktemp -d)"; cd "$W"
printf '# 案A シンプル\n本文A' > a.md
printf '# 案B 情報量多め\n本文B' > b.md
node ~/dev/dev-harness/scripts/konpe.mjs new textbook-demo \
--spec "サンプルの見せ方をどちらにするか" --axes "シンプル,情報量多め" a.md b.md
node ~/dev/dev-harness/scripts/konpe.mjs pick textbook-demo 0 --note "シンプルな方が読みやすいため"
cat ~/dev/dev-harness/runs/textbook-demo/konpe/decision.json
decision.json に winner: 0 と理由が残ります。ブラウザで会場を見たい場合は同じ手順の途中で node scripts/konpe.mjs serve を実行し、返ってきた URL を開いてください(採用後は「選び直す」も試せます)。確認できたら runs/textbook-demo/ は削除して構いません。
次に precheck です。論点2問の questions.json を書いて HTML を作り、開いてフォームに答えず「回答プロンプトを生成」を押してみてください。
cat > "$W/q.json" <<'EOF'
{ "topic":"textbook-demo","revision":1,
"tldr":"サンプル画面の配置を決めてください。",
"body_html":"<p>案: ボタンを右上に置く。</p>",
"questions":[
{"id":"q1","title":"ボタンの位置","options":[{"label":"右上(推奨)","recommended":true},{"label":"左下","recommended":false}],"allow_note":true},
{"id":"q2","title":"色","options":[{"label":"青(推奨)","recommended":true},{"label":"緑","recommended":false}],"allow_note":true}
]}
EOF
node ~/dev/dev-harness/scripts/precheck_build.mjs "$W/q.json" "$W/out.html"
open "$W/out.html" # macOS。開けない環境は file:// のパスをそのまま使う
未回答のまま「回答プロンプトを生成」を押すと、両問とも「(推奨)」の案が採用された文面になることを確かめてください。
確認問題 9-1. konpe の生成係が互いの案を見てはいけない(ブラインド規律)のはなぜですか。依頼した側(オーケストレーター)も候補の中身を読んで自己採点しないのはなぜですか。
他の案を知った瞬間に比較して優劣をつけたくなり、人間が選ぶ前に事実上決まってしまうからです。好みが混入すると、人間が採用する前にAI側で結果が決着してしまい、konpe の存在意義(人間だけが判定する)が崩れます。依頼した側が候補を読んで自己採点しないのも同じ理由で、生成と評価を分けた第3章の原則を「人間が選ぶ」という工程にまで一段厳しく適用したものです。
確認問題 9-2. precheck で、人間の沈黙を AI 側が勝手に「お任せ」と判定して実装を進めてはいけないのはなぜですか。
お任せの確定はフォーム(機械)側の既定であり、AI が独断で判定するものではないからです。回答が届いていない状態で先に進めると、人間が想定していなかった前提のまま実装が進みかねず、「実装前に確認する」というゲートの意味が失われます。だから禁則は「回答が届くまで実装しない」であり、お任せは人間が回答を送らなかった結果として、機械(フォームの既定=推奨案)が確定させます。
確認問題 9-3. konpe の採用結果を decision.json という構造化データに残すのは何のためですか。第3章の verdict.json と共通する発想は何ですか。
採点係が後から「採用した案を土台に実装したか」を機械的に突き合わせられるようにするためです。第3章の verdict.json は、採点結果を感想文でなく構造化データにすることで採点係の自己申告に頼らない仕組みでした。decision.json も同じ発想を人間の判断に適用したもので、「誰が・どの案を・なぜ選んだか」を会話の記憶ではなくファイルとして固定し、後から検証できるようにします。
確認問題 9-4. AI自動判定(run-konpe-auto)を使ってよい題材と、使わずに人間判定(run-konpe)に戻すべき題材を、それぞれ具体例つきで説明してください。
AI自動判定は、良し悪しの基準をあらかじめ言葉(基準表)にできる題材に向いています。例えば「エラーメッセージの文言が用語集と一致しているか」のように、正解に近い基準を機械的に書き下せる場合です。一方、配色の好み・ブランドの「らしさ」・キャッチコピーの語感のように、良し悪しが人の感覚そのものである題材は、基準表に落とし込むと第5章のグッドハートの法則どおり点数だけが上がって本質からずれます。この場合は自動判定を使わず、人間が選ぶ run-konpe に戻すのが決まりです。
本文で教えた用語だけを、最初に出た章とともに並べます。
axes: 行と完全一致でなければ無効になる。threshold: 行に書く。decision.json に記録される。axes:(軸)と threshold:(合格ライン)の行+各軸のアンカーからなる。score・内訳 breakdown・具体的な feedback・合否 passed を持つ構造化データ。smoke ok。ref-)と、ファイルやコマンドに作用するスキル(ワークフロー型)。1本に混ぜない。bash scripts/smoke.sh を実行し、7項目の緑を見る。ref-harness-rules/SKILL.md を開き、ls ~/Downloads/skills の実在スキル名から接頭辞の責務を当てる。runs/sample-task/verdict.json を読み、verdict_check.sh で PASS を出す。tasks/sample-task.md と harness-settings/criteria/sample-task.md を並べ、スコープ遵守の上限則を見つける。fixtures/verdict-lying.json を番人に渡して INVALID(exit 2)を見る。書き換えて別の弾かれ方も試す。scripts/loop_check.sh を開き、点数と合格ライン・周と最大周回の整数比較を探す。scripts/browser_check.mjs・scripts/policy_gate.sh・scripts/procedure_check.sh(loop-demo と browser-auth の実走に検品をかける)を本文の試し方どおりに動かす。予習部分は run-skill-live-trial / run-skill-iter-improve(配布スキル)の SKILL.md を読み、第5・6章の原則との対応を取る。harness-settings/ を開き、顧客が触ってよい層の中身を確認する。scripts/konpe.mjs で2案から1案を CLI 採用し decision.json を読む。scripts/precheck_build.mjs で論点2問の確認 HTML を生成し、未回答のまま反映プロンプトが推奨案で埋まることを見る。axes:・threshold:・各軸のアンカー)を一枚書いてみる。この教科書の内容は、次の実物に基づいています。事実の創作はしていません。
~/dev/dev-harness(docs/HANDOVER.md・docs/handover.source.md・DESIGN.md・README.md・docs/konpe-precheck-spec.md・.claude/skills/*/SKILL.md・scripts/*.sh・scripts/konpe.mjs・scripts/precheck_build.mjs・harness-settings/・tasks/・runs/sample-task/verdict.json・scripts/fixtures/*)~/Downloads/skills(接頭辞命名の19スキル)/~/Downloads/eval-loop-main 2/plugins/eval-loop(ループ実装。参考コピー ~/dev/eval-loop-main)/~/Downloads/anti-goodhart-main 3/.claude/skills(live-trial・iter-improve)dev-harness 教科書 — AIに任せて、機械が合否を決める開発