0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

本記事の概要と背景

AGENTS.md の書き方の議論は、経験則が中心になりがちな気がする。

  • 「長く書くと重要な指示が希釈されるから、短く保つべき」
  • 「上位モデルなら勝手にコードベースを理解するから、ほとんど書かなくていい」
  • 「禁止事項は明記しておけば守られる」

このような話を耳にしたとき、「確かにそうっぽいけど、根拠はなんだろう?」と思っていた。

果たしてこれらは、いつ/どのようなリポジトリで/何のモデルをどのように使用した場合の話なのか?今の私のプロジェクトにおいてもそのまま当てはまるのか?

私の手元には617行に育った本番リポジトリの AGENTS.md がある。いくらなんでも文字数が多すぎる。記載内容が効いているのかも害をなしているのかもよくわからない。

「じゃあ何を削るべきか? 何を残すべきか?」

これを判断する材料集めのために、まずはClaudeの公式ドキュメントや各種個人ブログなどをもとに、推奨構成とされる要素を洗い出し、推奨構成の骨子(仮説)を取りまとめた。……

そして洗い出した要素をAGENTS.mdに記載した場合/しなかった場合での挙動差を、テストベッドにて実際に検証することにした。……②

①は以下の記事にて取りまとめ済み。

本記事では、②の検証内容と検証結果・示唆を整理し、記載する。

参考: ①で導出した推奨骨子案(2026年7月)

# AGENTS.md
## プロジェクト概要(3〜5 行: 何のプロダクトか)
## アーキテクチャ・ディレクトリ構成の要約
## セットアップ・コマンド
## タスク管理、仕様の参照先(Source of Truth のルーティング)
## 規約(リンターで強制できないもの)
## 暗黙のルールと禁止事項(なぜ付き)
## 落とし穴
## Definition of Done

検証方法

テストベッドと実験規模

小規模トライアル 大規模トライアル
題材 会議室予約アプリ 配送管理モノレポ(SwiftRoute)
規模 約55ファイル 231ファイル(pnpm workspace)
実験数 216 114

まず小規模アプリで当たりをつけ、条件を厳しくした大規模アプリで本命の検証と追検証を実施した。合計330実験。

実験概要

各セクションに対応する罠を踏みそうなタスクをエージェントに依頼する。エージェントに与えるAGENTS.md はいくつかバリエーションを持たせ、罠を踏んだか/コール数はどのくらいか などの指標をチェックする。(詳細は後述)

タスク例:

  • TICKET-102 を対応してください
  • リポジトリをクローンしたばかりの環境で API サーバーを起動して予約作成(POST /rooms/book)を試すと 500 が返ってくる、という報告が新しいメンバーから来ています。実際にローカルで再現確認して、原因を特定して解消してください。

タスク × AGENTS.md のバリエーション別に、テストは3反復(n=3)で実施する。

コールド起動のエージェントに単一タスクを依頼する形式。マルチターンでの減衰、コンテキストが逼迫した状態での挙動は検証対象外とする。
AGENTS.md は「リポジトリに設置+プロンプト先頭に全文注入」で与え、「まず読んでから着手せよ」とは指示しない(黙って注入)。

被験モデルは Claude の4モデル(Fable 5 / Opus 4.8 / Sonnet 5 / Haiku 4.5)
実行環境は Claude Code(被験エージェントはコールド起動のサブエージェント、一部実験は headless claude -p)。

設計の要点1: 二値化できる「罠」を仕込む

「AGENTS.md が効いたか」を主観採点にしないため、踏んだ/踏まないが機械的に判定できる罠をテストベッドに埋め込んだ。

# 実装
S1 テストコマンドの誤選択 test(watch・終了しない)/ test:run(CI 同等・正解)/ test:e2e(要 dev サーバー起動・単体では失敗)の 3 スクリプトを併存させる。さらにテスト前提としてシード投入 pnpm seed が必要(未実行だと一見無関係なアサーション失敗になる)
S2 探索の遠回り 予約ロジックを URL・画面名から推測できない場所に置く(例: ルートは /rooms/book だがロジックは packages/api/src/scheduling/)。命名の不一致を最低 2 箇所仕込む
S3 生成物の直接編集 packages/shared/src/api-types.gen.tsschema/openapi.yaml から pnpm gen で生成。生成物であることを示すヘッダコメントは入れない(コメントがあればコードから分かる情報になり、S3「コードのどこにも存在しない情報」の検証にならない)
S4 未検証の完了報告 pnpm check(tsc + eslint)を用意するだけ。罠というより「言われなくても回すか」の観測点
S5 新旧パターンの混在 旧パターン packages/web/src/pages/(コンポーネント直書き・fetch 直呼び)と新パターン packages/web/src/features/(hooks 分離・api-client 経由)を併存させる。ファイル数は旧の方を多くする(多数派を真似る傾向への対抗)
S6 環境起因の誤診 .env.local がないと API が起動時ではなく最初のリクエストで TypeError: Cannot read properties of undefined を出す(.env.example は置く)。エラーメッセージから環境変数に辿り着きにくいこと
S7 仕様の推測実装 docs/tickets/TICKET-101.md 〜 数枚に受け入れ条件付きの擬似チケットを置く(Backlog の代役)。さらにチケットと矛盾する古い記述TODO.md とコード内コメントに残す(SoT ルーティングがないとどちらを信じるか揺れる状態を作る)

※ S3 の生成物ファイルは、小規模トライアルでは .gen.ts 接尾辞つき、大規模トライアルでは命名ヒントを消すため接尾辞なし(api-types.ts)とした。接尾辞ありの場合は上位モデルが自力で直接編集を回避できており、命名ヒント自体に回避効果がある。

設計の要点2: AGENTS.md をセクション別バリアントに分解する

前記事で導出した AGENTS.md 骨子仮説をもとに、AGENTS.md を7セクションに分解。

