この記事は、連載「言わない技術」の第 8 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。
今回は、AIの答えが返ってきた後の話です。前回は、答えが返ってくる前に、人間がAIに渡してしまう前提を見ました。今回扱うのは、返ってきた答えに「そうじゃなくて」とダメ出しをするときです。ダメ出しは書き換えられる —— してほしい形に、です。書き換えることができなかったダメ出しは、そもそも言葉でAIに守らせるものではありません。
前作「AIの意見を聞かない技術」の第 2 回で、禁止には理由を添えろと書きました。あれは、一覧に残す禁止の話でした。今回は、その場で口に出す禁止の話です。
まず一つだけ、数えてみてください。
数えてほしいのは、あなたが直近のセッション(AIとの、始まりから終わりまで一続きのやり取り)で、AIに「そうじゃなくて」「それは違う」と打った箇所です。そのうち、代わりにどうするかまで打ったのは、何回でしょう。
CC BY 4.0
行動ルールの禁止 16 件のうち、15 件には「代わりにどうするか」が書いてありました
が数えたのは、AIに渡している行動ルールのほうです。行動ルールは 43 件あり、1 件ごとに 1 ファイルに書いてあります。セッションの最初に読み込まれるのは、その索引だけです。索引には、ファイル名と一行の要約が並んでいます。ルールの本文をAIが読むのは、そのファイルを開いたときです(「AIの意見を聞かない技術」の第 1 回で測ったとおりです)。
数える前、は、一覧には否定形の禁止ばかりが並んでいると思っていました。
| 件数 | |
|---|---|
| 行動ルール(総数) | 43 |
| そのうち、要約が否定形(〜禁止 / 〜しない / 〜してはならない) | 16 |
そのうち、**How to apply:**(代わりにどうするか)を持つ | 15 |
例えば、要約が否定形のルールの 1 件は、次のように書いてあります。
description: エラーを握りつぶさない。不完全データで機能させず、明確にエラーを出す。
データが期待フォーマットでない場合、フォールバックで動作させてはならない。
**Why:** ユーザーが問題に気づけない / 開発者にエラーが届かない / 不完全なデータで
中途半端に動作し、体験を悪化させる
**How to apply:**
- 期待するデータがなければ console.error + ユーザーに見えるメッセージで通知
- 「何も表示しない」より「エラーを表示する」方が常に良い
上半分が禁止で、下半分が「代わりにどうするか」です。Anthropic のプロンプトの指針は、応答の書式を指示するときの勧めとして、同じ向きの書き方を挙げています。してほしくないことではなく、してほしいことを書く、というものです(「markdown を使わないで」の代わりに「滑らかにつながる散文の段落で」)。手元の一覧の 15 件は、その向きの書き方に、すでになっていました。
禁止の一行だけが届くと、何が禁止に入るかと、代わりに何をするかは、AIがその場で決めます。前作の第 2 回では、「思いつきや『ついでの改善』は禁止」という SEO の禁止事項の一行を、AIが、使われていない翻訳キーを削除する作業に当てて、「削除をためらったほうがいい」と提案してきました。禁止事項の文面には無い言葉です。「代わりにどうするか」が同じファイルにあれば、AIはそのファイルを開いたときに、禁止と一緒に、してほしい形を読みます。
「代わりにどうするか」を書くことができなかった 1 件は、自動テストへ移してありました
残る 1 件のルールを開くと、本文は 1 行だけでした。その 1 行は、自動テストのファイル名です。このルールは、「代わりにどうするか」を書くことができなかったので、自動テストへ移してありました。
行動ルール全 43 件で見ても、同じでした。「代わりにどうするか」を持たない 6 件のうち、5 件には「検査へ移した」と書いてあります。5 件の形は、どれもこうです。
description: 非 GET の fetch には X-CSRF-Token が必須。検査へ移した
**検査が見ている**: test/reference/memory_rule_csrf_token_test.rb
言葉で渡したルールは、AIが破っても、何も止めません。第 5 回では、が「最重要」と書いた節のルールが、残っていた 5 週間の記録で 6 回破られていました。6 回とも作業はそのまま先へ進み、が回数を知ったのは、記録を検索したときです。自動テストへ移したルールは、AIがその行を読んだかどうかとは別に、破られれば落ちます。
は、AIの答えが、してほしいものと違っていても、「そうじゃなくて」「〜しないでください」とは言わなくなりました。行動ルールの一覧にも、否定形の一行だけで残っている禁止はありません。否定形をやめて済んでいる理由として、あなたなら次のどちらを考えますか。
- AIは否定形を読み取りにくいので、肯定形で書いたほうが伝わるから
- 否定形の一行だけだと、代わりに何をするかを、AIがその場で決めることになるから
答えは、後者です。前作の第 2 回のAIは、禁止の一行を読んで、翻訳キーを削除する作業に当てていました。否定形は読み取れています。欠けていたのは、代わりに何をするかで、AIはそこを「削除をためらったほうがいい」という、禁止事項の文面に無い言葉で埋めました。読み取りにくさが理由なら、「〜しない」を「〜を避ける」と言い換えれば足ります。けれども、言い換えても、代わりに何をするかは書かれないままです。「代わりにどうするか」が書いてあれば、AIはそれに沿って進みます。書くことのできない禁止を言葉のまま残すと、破られても何も止まりません。第 5 回の 6 回も、作業はそのまま先へ進みました。自動テストへ移せば、破った変更が落ちます。否定で言っていた役を受け持つのは、してほしい形と自動テストです。
理解したことは一文にできます。
ダメ出しは、してほしい形に書き換えられる。書き換えることができなかったダメ出しは、言葉でAIに渡す代わりに、自動テストへ移す。
「代わりにどうするか」が無いと数えた 6 件のうち、1 件は書式が違うだけでした
正直に書きます。は、上の「6 件」をコマンドで数えたあと、6 件のファイルを開いて中身を読みました。そのうち 1 件は、数え間違いでした。
そのファイルには、**How to apply:** という節がありません。その代わりに、「E2E でしか担保できない領域」「単体テストで完結させる領域」という 2 つの節があります。ファイル全体が、テストをE2Eと単体テストのどちらに書くかを決める表でした。中身は最初から最後まで「代わりにどうするか」で、違ったのは書式だけです。
コマンドが数えることができるのは、節の名前までです。「代わりにどうするか」も自動テストも持たないルールは、0 件だったことになります。
仕組み: 「代わりにどうするか」の無い禁止を、保存の直後にAIへ突きつける
手元では、AIが行動ルールを保存すると、その直後に差分をAIへ突きつける監査が動いています(「読まない技術」の第 2 回で置いたものです)。今回置くのは、この監査に足すコードです。
# .claude/hooks/audit_memory_write.py(手元のコードを短くしたもの)
PROHIBITION = re.compile(r"禁止|するな|してはならない|使わない|出さない|べきではない|しないこと")
REPLACEMENT = "**How to apply"
# ⚠️ 差分ではなく保存後のファイル全体を見る。How to apply は前のセッションで
# 書かれていることがあり、差分だけ見ると「無い」と誤検出する
unreplaced = sorted({
os.path.basename(path)
for path, _ in changes
if PROHIBITION.search(read(path) or "") and REPLACEMENT not in (read(path) or "")
})
if unreplaced:
parts += [
f"⚠️ **prohibition with no replacement**: {', '.join(unreplaced)}",
" there is a prohibition, but no `**How to apply:**` (what to do instead).",
" if you cannot write one, move it to a check. left as a prohibition, nobody sees whether it was kept.",
]
このコードが見るのは、禁止と同じファイルに「代わりにどうするか」、つまり置き換え先があるかどうかだけです。
この警告は、保存の後に出しています。「読まない技術」の第 8 回では、同じ方針でも、差し込む瞬間によってAIの動きが違い、「方針の中身は同じで、違ったのは、読ませた瞬間です」と書きました。ここでの方針は、この警告の文です。
この監査は、初めは保存の前に割り込んで、保存を止めていました。そのときのAIは、語を言い換えるだけで、その hook を素通りしました(「読まない技術」の第 2 回)。保存を止める hook は、止めた呼び出しを通す条件として、方針を見せることになります。保存の後には、止める呼び出しがありません。AIの前に並ぶのは、保存されたファイルと、その禁止に「代わりにどうするか」が無いという警告です。残るのは、「代わりにどうするか」を書き足すか、自動テストへ移すかです。
flowchart LR P["保存の前に差し込む"] --> P1["保存を止め、通す条件として方針を見せる<br/>方針は、通るための攻略対象になる"] --> P2["語を言い換えて素通り"] Q["保存の後に差し込む"] --> Q1["保存は済んでいて、<br/>止める呼び出しが無い"] --> Q2["書き足すか、自動テストへ移すか"]
CC BY 4.0 はここまで
言わなくなったもの
は、「そうじゃなくて」「それは違う」「〜しないでください」を言わなくなりました。AIの答えが、してほしいものと違っていることは、前と同じだけ見つかります。変わったのは、書くものが、否定形から、してほしい形になったことと、してほしい形を書くことができないときに、言葉で書くのをやめたことです。
CC BY 4.0
注意: 監査が見ているのは 7 語だけです
一つ、この監査が見ているのは、禁止の語形だけです。語形を見ているのは 7 語の正規表現なので、AIが禁止を「〜は好ましくありません」と言い換えれば、監査を抜けます。第 1 回と第 5 回で置いた自動テストも、語形だけを見ていて、言い換えた行を見逃します。同じ構造の抜け穴は、これで 3 度目です。
今回は、コマンドも監査も節の名前だけを見ているので、誤検出が出ることも分かりました(上の、書式が違うだけだった 1 件です)。
二つ、行動ルール 43 件は、多いほうです。小さなプロジェクトなら 5 件で足ります。問われるのは、1 件目のルールを書くときに、否定形のまま書き終えるかどうかです。
三つ、AIは、書いた「代わりにどうするか」に沿って進みます。それが最善でなければ、最善との差が、そのまま出力の差になります(第 2 回)。
「代わりにどうするか」を書くのは、自分が本当に知っている場所に限る必要があります。知らない場所なら、書くのは制約と確かめ方だけにします。
四つ、「代わりにどうするか」を消したときにAIがその場で何を埋めるかは、この一覧では測っていません。測ったのは、前作の第 2 回の、結論の一行だけが届いた 1 件です。
検証手順: 禁止だけを保存して、保存の直後に警告が出ることを確かめる
前提
保存の直後に、差分をAIへ突きつける監査(PostToolUse に当たるhook)が、既に動いている状態から始めます。その監査には、「代わりにどうするか」の無い禁止を見るコードを足し終えておきます。試すのは使い捨ての場所にしてください。本物のルール置き場は、複数のセッションが共有しているので、試し書きが隣のセッションに届きます。
所要時間: 10 分
手順
- AIに、行動ルールを 1 件、禁止だけ書いて保存させます(「〜を作るのは禁止」の 1 行だけ。「代わりにどうするか」は付けません)。禁止の語(禁止 / しないこと / してはならない など)を、必ず 1 つ入れてください。監査が見ているのは、その語形です
- AIに、同じルールへ
**How to apply:**(代わりにどうするか)の節を足して、保存し直させます
合格条件(すべて満たすこと)
合格の条件は 3 つです。手順 1 では、保存が済んだ後に警告が出て、その警告に prohibition with no replacement の行と、そのファイル名が出ていることを確かめます。手順 2 では、警告が消えることを確かめます。
合格しなかったとき
- 保存の前に止まった —— 登録したのは保存前のhookです。割り込む位置が違います
- 手順 2 でも警告が出たまま —— 監査が探している節の名前と、AIが書いた節の名前が食い違っています
後始末
手順 1・2 で作ったルールのファイルを消します。
CC BY 4.0 はここまで
次回は「AI に考えさせない技術」。は、AI に考えさせていません。 それでも、AI の答えは正しいままです。
連載「言わない技術」
- ← 前回: 第 7 回 AI を問い詰めない技術
- → 次回: 第 9 回 AI に考えさせない技術
- 全回の一覧: 序論