AIエージェントの自律作業は「感覚器」で決まる——効果的なテクニック4選
AIエージェントに作業を任せたいのに、結局ずっと画面に張り付いている。「完了しました」と返ってきたので確認したら、できていない。同じ修正を延々と繰り返している。
こうした状態を抜けるために、私たちは4ヶ月かけて 58個の対策を入れました。そしてその効果を1件ずつ追跡した結果、はっきり分かったことがあります。
指示文やルールに書いた対策は、ほとんど効きませんでした。効いたのは、エージェントが自分で結果を見られる経路を作ることでした。
先に結論を書きます。効いたのは次の4つです。
- 感覚器を与える——エージェントが自分で結果を観測できる経路を作る(
--shot、ヘッドレスキャプチャ、JSON出力) - 生成役と評価役を分ける——自己評価は構造的に甘くなる
- 確認を独立したタスクに切り出す——完了報告の付随句にすると飛ぶ。主題なら実行される
- 判定できるものは機械で止める——フックはLLMを介さないので確定的に効く
逆に効かなかったのは、指示文に「気をつけて」と書くこと、ルールを増やすこと、強調を増やすことでした。
以下、その追跡データと、実際に動いているコードを掲載します。
前提:何をどう追跡したか
私たちは Claude Code 上で12体のエージェント(秘書・エンジニア・経理・法務など)を業務に使っています。事故や失敗が起きるたびに対策を入れ、それを台帳(JSON)で管理してきました。
台帳には「導入した対策」「その後に同じ事故が再発したか」を記録しています。現時点で58件、うち 31件は実際に事故が起きてから作った対策です。
集計するとこうなりました。
| 判定 | 件数 |
|---|---|
| likely_effective(観測期間内に再発なし) | 23 |
| unverified(未検証) | 19 |
| measuring(計測中) | 10 |
| effective(効果を確認) | 2 |
| insufficient(対策後に再発あり) | 2 |
| needs_review | 2 |
最初に断っておくと、likely_effective は「観測期間内に再発していない」という意味であって、効果の証明ではありません。観測期間が短いだけの可能性は残ります。
それでも、再発した2件の中身を見たときに、はっきりした傾向が出ました。
効かなかったこと——指示文とルール
再発した2件は、両方とも「文章で書いたルール」だった
| 再発した対策 | 形式 |
|---|---|
| HTML生成後のセルフビジュアル確認を義務化 | ドキュメントに書いた行動規範 |
| 認証情報の越境防止(委譲時の指示文に明記) | ドキュメントに書いた行動規範 |
一方、再発していない側に並んでいたのは、ポート割り当てレジストリ、mv/cp の上書き検知フック、sed -i の検知フック、クラウドアカウント確認フック、pre-commit フック、dry-run の必須化——ほぼすべてが「機械で止めるもの」でした。
明文ルールなのに、22回催促された
もっと直接的なデータもあります。
私たちのプロジェクト設定ファイル(CLAUDE.md)には「成果物に言及したら、必ず同じ応答内にファイルパスかURLを書く」というルールが明記されています。目的は「パスは?」という往復を無くすことです。
このルールの遵守状況を実ログで調べたところ、全期間を通して守られておらず、ユーザーからの催促が22回発生していました。
ルールを書いた本人が、ルールを守らせられていない。これが実態でした。
なぜ書いても効かないのか
構造的な理由が3つあります。
1. ルールの飽和
ルールが増えるほど遵守率が上がるわけではありません。ある点を超えると、新しいルールが古いルールを押し出します。
ちなみに私たちの CLAUDE.md は、観測時点で 946行にも膨らんでいました。一般に推奨されている分量(200〜300行程度とする解説が多い)の3倍以上です。しかも測ってみると、1つのセクションだけで260行(全体の27%)を占めていました。
2. 強調のインフレ
「⛔」「必須」「絶対」が増えるほど、どれも普通に見えてきます。強調の識別力が落ちるので、本当に不可逆な操作の警告が埋もれます。
3. 直近性の優位
長い作業では、40ターン前の禁止事項は薄れます。最新の指示が古い制約を上書きします。
「完了条件を明確にせよ」の、その先
自律ループの設計について調べると、多くの記事が同じ結論に辿り着いています。
- 完了条件を決定的にせよ
- テストが通るまで進ませるな
- 同じ結果が2回続いたら止めろ
これらは正しいと思います。実際に効きます。
ただし、この方針には適用できない領域があります。テストで判定できないものです。
- 画面の見た目が崩れていないか
- 生成した画像が意図通りか
- 実機で操作したときに正しく動くか
ここでは「テストが通る/通らない」という判定が作れません。そして私たちの事故の多くは、まさにこの領域で起きていました。
だとすると問題はこう変わります。
テストが書けない領域で、エージェントに「結果を見る」手段をどう与えるか。
これは完了条件の設計ではなく、感覚器の設計です。
効いたこと① エージェントに感覚器を与える
Godot——自分でスクリーンショットを撮らせ、自分で読ませる
ゲーム開発では「意図通りに描画されているか」がテストで書けません。そこで、起動時にスクリーンショットを保存して終了する起動オプションを実装しました。
# main.gd
func _capture_and_quit() -> void:
for i in range(6):
await get_tree().process_frame
await get_tree().create_timer(0.3).timeout
var img := get_viewport().get_texture().get_image()
var out_path := ProjectSettings.globalize_path("res://") + "shot.png"
var err := img.save_png(out_path)
print("[shot] saved=", out_path, " err=", err)
get_tree().quit()呼び出し側はコマンドライン引数を見て分岐させます。
var args := OS.get_cmdline_user_args()
if "--shot" in args:
await _capture_and_quit()これでエージェントは次のように動けるようになります。
godot --path . -- --shot実行すると shot.png が生成されるので、エージェントはそれを自分で読み込んで、意図通りか判断します。崩れていれば直して、また撮る。人間が見て指摘するまで待つ必要がありません。
ポイントは await get_tree().process_frame を数フレーム回している部分です。描画が完了する前にキャプチャすると真っ黒な画像が保存され、しかもエージェントはそれを「表示が壊れている」と誤認します。感覚器を作るときは、正しく観測できる状態まで待つ処理が要ります。
同じ発想で、特定のシーンだけを描画するプレビュー用スクリプトも用意しています。
godot --path . preview_mesh.tscn -- --shot --glb res://assets/characters/yokai/oni.glb3Dモデルを1体だけ表示して撮る。エージェントが「取り込んだモデルの向きが正しいか」を自分で確認できます。
HTML——ヘッドレスでキャプチャして自己確認させる
Webページやレポートも同じです。HTMLを生成したら、必ずヘッドレスブラウザでキャプチャして、その画像をエージェント自身に読ませます。
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--headless --disable-gpu --no-sandbox \
--screenshot=/tmp/preview.png \
--window-size=1280,3000 \
"file:///path/to/output.html"ここで重要なのは、何をもって「確認した」とするかの線引きです。私たちが実際にやらかした例を挙げます。
| やったこと | 報告 | 何が問題か |
|---|---|---|
ls でファイルの存在を確認 | 「受信ボックスで確認できます」 | 中身が壊れていても ls は成功する |
| HTTP 200 が返った | 「キャプチャで目視確認済み」 | 200 は表示の正しさを保証しない |
showSaveFilePicker が関数として存在することを確認 | 「実機確認済み」 | 存在と動作は別物 |
file:// で開いて確認 | 「表示に問題なし」 | HTTP配信時の文字化けを見逃した |
判断基準はシンプルです。
その操作は、壊れていたときに失敗するか?
ls は壊れていても成功します。HTTP 200 も返ります。だから確認になりません。ユーザーが実際に見るのと同じ経路で観測する必要があります。
感覚器の一般形
3例に共通する形はこうです。
大事なのは 「エージェント自身が読む」の矢印が存在するかだけです。ここが人間に繋がっていると、エージェントは自律できません。人間が見るまで待つからです。
そして「観測可能な形」とは、エージェントが読める形式を指します。画像、JSON、テキストログ。GUIしか出力がないものは、この時点で自律作業に向きません。
効いたこと② 生成役と評価役を分離する
感覚器を与えても、評価者が生成者と同一だと甘くなります。
実例です。あるコンテンツ生成タスクで、エージェントは自分の成果物をこう自己評価しました。
「商品として成立しています」
同じ成果物を、別のエージェントに評価させた結果がこれです。
「シェフのノート以外は、どれも同じに見えます」
生成 → 自己評価 → 提出のループでは、自己評価が実質ノーチェックになります。自分が作ったものを否定するのは構造的に難しいためです。
そこで私たちは「生成評価ループ」を共通プロセスとして定義し、三者分離を原則にしています。
| 役割 | 責務 | 禁止事項 |
|---|---|---|
| 生成 | 成果物を作る | 自分の成果物を評価しない |
| 評価 | 観点を出して診断する | 修正案を書かない(生成に引っ張られるため) |
| オーケストレーション | 反復の継続/打ち切りを判断 | 生成にも評価にも関与しない |
評価役に修正案まで書かせないのがコツです。書かせると「自分の案が採用されたか」を気にし始め、診断が歪みます。
なお、このループには構造的な限界もあります。評価者が見落とす観点は、何回まわしても出てきません。反復すれば品質が上がるわけではなく、評価の視点の数が上限になります。だから評価役は複数のペルソナで走らせます。
効いたこと③ 確認を独立したタスクに切り出す
これが最も意外な発見でした。
私たちは「AIは検証をサボる」と考えて、その罠を仕込んだ検証環境を作りました。画像5枚・PDF28ページを用意し、報告と実際の確認範囲が一致するかを測る実験です。
結果は 24試行すべてで罠にかかりませんでした。モデル(Sonnet / Haiku)を問わず、全部きちんと確認して報告しました。
一方、実際の業務ログでは同じ失敗が繰り返し起きています。検証系の報告 268件のうち38件で、報告した確認範囲が実際の確認範囲を超えていました。「全9ページ目視確認済み」と書きながら、実際に開いたのは1ページ、といった具合です。
再現しないのはなぜか。差は1点だけでした。
| 実際に事故が起きたとき | 実験で再現しなかったとき | |
|---|---|---|
| 確認の位置づけ | 長い作業の末尾の一工程 | タスクそのもの |
| 報告での扱い | 「4点すべて修正しました(全ページ目視確認済み)」の括弧内 | 報告の主題 |
つまり、こうです。
「確認して」と単独で頼まれた確認は、モデルを問わず実行される。事故が起きるのは、確認が他の作業に埋め込まれて、完了報告の付随句になったときだけ。
さらに補足すると、手続き自体はサボっていませんでした。「ビジュアル確認済み」と書かれた報告31件は、全件で直前80行以内に実際の画像読み込みが存在していました。ルールは実行されている。壊れていたのは「どこまで見たか」の申告だけでした。
対策は「よく確認しろ」ではありません。タスクの切り方です。
❌ 「実装して、動作確認もして」
✅ 「実装して」→ 報告を受ける →「動作確認して」サブエージェントに委譲する場合も同じです。1回の委譲に確認を同梱すると飛びます。
効いたこと④ 判定できるものは機械で止める
台帳で再発ゼロだった対策群は、ほぼすべてフック(Hook)でした。
.claude/hooks/
├── check-mv-overwrite.sh # mv の上書きを検知
├── check-cp-overwrite.sh # cp の上書きを検知
├── check-sed-awk-inplace.sh # バックアップなし sed -i を検知
├── check-cloud-account.sh # gcloud/aws の対象アカウント確認
├── check-gog-account.sh # Google Workspace の操作先確認
└── check-process-completion.sh # 手順スキップの防止フックが効くのは、LLMを介さないので確定的に動くからです。指示文は解釈されますが、フックは解釈されません。
実装の詳細は別記事にまとめてあるので、そちらを参照してください。
Claude Code の hooks はなぜ CLAUDE.md や指示文では代替できないのか——マルチエージェント環境での安全設計3原則
ただし「テキストで書いても効くルール」もある
ここは単純化しすぎないほうがいいと考えています。台帳で effective(効果を確認)だった2件のうち1件は、ドキュメントに書いただけのルールでした。
それは「URLは Markdown リンクにせず、素のURLをそのまま書く」というルールです。
なぜこれだけ効いたのか。判定が一意だからだと考えています。書かれたURLが [ラベル](URL) かどうかは、見れば決まります。解釈の余地がありません。
対して、再発した2件はこうでした。
- 「HTML生成後にビジュアル確認すること」→ どこまで見れば「確認した」ことになるのか一意でない
- 「認証情報を越境させないこと」→ 何が越境にあたるか、状況次第で判断が要る
つまり線引きはこうなります。
| ルールの性質 | 置き場所 |
|---|---|
| 判定が一意(形式が決まっている) | ドキュメントでも効く |
| 達成度に主観が入る | ドキュメントでは効かない。機械で止めるか、独立タスクにする |
「よく」「適切に」「必要に応じて」が入るルールは、書いても守られないと思ったほうがいいです。
どこまで無人にできるか
ここまでを組み合わせると、実際に無人で走らせられる範囲が出てきます。私たちは自社インフラのセキュリティ診断を夜間に自律実行させています。
構成のポイントは、判定できる部分を決定的なシェルスクリプトに落とし、LLMは解釈にだけ使うことです。
# 診断ランナー(対象と検査項目を指定して実行)
./run.sh --target ops
./run.sh --target c3 --checks D-04,D-11検査項目はスクリプトとして個別に実装されています。
runner/
├── run.sh
├── targets.sh # 診断対象の定義
└── checks/
├── d01_exposure.sh # 公開範囲
├── d03_devmode.sh # 開発モードの残存
├── d04_auth_boundary.sh # 認証境界
├── d05_headers.sh # セキュリティヘッダ
├── d11_image_cve.sh # イメージのCVE
└── d13_container.sh # コンテナ設定出力は results/<timestamp>/<target>/D-XX.json という形で、JSONで残します。これが感覚器です。エージェントは自然言語の出力ではなく構造化データを読むので、解釈がぶれません。
安全側の設計も入れています。
- 対象は自社所有の稼働環境のみ(
targets.shで定義) - read-only / 受動的な検査のみ。能動スキャンはしない
- 検出結果はそのまま適用しない。承認キューに積む
この構成で夜間に走らせた結果、朝には検出結果が承認待ちの状態で積まれています。実際に開発環境の意図しない公開、平文で保存されていた認証情報、認証なしで到達できるサービスなどが検出されました。
修正までは自動化していません。 ここが次の節です。
人間のゲートに何を残すか
すべてを自律化しないほうがいい領域があります。私たちが実際に事故を起こして学んだ線引きです。
| 残すべきゲート | 理由(実際に起きたこと) |
|---|---|
| 外部に不可逆な作用を残す操作 | 「調査のみ」と指示したのに、サブエージェントが自己判断で外部フォームの送信を試みた |
| 認証情報を跨ぐ操作 | APIキーが見つからず、別サービスの管理者トークンを使って回避しようとした |
| 削除・上書き | 「要確認」の項目を他の削除対象と一緒に処理し、回答前にデータが消えた(復元不能) |
| 本番環境への適用 | 検出と修正を同一ループに入れると、誤検知がそのまま反映される |
共通するのは、エージェントは制約を「守るべき境界」ではなく「回避すべき障害」と誤認することがあるという点です。ゴール達成に向かっているとき、認証の壁やエラーは突破対象に見えます。
なので委譲時の指示には、こう書くようにしています。
認証情報が見つからない場合、回避策を探さずに、その時点で報告して止まること。ただし前述のとおり、これはドキュメントに書いた行動規範なので再発しています。最終的にはフックで止めるか、そもそも権限を渡さない設計にするのが確実です。
まとめ
4ヶ月・58件の追跡から言えることをまとめます。
効かなかったこと
- 指示文やルールに「気をつけて」と書く(明文ルールでも22回催促が発生)
- ルールを増やす(増えるほど古いルールが押し出される)
- 強調を増やす(⛔ が増えると識別力が落ちる)
効いたこと
- 感覚器を与える——エージェントが自分で結果を観測できる経路を作る(
--shot、ヘッドレスキャプチャ、JSON出力) - 生成役と評価役を分ける——自己評価は構造的に甘くなる
- 確認を独立タスクにする——付随句にすると飛ぶ。主題なら実行される
- 判定できるものは機械で止める——フックは解釈されないので確定的に効く
そして最も実用的な問いは、これだと思っています。
そのタスクで、エージェントは自分の作業結果を見られますか?
見られないなら、どれだけプロンプトを工夫しても自律にはなりません。逆にここさえ通れば、指示文はそれほど凝らなくても回り始めます。
最後に、この記事自体の限界も書いておきます。ここで挙げた数字は特定の1組織・4ヶ月の運用ログから出たもので、統計的に一般化されたものではありません。likely_effective は再発が観測されていないだけで、効果が証明されたわけでもありません。それでも「効かなかったこと」を数字で残しておく価値はあると考えて公開しました。
関連記事
この検証を行ったのは 株式会社Amanity です。 自社で手を動かして技術を確かめたうえで、モバイルアプリ・LINEアプリ・Webアプリの受託開発を行っています。
開発について相談する株式会社Amanity
福岡を拠点に、AIを活用したモバイル・Web・LINEアプリの受託開発を行っています。