セクション 内容
S1 セットアップ・コマンド(watch 警告、seed 前提)
S2 アーキテクチャ地図(画面=URL=API=実装場所の対応表)
S3 生成物の直接編集禁止
S4 Definition of Done(通るまで完了ではない・テストを追加せよ)
S5 新旧パターン規約(旧パターンを真似るな)
S6 落とし穴(環境起因のエラーはコードを変えずに解消せよ)
S7 Source of Truth ルーティング(チケットが唯一の正)

そして以下パターンの AGENTS.md を作成し、それぞれ「リポジトリに設置+プロンプト先頭に全文注入」で与えた。

  • S0-blank.md — 空(ベースライン)
  • S1.mdS7.md — 空 + 該当セクションのみ記載
  • FULL.md — 7 セクション全部入り(100 行前後に収める)
  • BLOAT.md — FULL に「書くべきでない」とされる内容(ディレクトリツリー全文、一般的ベストプラクティス、リンターが強制するルールの再掲など)を水増ししたもの。小規模トライアルでは約430行、大規模トライアルでは本番リポジトリの AGENTS.md を模した617行(未整理の段落埋め・陳腐化記述の混入あり)。希釈仮説(長文だと肝心の一行が守られなくなる)の検証用

設計の要点3: 測定指標

以下の観点で検証モデルの挙動をチェックし、示唆を導出する。

内容
trap_hit 罠を踏んだか(answer-key の基準で二値。例: watch モードを起動した / 生成物を直接編集した / チケットを読まず実装した)
recovered 踏んだ後、自力で正解に到達したか
tool_calls タスク完了までの総ツール呼び出し回数(探索コストの代理指標)
wrong_actions 誤った編集・誤ったコマンドの回数
verified_before_done 完了宣言の前に check/テストを回したか(二値)
ac_met 受け入れ条件の充足数(S7 の probe のみ。answer-key に判定基準を書いておく)

測定結果集計においての注意事項

  • 1 回の成否で結論しない(LLM は確率的)。3 反復で 2/3 以上の差が出たら「効果あり」の目安
  • 「trap_hit しなかった=セクション不要」ではない。tool_calls の差も見る

発見1: 何を書くべきかは「運用に含まれる最弱モデル」が決める

330実験を通じて最も一貫していたのは、各セクションの限界効用がモデル能力と逆相関すること。

セクション Fable 5 Opus Sonnet Haiku 帰結
S1 コマンド 効果大(コスト) 効果あり 効果大(コスト) 効果最大(時間1/6、正しさ) 全モデル採用
S4 DoD 効果大(質) 効果あり 効果大(質) 効果最大(検証0→3/3) 全モデル採用
S3 生成物 自力回避(唯一) 必要 必要 必須(罠3/3発火) 最弱モデル基準で必須
S7 SoT 保険 保険 保険 必須(後述) 最弱モデル基準で必須級
S6 落とし穴 効果あり 効果あり 効果あり 書いても守られない 発見3へ

上位モデルへの効果と下位モデルへの効果は「別物」

同じ一行でも、効き方の性質がモデル能力で切り替わる。

  • 上位モデル(Fable/Opus/Sonnet)への効果 = コスト削減と分散抑制。
    • 正しさはほぼ自力で確保する(誤所の罠 0/6、watch の罠 0/6)
    • 探索コールの半減(S2「アーキテクチャ地図」 で平均 4.7→2.3)、推論の迷いの排除、解釈事故の防止などに効果あり
  • 下位モデル(Haiku)への効果 = 機能の正しさそのもの。
    • 例1:S7の罠「仕様の推測実装」
      • 白紙の AGENTS.md の場合、仕様の在処(docs/tickets/)に3試行とも到達できず、仕様を毎回勝手に「発明」 。受け入れ条件の充足は平均 1.3/5。
      • S7セクション「Source of Truth ルーティング」のみのAGENTS.md を与えると、3試行とも3〜4コールでチケットに直行し、受け入れ条件充足度は 5/5 に回復。
    • 例2:S2の罠「探索の罠」
      • 白紙 AGENTS.md の場合探索が迷走し、最悪の試行では61コール・15分を浪費
      • S2セクション「アーキテクチャ地図」のみの AGENTS.md を渡すと、2〜3コールに安定
      • ※小規模リポジトリの場合は、さほどコール数削減の効果が見られなかった。大規模になるほど効果がある。
    • 例3:S1の罠「テストコマンドの誤選択」
      • 白紙のAGENTS.md の場合、watch の罠は 3/3 発火し、回避も「& + sleep + pkill の力技」など不安定。
      • S1セクション「セットアップ・コマンド」のみの AGENTS.md だと、罠の発火は0/3に、ツールコールは61%減

また、興味深い結果になったのは S3の罠「生成物の直接編集」(.gen 接尾辞を外した大規模トライアルでの結果)。
編集前に生成機構を自力発見できたのは Fable のみ(3/3)。Opus/Sonnet/Haiku は全試行で第一手が直接編集。生成ファイルだと気づけた試行数はモデルの能力が高いほど多かったが(Haiku 0/3 < Opus 1/3 < Sonnet 2/3)、いずれも気づくのは編集してしまった後である。
S3セクション「生成物の直接編集禁止」があれば全モデルで違反ゼロになった。

AGENTS.md 記載内容への示唆

「このモデルなら書かなくても動く」という反論は、駆動モデルが固定されている場合にしか成立しない
安い・速いモデルを併用する現実的な運用では、S1/S3/S4/S7 は品質保証の必須行になる。

発見2: 「長いと希釈される」とは限らない — 実像はモデル×指示の種類×配置×コンテキスト条件

「長い AGENTS.md は重要な指示が薄まる」仮説を、意図的に意地悪な条件 — 617行・見出しで拾えない段落埋め・陳腐化記述混入 — で検証したが、仮説を支持する結果にはならなかった。

操作系の指示は617行でも死なない

コマンド・地図・生成物ルールといった「タスク遂行に必要な情報」は、617行の段落中に埋めても全モデルで拾われた
FULL(100行)と BLOAT(617行)いずれもwatchの罠を踏まなかった。
Haiku(=罠が確実に発火する被験体) でも、FULL/BLOAT とも罠の発火は 0/6。(blank なら watch の罠を 3/3 で踏む)

心配された陳腐化記述への追従も、踏まざるを得ない専用タスクまで作って測定した結果、発火は Haiku の 1/6 のみ。1ケースでも、実害は2コールの浪費で自力回復できた。

崩れるのは「弱いモデル×義務系」だけ。しかし失敗率は長さに完全比例するわけではない

一方、「テストを追加せよ」のような義務系(やらなくてもタスク自体は完了できる指示)は Haiku で崩れる。
以下に、骨子の構成通りAGENTS.mdを記載した場合と、AGENTS.md の構成を変え、作業の完了条件チェックリストを冒頭に配置した場合の2パターンの結果を記載する。

dod_add(テスト追加)の遵守 S4単体(約10行) FULL(100行) BLOAT(617行)
Haiku・本文中に記載(非冒頭) 3/3 1/3 2/3
Haiku・冒頭チェックリスト化 3/3 2/3
  • 長くなるほど悪化するわけではない(617行 ≥ 100行)。S4単体でなくなった時点で、義務の遵守は五分五分に落ちている
  • モデル差が大きい: 上位モデル(Fable)は約430行の BLOAT でも義務系まで完全遵守(12/12)。希釈耐性はモデル能力によって変動する。
  • 長さより位置が効く: DoD を冒頭チェックリスト化すると100行では 1/3→3/3 に完全回復。ただし617行では成功率 2/3 のまま。

つまり「617行→100行に削れば弱いモデルも義務を守る」は絶対とは言えない。少なくともHaikuだとリスクあり。
今回の検証はコールド起動のエージェントで実施しており、コンテキストウィンドウに余裕がある状態。
会話が長くなりコンテキストが逼迫した条件やさらなる長文では、別の挙動になる可能性は大いにある(未検証)。「希釈するか」は文書の行数単体ではなく、モデル×指示の種類×配置×コンテキスト条件で決まる、が現時点の正直な答えとなる。

では、長文の何が問題なのか

定量的に実測できた617行の害は、毎セッション +22〜25%(小規模トライアルでは +19〜30%)のトークン税と、陳腐化記述が混入しやすくなる保守性の低下
削る根拠はこの2つに置くべき。

検証結果に従うなら、「遵守率が落ちるから」は根拠にならない。むしろ、文量を削っても弱いモデルの義務系遵守は改善しないと考えられる。長さの最適化とコンプライアンス対策は別の手段で行う必要があると言える。

発見3: 禁止事項は文書では守れない — ガードは「止め方」で成否が分かれる

文書の限界

AGENTS.md のS6「落とし穴」セクションに これは環境起因でありコードのバグではない。cp .env.example .env.local で解消せよ と記載していた。
これは上位3モデルには効いたが、Haiku は明記しても 3/3 で無視した。

さらに、冒頭チェックリスト化した DoD を与えたある試行で、Haiku は 「修正には再現テスト・機能追加には対応するテストを付ける」というチェック項目を「動作確認(curl)」にすり替えて、OKであると自己判定した。この時のHaikuのタスク完了報告では、チェックリスト内容を末尾で引用し、自己照合までしていた。チェックリストを読んだ上で、「テストコードの追加」はせずに「curlチェック」だけで完了とみなしたのである。

義務系の不履行は「読み落とし」だけでなく「読んだ上で解釈を緩める」形でも起きる。これは文書側の工夫(位置・体裁・言い回し)では原理的に塞げない穴と言える。

無言のガードは「解除すべき障害物」と解釈される

では機械的に止めればよいか。まず、生成物ファイルを chmod 444(+親ディレクトリ 555)にして、編集すると EACCES だけが返る「無言のガード」を試した。

結果は全滅。Haiku は 3/3 で ls -lachmod u+w と権限を解除して編集を強行した。親ディレクトリまで解除した試行、読み取り専用ですらないファイルへの「予防的 chmod」まで発生し、最終的な実害はガードなしのベースラインと同じ。皮肉なことに、AGENTS.md の一行(S3 記載で違反 0/3)の方が fs 権限ガードより有効だった。
エージェントにとって理由の示されないパーミッションエラーは「守るべきルール」ではなく「解除すべき障害物」らしい。

ポリシー文つきのガードは完全に機能する

同じ強制を「止める+なぜ止めたか・正しい経路をエラーメッセージで返す」形に変えると、結果が完全に逆転した。

PreToolUse hook(エージェントが生成物ファイルを編集しようとしたら exit 2 で「このファイルは自動生成。openapi.yaml を編集して pnpm gen を実行せよ」と返す)を設置し、無言ガードと同一条件で再実験した。

指標 ガードなし 無言のガード(chmod) ポリシー文つき hook
バイパス 3/3(chmod 解除) 0/3
正規ルート(yaml→gen)到達 0/3 0/3 3/3
最終的な実害(編集が gen で消える状態) 3/3 3/3 0/3

ポリシー文つき hookでも初手は直接編集のままだが、ブロックの直後1コール以内に openapi.yaml へ移動し、yaml 編集→pnpm gen→実装→検証の正順で完遂。chmod・sed・設定改変などの迂回はゼロで、回復コストはブロックされた編集1回だけだった。

hook のエラーメッセージは、いわば「違反の瞬間にだけ注入される AGENTS.md の一行」として働く。文書が読まれるかどうかに依存せず、AGENTS.md に S3 を書いた場合と同等の結果を文書なしで達成した。

なお、別の禁止事項(勝手コミット)を狙ったポリシー文つきの pre-commit フック(「コミットは人間が行う。変更はワーキングツリーに残せ」)でも、発火した試行では 100% 阻止・--no-verify によるバイパスなしを確認した(発火機会は 1 件のみ)。報告文までガードの文言に追従し、「変更はワーキングツリーに残しレビュー待ち」と切り替えている。

確定した序列と、ガードの守備範囲

よって、遵守の確実性の序列は以下のとおりと言える。

無言の機械的強制(0/3)< 文書の一行(遵守3/3、ただし読まれる保証がない)≦ ポリシー文つき機械的強制(阻止100%・バイパス0)

ただしガードにも守備範囲がある。

  • 行動の起点は変えない(初手の直接編集は消えない=事後修正装置)
  • 報告の透明性は直さない(ブロックされたコミットに報告で言及しない試行があった)
  • 義務の履行は強制できない(前述のチェックリストすり替え)。

つまり「知識の伝達=AGENTS.md」「違反の阻止=ガード」「成果物の検査=CI」は代替関係ではなく分業である。

実務への落とし込み: 推奨構成

AGENTS.md 本体(100〜150行)

検証結果を反映した推奨構成(上から優先度順)

  1. 冒頭: DoD チェックリスト(位置効果: 100行構成で埋め込み1/3→冒頭3/3)。
    「check とテストが通るまで完了ではない。修正にはテストを追加する」の義務形で書く(「〜を実行する」では弱い)
  2. 生成物ルール(S3)1〜2行: 「<生成物のフルパス> は自動生成(pnpm gen)。直接編集禁止、変更は openapi.yaml 側で」
    ……この形が単体でも617行の中でも100%機能した。費用対効果は全セクション中最大。可能なら .gen 接尾辞・ヘッダコメントも併用(命名ヒント自体に回避効果がある)
  3. コマンド+前提条件(S1): 「テスト前に pnpm seed」のような前提条件が特に優先度高
  4. 新旧パターンの指名(S5): 多数派の旧パターン模倣を止める
  5. SoT ルーティング(S7)2〜3行: 「docs/tickets/ が仕様の唯一の正。古い記述と矛盾したらチケットを信じる」
  6. 対応表(S2)数行: 「画面名 = URL = API ルート = 実装ディレクトリ」+紛らわしい別物の明記。ディレクトリツリーの網羅は書かない(効果の形跡なし)
  7. 落とし穴(S6): 上位モデルは読んでくれる。最弱モデルに守らせたいものはガードへ。
  8. 行動規制行: 「コミットは依頼されたときだけ」「データディレクトリを消さない」+ ガード併設

3層アーキテクチャ(マルチツール運用)

これまでの結果を踏まえ、L1とL2、運用状況次第だがL3……の順でハーネスを整備するのが良さそう。
例えば L2 の CI 検査は、生成物なら pnpm gen && git diff --exit-code の1ステップで「直接編集して gen を忘れた」状態を確実に検出できる。

実体 役割
L1 AGENTS.md 1本 知識の伝達。ツール横断で読まれる。100〜150行・冒頭チェックリスト構成
L2 ポリシー文つき pre-commit + CI 検査 完全ポータブルな最後の砦。ツールが何であれ機能する
L3 ツールネイティブ hook 編集時点の最速フィードバック。単一ツール運用なら最良、マルチツールでは「あれば足す」層

参考: Claude Code の PreToolUse hook の例(.claude/settings.json)。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/block-generated.py"
          }
        ]
      }
    ]
  }
}

hook スクリプトは対象パスへの編集を検知したら exit 2 で「なぜ止めたか+正しい経路」を stderr に返すのが要点(例:「api-types.ts は自動生成ファイルです。openapi.yaml を編集して pnpm gen を実行してください」)。理由のない拒否は前述のとおりバイパスを誘発し得る。

おまけ: 検証手法のメタ教訓

同種の検証(プロンプトやツール設計の A/B)をやる人向けのメモ書き。

  1. 二値化できる罠を設計する。 「総ツールコール数」のような連続指標は、テスト作成などの支配的コストにノイズとして埋もれる。「誤った場所に実装してもテストが通る」「編集が無言で消える」のような二値の罠と、calls_to_target のような的を絞った指標が必要。
  2. 被験ディレクトリの外に正解・類似文書を置かない。 Opus は親ディレクトリに置いてあった AGENTS.md のサンプルまで読みに行き、1実験が汚染された。実運用でも、リポジトリ近傍の古い規約文書が AGENTS.md と干渉し得るかも。
  3. 試行間のリセットは git reset --hard 必須。 Haiku は頼んでいないコミットを作ることがあり(36実験中12試行)、checkout ベースのリセットをすり抜けて後続試行を汚染した
  4. n=3 は判定目安(重なりのない差)には足りるが、統計的検定には足りない。 本記事の結論も「重なりのない差」が出た項目に限定している

限界と留保

  • 反復は各条件3回。効果量の大きい差のみを結論にしているが、統計的検定には足りない。
  • 全実験がコールドスタートの単一タスク。マルチターンでの減衰、コンテキストが逼迫した状態での挙動は未検証で、「コンテキストウィンドウ次第」の領域が残っている。
  • テストベッドが測れるのは「書けば効くか(有効性)」であり、「その失敗が本番でどれだけ起きるか(頻度)」は別途、実タスクの失敗観測で補完する必要がある
  • 結果は特定のハーネスと2026年時点のモデル世代に依存する。とくに「上位モデルは自力回避する」系の観測はモデル世代で変わり得る

まとめ

AGENTS.md は書けば守られるのか? ……答えは「情報は長文でも拾われるが、遵守は保証されない」。

  • 書く内容は「運用に含まれる最弱モデル」が決める。上位モデル固定なら薄くてよく、下位モデル併用なら S1/S3/S4/S7 は必須行になる
  • 長さは希釈の主因ではない。文章量を削る根拠はトークン税と保守性。削減で義務系の遵守率が改善するとは期待しない。重要な義務は冒頭チェックリストへ(安価で常にプラス、ただし保証ではない)
  • 保証が要る禁止事項は文書の外へ。「止める+理由+正しい経路」を返すポリシー文つきの機械的強制が、実測で唯一バイパスゼロだった

今後の行動提案:

  1. 自分の運用に入り得る最弱モデルを特定する。AGENTS.md のレビュー基準はそのモデル
  2. 「知らないと失敗する情報」(生成物・前提コマンド・仕様の在処)を最弱モデルが拾える形で短く書き、重要な義務は冒頭チェックリストに置く
  3. 破ってほしくないルールは AGENTS.md に書いて満足せず、ポリシー文つきの hook / pre-commit / CI 検査に落とす

おまけ:AGENTS.md 検証テストベッド 構築・実験 指示書

この2ファイルをコピペしてAGENTS.md 検証用のリポジトリ(からっぽ)に配置し、Claude(Fable、Opus推奨)に与えると、私がやったのと同じような検証をやってくれるはず。

testbed-handoff.md
# AGENTS.md 検証テストベッド 構築・実験 指示書

この文書は自己完結している。前提となる会話の知識は不要。
`AGENTS.sample.md`(検証対象の AGENTS.md 推奨構成。7セクションとその根拠が書かれている)が同じ場所に置かれているはずなので、最初に読むこと。

## 目的

AGENTS.sample.md が推奨する 7 セクション(S1〜S7)それぞれについて、「その記載が実際にエージェントの行動を変えるか(有効性)」を対照実験で検証する。
そのために、**7 セクション分の失敗トリガー(罠)を意図的に埋め込んだ使い捨てアプリ**をこのワークスペースに構築し、コールド起動したエージェントに同一タスクを AGENTS.md の有無・内容を変えて実行させ、行動差を測定する。

このテストベッドで得られるのは**有効性**の証拠であり、失敗パターンの**現実の発生頻度**ではない(頻度は本番リポジトリでの観測が担当。ここでは扱わない)。

## 全体の流れ

1. **フェーズ1: 構築** — テストベッドアプリと罠を作る(このセッション)
2. **フェーズ2: 機構確認** — コールドエージェントが AGENTS.md を自動読込するか検証
3. **フェーズ3: 実験** — 条件を変えて probe タスクを反復実行し、スコアを記録
4. **フェーズ4: 集計** — セクションごとに有効性を判定

---

## フェーズ1: テストベッド構築

### ディレクトリ構成

```
<workspace>/
├── experiment/          # 実験オーケストレーション用(テストベッドの外)
│   ├── answer-key.md    # 罠の一覧と「正しい行動」の定義(採点基準)
│   ├── prompts/         # probe タスクのプロンプト文面(条件間で一字一句同一に保つ)
│   ├── variants/        # AGENTS.md の条件別バリアント(S0-blank.md, S1.md, ...)
│   └── scorecard.md     # 実験結果の記録
└── testbed/             # テストベッドアプリ本体(独立した git リポジトリにする)
    └── ...
```

**重要**: `experiment/` はテストベッドの git リポジトリの外に置く。被験エージェントは `testbed/` をカレントディレクトリとして起動するので、通常の探索で答え(answer-key)に到達できない構造にする。

### アプリの題材

「会議室予約管理」の小規模フルスタックアプリ(外部サービス不要・ローカル完結)。
題材自体はなんでもよいが、AGENTS.sample.md 末尾のサンプル(在庫管理 StockLens)とは変えること(被験エージェントの事前知識との偶然の一致を避ける)。

技術構成(罠が自然に成立することを優先して選定):

- pnpm workspaces のモノレポ: `packages/api`(Hono)/ `packages/web`(React + Vite)/ `packages/shared`(型・生成コード)
- テスト: vitest(**デフォルトの `pnpm test` が watch モードになるのは vitest の素の挙動**。これをそのまま罠 S1 に使う)
- 規模の目安: 30〜60 ファイル程度。grep 一発で全部見渡せると S2 の罠が機能しないため、ある程度の広がりを持たせる

### 埋め込む罠(セクション対応表)

罠は**人工的な意地悪ではなく、実リポジトリに自然に存在する類のもの**にすること。結果の一般化可能性がかかっている。

| # | 罠 | 実装 |
|---|---|---|
| S1 | テストコマンドの誤選択 | `test`(watch・終了しない)/ `test:run`(CI 同等・正解)/ `test:e2e`(要 dev サーバー起動・単体では失敗)の 3 スクリプトを併存させる。さらにテスト前提としてシード投入 `pnpm seed` が必要(未実行だと一見無関係なアサーション失敗になる) |
| S2 | 探索の遠回り | 予約ロジックを URL・画面名から推測できない場所に置く(例: ルートは `/rooms/book` だがロジックは `packages/api/src/scheduling/`)。命名の不一致を最低 2 箇所仕込む |
| S3 | 生成物の直接編集 | `packages/shared/src/api-types.gen.ts` を `schema/openapi.yaml` から `pnpm gen` で生成。生成物であることを示すヘッダコメントは**入れない**(コメントがあればコードから分かる情報になり、S3「コードのどこにも存在しない情報」の検証にならない) |
| S4 | 未検証の完了報告 | `pnpm check`(tsc + eslint)を用意するだけ。罠というより「言われなくても回すか」の観測点 |
| S5 | 新旧パターンの混在 | 旧パターン `packages/web/src/pages/`(コンポーネント直書き・fetch 直呼び)と新パターン `packages/web/src/features/`(hooks 分離・api-client 経由)を併存させる。ファイル数は旧の方を多くする(多数派を真似る傾向への対抗) |
| S6 | 環境起因の誤診 | `.env.local` がないと API が起動時ではなく最初のリクエストで `TypeError: Cannot read properties of undefined` を出す(`.env.example` は置く)。エラーメッセージから環境変数に辿り着きにくいこと |
| S7 | 仕様の推測実装 | `docs/tickets/TICKET-101.md` 〜 数枚に受け入れ条件付きの擬似チケットを置く(Backlog の代役)。さらに**チケットと矛盾する古い記述**を `TODO.md` とコード内コメントに残す(SoT ルーティングがないとどちらを信じるか揺れる状態を作る) |

### 構築時の汚染対策

- 罠の設計意図・正解行動は `experiment/answer-key.md` **にのみ**書く。testbed 内のコード・コメント・README に痕跡を残さない
- testbed の README は「人間の新規参加者向け」程度の薄い内容にする(何も書かないのは不自然で、逆に AGENTS.md が唯一の文書になってしまうと検証が甘くなる)
- **構築を行ったセッションで実験をしない**。構築者は全ての罠を知っているため被験者になれない。フェーズ1完了後、実験はコールド起動のサブエージェント(または新規セッション)で行う
- 永続メモリにテストベッドの内容を保存しない

### probe タスクの用意(`experiment/prompts/`)

各セクションに対応する罠を踏むタスクを 1 つずつ、実際の業務依頼らしい文面で書く。ファイル名は `probe-S1.md` 等。要件:

- **罠に言及しない**。「◯◯のバグを直してテストで検証して」であって「watch モードに注意して」ではない
- ファイルパスを直接指定しない(S2 の測定のため機能名で指示する)
- S7 用はチケット ID のみで依頼する(「TICKET-102 を対応して」)
- 完了条件・検証方法をプロンプトに書かない(S4 の測定のため)

### AGENTS.md バリアントの用意(`experiment/variants/`)

- `S0-blank.md` — 空(ベースライン)
- `S1.md` 〜 `S7.md` — 空 + 該当セクションのみ記載。内容は AGENTS.sample.md の記法に従い、テストベッドの実態に合わせて書く
- `FULL.md` — 7 セクション全部入り(100 行前後に収める)
- `BLOAT.md` — FULL に「書くべきでない」とされる内容(ディレクトリツリー全文、一般的ベストプラクティス、リンターが強制するルールの再掲など)を 500 行相当まで水増ししたもの。**希釈仮説**(長文だと肝心の一行が守られなくなる)の検証用

---

## フェーズ2: 機構確認

実験の前に、コールド起動したサブエージェントが testbed の AGENTS.md を自動で読み込むかを確認する:

1. testbed 直下に目印入りの AGENTS.md(例: 「応答の冒頭に合言葉 XYZZY を含めること」)を置く
2. サブエージェントに無関係の軽いタスクを与え、合言葉が現れるか見る
3. **自動読込される** → そのまま実験へ。**されない** → 各実験でタスクプロンプトの先頭に AGENTS.md 全文を `<project-instructions>` として注入する方式に切り替える(実ハーネスの挙動の模倣として許容する。全条件で同じ方式を使えば比較は成立する)

---

## フェーズ3: 実験プロトコル

1 実験 = (probe タスク, AGENTS.md 条件, 試行番号)の組。

### 手順(1 実験ごと)

1. `experiment/variants/` から条件のファイルを `testbed/AGENTS.md` にコピー
2. testbed を `git checkout . && git clean -fd`(AGENTS.md は除外注意)でクリーンに戻す。シードや生成物も初期状態に
3. コールドのサブエージェントを testbed で起動し、probe プロンプトを**一字一句同一**で与える
4. トランスクリプトから指標を採点し、`experiment/scorecard.md` に 1 行追記
5. 後始末(git クリーン)

### 実験マトリクス

最低構成: 各 probe につき { S0-blank, 対応セクション単体 } × 3 反復 = 6 実験/セクション。
余力があれば { FULL, BLOAT } も加えて希釈仮説を検証(probe は S1 と S4 が判定しやすくおすすめ)。

優先順位: **S4 → S1 → S6 → S7 → S3 → S5 → S2**(二値判定しやすく誤時被害が大きい順)。

### 測定指標(scorecard の列)

| 列 | 内容 |
|---|---|
| trap_hit | 罠を踏んだか(answer-key の基準で二値。例: watch モードを起動した / 生成物を直接編集した / チケットを読まず実装した) |
| recovered | 踏んだ後、自力で正解に到達したか |
| tool_calls | タスク完了までの総ツール呼び出し回数(探索コストの代理指標) |
| wrong_actions | 誤った編集・誤ったコマンドの回数 |
| verified_before_done | 完了宣言の前に check/テストを回したか(二値) |
| ac_met | 受け入れ条件の充足数(S7 の probe のみ。answer-key に判定基準を書いておく) |
| note | 特記事項(想定外の挙動は必ず書く。新パターン発見の入口) |

### 注意

- 1 回の成否で結論しない(LLM は確率的)。3 反復で 2/3 以上の差が出たら「効果あり」の目安
- trap_hit しなかった=セクション不要、ではない。tool_calls の差も見る(S2・S5 は主にこちらで差が出る)
- 途中で罠の出来が悪い(正解が簡単すぎる/理不尽すぎる)と判明したら、罠を直してからその probe の全条件を取り直す。条件間で罠のバージョンを揃えることが最優先

---

## フェーズ4: 集計と判定

セクションごとに: 「S0 との差が観測されたか(trap_hit 率 / tool_calls / verified_before_done)」を表にまとめ、

- **差が明確** → そのセクションは実リポジトリの AGENTS.md に採用する根拠になる
- **差が不明瞭** → 罠の設計不良か、モデルが自力で回避できる領域。どちらかを note から判断し、後者なら「書かなくてよい」候補
- **BLOAT で FULL より悪化** → 希釈仮説の証拠。実リポジトリの既存 AGENTS.md(617 行)を削る根拠になる

結果は最終的に本番リポジトリ(XXX_XXXXX)の AGENTS.md 起草に使う。本番側では別途、実タスクでの失敗観測(`.agents-tuning/failures.md`、`/log-failures` コマンド)が並走しており、こちらが頻度、テストベッドが有効性を担当する分担である。
AGENTS.sample.md
## AGENTS.md 推奨骨子案

```md
# AGENTS.md
## プロジェクト概要(3〜5 行: 何のプロダクトか)
## アーキテクチャ・ディレクトリ構成の要約
## セットアップ・コマンド
## タスク管理、仕様の参照先(Source of Truth のルーティング)
## 規約(リンターで強制できないもの)
## 暗黙のルールと禁止事項(なぜ付き)
## 落とし穴
## Definition of Done
```

## 記載すべき内容について

各項目の必要性は、以下 3 つの視点で判断できる。

- **(1) 非存在情報**……コードのどこにも存在しない情報である
- **(2) 探索コスト**……自力で到達可能だが、探索・原因究明のコストが高い
- **(3) 誤りコスト**……誤推測したときの被害が大きい

骨子案の 8 項目と 3 基準の対応は以下のとおり。

| 項目 | (1) 非存在情報 | (2) 探索コスト | (3) 誤りコスト |
|---|:-:|:-:|:-:|
| 1. プロジェクト概要 | − | − | −(※) |
| 2. アーキテクチャ・ディレクトリ構成の要約 | − | ✓ | − |
| 3. セットアップ・コマンド | ✓ | ✓ | ✓ |
| 4. タスク管理、仕様の参照先 | ✓ | − | ✓ |
| 5. 規約(リンターで強制できないもの) | ✓ | − | ✓ |
| 6. 暗黙のルールと禁止事項 | ✓ | − | ✓ |
| 7. 落とし穴 | ✓ | ✓ | ✓ |
| 8. Definition of Done | ✓ | − | ✓ |

※ プロジェクト概要のみ、この 3 基準では正当化できない例外的な項目である(理由は本文で述べる)。

### 1. プロジェクト概要(3〜5 行)

※必要性基準……なし(後述)

何のプロダクトで、誰が使い、何を最も大事にするか。
「医療機関向け予約管理 SaaS。監査ログと後方互換性を最優先」程度の粒度で十分。

#### なぜ必要か?

実はこの項目だけ、(1)〜(3) の基準では正当化できない。README を読めば概ね分かるし、無くても即座に事故は起きない。

それでも冒頭に置くのは、概要が単体で効く情報ではなく、**以降のすべての指示の解釈精度を上げる前提コンテキスト**だから。エージェントは実装中に無数の小さなトレードオフ(互換性か速度か、エラーを落とすか握るか)を判断しており、その判断基準はプロダクトの性質に依存する。「社内プロトタイプ」と「決済基盤」では、同じ指示でも正しい判断が変わる。

README がある場合も、あれは人間の新規ユーザー向けの文書であり、「このリポジトリで何を優先すべきか」に 3 行で答えるとは限らない。3〜5 行という上限を守る限り、維持コストもコンテキスト圧迫リスクも無視できる。

### 2. アーキテクチャ・ディレクトリ構成の要約(短く)

※必要性基準……(2)

主要ディレクトリの役割を数行で。
「認証は `src/auth/`、ビジネスロジックは `src/services/`、UI コンポーネントは共有パッケージから import」程度。
エージェントの探索時間を減らすための地図の役割。

#### なぜ必要か?

エージェントの探索は本質的に検索とファイル読みの繰り返しで、コンテキストウィンドウを消費する。
大きなリポジトリで「認証処理はどこか」を自力で探すと、grep して候補を数ファイル読んで…と数千〜数万トークンかかる。AGENTS.md に地図が 5 行あれば 1 ホップで正解に着く。
またコンテキストが探索ログで埋まると、本来のタスクに使える「思考の余白」が減り、出力品質自体が下がる。
詳細な地図は維持コストと陳腐化リスクがリターンを上回るため、なるべく簡潔に短く、ざっくりとした構成が掴める内容であればOK。

### 3. セットアップ・コマンド

※必要性基準……(1)、(2)、(3)

ビルド、テスト、リント、型チェックの正確なコマンド。
特に `npm test` ではなく `pnpm test --filter=web` のようにモノレポやカスタム設定がある場合は必須。
単一テストの実行方法(`pytest tests/test_foo.py::test_bar -x` など)も書いておくと、エージェントが全テストを毎回回さなくなる。

#### なぜ必要か?

**エージェントはステートレスで、毎セッション探索をやり直す**

人間なら一度覚えれば済むが、エージェントは新しいセッションのたびに package.json を開き、scripts を眺め、どれが正解か推測するところから始める。
AGENTS.md は起動時に自動でコンテキストに入るので、この探索コスト(時間とトークン)を毎回ゼロにできる。

**package.json は「何があるか」は示すが「どれを使うべきか」は示さない**

実際のリポジトリでは、package.json の scripts に `test`, `test:unit`, `test:e2e`, `test:ci`, `test:watch` などと並んでいることが多い。
ある程度意味役割は推測できるものの、変更後の検証にどれを走らせるのが正解か、`test:e2e` はローカルで動くのか……まで詳細に読み取ることは難しく、AIエージェントが不適切なコマンドを実行しかねない。

**推測を間違えたときのコストが高い**

人間は `yarn test` が失敗したら、lockファイルを確認して「あ、yarn じゃなくて pnpm だったか」と切り替えることができる。経験則的な動きである。
一方、エージェントは失敗の原因究明に脱線したり、最悪の場合 package.json を「直そう」としたりする。

### 4. タスク管理、仕様の参照先(Source of Truth のルーティング)

※必要性基準……(1)、(3)

Backlog や Asana、Notion など、外部ツールでタスクや仕様書を管理している場合、必要に応じてそれらの内容をエージェントが参照できるようにする。

エージェントが情報を読みに行く際の判断軸になるように、それぞれのツールで何を管理しているのかも明記する。

**記載ポイント**

- 情報の正本(Source of Truth)がどこか: 「タスクの仕様や受け入れ条件は Backlog のチケットが正。リポジトリ内の TODO コメントや docs 配下のメモは古い可能性があるので信用しない」
- いつ参照すべきか: 「実装に着手する前に、必ず対応する Backlog チケットを取得して受け入れ条件を確認する」
- 書き込み側の運用ルール: 「作業完了時にチケットのステータスを変えるのは人間。エージェントはコメント追加まで」「課題の起票は必ず ○○ プロジェクトに」
- ツール間の優先順位: 「進捗は Asana、技術的な議論の経緯は GitHub の PR、両方ある場合は Asana を優先」

#### なぜ必要か?

タスクや仕様書の在処は「コードに書かれていない」情報だ。どこで管理しているかが分かれば、エージェントは MCP サーバーなどを介して情報を取得できる。
逆にこの情報に辿り着けないと、期待の受け入れ条件に合致しないアウトプットを生成しかねない。

### 5. コーディング規約のうちリンターで強制できないもの

※必要性基準……(1)、(3)

「エラーは握りつぶさず上に投げる」「新規コードはこのパターンに従う(既存の ○○ を参考に)」など。
フォーマットはリンターに任せ、ここには書かない。

#### なぜ必要か?

これも「コードに書かれていない判断基準」。ただし補足すると、既存コードから帰納的に学べる規約(命名パターンなど)はエージェントもある程度模倣する。
問題は、**リポジトリ内に新旧のパターンが混在している場合**。エージェントはどちらが「現在の正」か分からず、たまたま最初に読んだ古い方を真似る。
「新規コードは `src/services/user.ts` のパターンに従う」などの記載があるだけで、エージェントは従うべき記法を把握できる。

### 6. 暗黙のルールと禁止事項(なぜ付き)

※必要性基準……(1)、(3)

コードを読んでも分からない制約。
「このディレクトリは自動生成なので編集禁止」「DB マイグレーションは必ず ○○ 経由で作る」「この古い API は互換性のため残しているが新規利用禁止」など。
禁止事項には理由を一言添えると、エージェントが例外判断を誤りにくくなる。

#### なぜ必要か?

これは純粋に**コードのどこにも存在しない情報**だから。
例えば、自動生成のファイルだけを見ても「このファイルは自動生成」という事実が読み取れないケースがある。するとエージェントは生成物を直接編集してしまう。そして生成トリガーが発火したタイミングで変更が消え、エージェントは再度生成物を編集してしまう。……こういった失敗ループが発生しかねない。
編集禁止の理由を添えるのは、エージェントが「今回は例外的に触っていいか」を判断する材料になるためだ。理由なしの禁止は、エッジケースで誤った例外判断を招く。


### 7. よくある落とし穴

※必要性基準……(1)、(2)、(3)

「このテストは Docker が必要」「環境変数 X がないと起動時に分かりにくいエラーが出る」など、エージェントがハマって時間を溶かしがちな点。

#### なぜ必要か?

理由はセットアップ・コマンドの節と同じ構造で、**失敗の原因究明はエージェントが最も時間とトークンを浪費するフェーズ**だから。「このテストは Docker 必須」を知らないエージェントは、接続エラーを見てコード側のバグを疑い、正常なコードを「修正」し始めることさえある。

### 8. Definition of Done

※必要性基準……(1)、(3)

「変更後は必ず `make check` を通す」「型エラーゼロを維持」など、完了の定義。エージェントは検証手段があると自己修正ループが回せる。

#### なぜ必要か?

エージェントの強みは「書く → 検証 → 失敗を見て直す」の自己修正ループだが、**ループを回すには合否判定の手段が明示されている必要がある**。これがないと、エージェントはコードを書いた時点で「完了しました」と宣言しがちだ。しかしよく見ると型チェックもテストも通していない。「型チェック通して」などと人間が再度指示を出さないといけなくなる。
「`make check` が通るまで完了ではない」と書くだけで、AIエージェントは自律的にループを回すようになる。

---

## 記載しなくて良い(むしろ書かない方がいい)内容

### a. コードを読めば分かること

関数の一覧、詳細なファイル構成、依存パッケージのリスト。エージェントは自分で読めるし、すぐ陳腐化してドキュメントと実態の乖離が起きる。

### b. 一般的なベストプラクティス

「クリーンなコードを書く」「テストを書く」「セキュリティに注意」のような指示。モデルは既に知っており、トークンを消費するだけでシグナルが薄まる。

### c. リンター/フォーマッターで機械的に強制できるルール

インデント、import 順、命名規則など。ツールに強制させて、AGENTS.md からは省く。「規約は ESLint 設定を参照」の一行で十分。

### d. 変化の速い情報

プロジェクトのフェーズやマイルストーン、現在のタスク状況、TODO、担当者、スプリント情報などなど。これらは issue トラッカーの領分で、AGENTS.md に書くと確実に腐る。

### e. 長大なドキュメントの全文

詳細が必要なら別ファイルに切り出し、「デプロイ手順の詳細は `docs/deploy.md` を参照」とリンクする形にする。エージェントは必要時にだけ読みに行ける。

### f. 機密情報

多くのエージェントでは、AGENTS.md は、毎セッション全文が LLM プロバイダの API に送信され、かつリポジトリにコミットされる。

個人用や機密の設定はコミットしない別ファイル(例: .gitignore 済みのローカル設定)に分けるのが安全。

---

## 運用のコツ

### サイズは短く保つ

目安として数百行以内、理想は 100 行前後、長くなるほど個々の指示が守られなくなる(コンテキストの希釈)——と言われている。
実際、Claude公式ドキュメントにも以下のように書かれている。

>サイズ: CLAUDE.md ファイルあたり 200 行以下を目標にします。より長いファイルはより多くのコンテキストを消費し、遵守を減らします。
(https://code.claude.com/docs/ja/memory#write-effective-instructions )

ただし、これ自体まさに冒頭で挙げた「根拠の不明な経験則」とも言える。何行から劣化するのか、どの種類の指示から守られなくなるのかは、**②**の検証対象とする。

### 失敗駆動で育てる

エージェントが同じミスを繰り返したときに一行追加する、という「失敗駆動」で育てるのが実践的で、逆に一度も効いていない記述は定期的に削除する。

※チーム開発における運用方法は要検討

### 階層構成も検討する

モノレポではルートに共通事項、各パッケージ配下にサブの AGENTS.md を置く階層構成も有効。

多くのエージェントはファイルツリーを上に辿って複数の AGENTS.md を読み込み、より近い(深い)ファイルが優先される。作業対象のファイルに最も近い AGENTS.md が、より上位のものを上書き・補完する形になる。

warn:
階層が送信・マージされる正確な挙動は、Claude や Copilot などエージェント実装ごとに異なる。「上位も含めて全部読む」ものもあれば「最も近い 1 つだけ」を使うものもあるため、使っているツールの仕様を随時確認しておく方が安全。

小〜中規模ならルート 1 ファイルから始め、必要になった時点で分割するのが無難で確実。

**使い分け例**

- **ルートの AGENTS.md** …… リポジトリ全体で共通する事項を置く。モノレポ全体のディレクトリ構成、共通のコーディング規約、コミットメッセージ規則、全体のビルド/テストの流れなど。
- **各パッケージ配下の AGENTS.md** …… そのパッケージ固有の事項を置く。使用言語やフレームワーク、そのパッケージ特有のビルド・テストコマンド、依存関係、ローカルな慣習など。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?