このドキュメントについて
- 分類: concept
- 対象読者: AIエージェント向けのツール(CLIスクリプト等)を設計・実装するエンジニア
- 扱わない範囲: 個別スクリプトの実装詳細
責任範囲
設計者(人間)
- 各原則の適用判断
- precondition の定義
- 三層分離の境界判断(どこまでをライブラリ層に置くか)
エージェント
-
logs/tool-registry.jsonを参照してツールを選択・実行 - exit code を読んで自律回復 or エスカレーションを判定
概要
エージェントがツールを使う際、情報の欠如はモデル能力に比例してコストが増大します(賢いモデルほど丁寧にギャップを推論で埋めるため)。
本ガイドラインは、エラーを「現象の記述」から「行動の処方」へ昇格させ、precondition を宣言的に置き、主語層・経路層・実装層を分離することで、エージェントが解釈に詰まらず自律的に動ける環境の作り方を扱います。
前提概念
本ガイドラインは以下3つの概念を前提にします。
アクター
本ガイドラインにおけるアクターは AIエージェントです(Claude Code、MCPツールを使うエージェント等)。
人間オペレータではない点に注意してください。
人間が読むためのドキュメントと、エージェントが解釈するためのツールでは、最適な設計が異なります。
Niche(ニッチ)
アクターと環境の関係性のことです。
「正しい行動が自然に起きる環境を構成する」という設計視点を指します。
ツールを「作る」のではなく、アクターが置かれる環境を「設計する」。
個別のツール単体ではなく、ツール群が成す情報経路全体が設計対象になります。
Affordance(アフォーダンス)
環境がアクターに提示する行動可能性です。
「説明されなくても次に何をすべきかが自明」な状態を指します。
エージェントにとっての Affordance の典型例は次の通りです。
- エラーメッセージに修復コマンドが添えてある
- 出力が
--jsonでパース可能な構造化データになっている - exit code から「自分で直せる/直せない」が判別できる
これらが揃っていれば、エージェントは推論で隙間を埋める必要がありません。
逆にこれらが欠けていると、推論で補おうとし、トークンコストが膨らみます。
要するに何をすればいいのか
- ツールは三層構造で書く(主語層/経路層/実装層)
- 文法は
<subject>.sh <noun> <verb>で統一する - 実装層は stateless にする(環境変数で文脈を受け取る)
- スクリプトヘッダに TOOL_META を書く(preconditions, idempotent, side_effects)
- エラーは修復コマンド付きで返し、exit code は semantic に使う
- ツールレジストリを自動生成し
logs/tool-registry.jsonに吐く
各項目の根拠は次節以降に示します。
基本原則
P1. エラーは行動を引き起こす形で返す
エージェントが「解釈」を挟む余地をゼロにします。
# NG: 現象を述べるだけ
Error: File not found: docs.yml
# OK: 次の行動が自明
Error: docs.yml not found. [LAYER: precondition] [RECOVERABLE: yes]
→ Fix: bash scripts/docs-init.sh
なぜそうするか:
エージェントが推論でギャップを埋めようとするたびにトークンが消費されます。
修復コマンドが書いてあれば推論コストがゼロになります。
ドキュメントが「読まれる」ことではなく「行動を引き起こす」ことを目的とするのと同じ思想です。
P2. エラーに発生層を付与する
[LAYER: precondition | runtime | platform]
[RECOVERABLE: yes | no | user-action-required]
層の意味は次の通りです。
- precondition: ツール実行前の状態不足。自律修復可能
- runtime: ツール内部の失敗。retry または escalate
- platform: ツールの外側で弾かれた(権限・ネットワーク等)。user-action-required
なぜそうするか:
層が分からないと、コードレベルの修正を繰り返しても platform レベルの問題は解決しません。
エージェントが無駄な retry ループに入るのを防ぐためです。
P3. Precondition は宣言的に書き、ツールに同居させる
ソースオブトゥルースは実装層スクリプト本体のインラインメタデータ(TOOL_META)にします。 レジストリは自動生成(派生物)です。
# TOOL_META
# noun: docs
# verb: fetch
# preconditions: [DOCS_DIR exists]
# idempotent: true
# side_effects: []
なぜそうするか:
別ファイルに置くと実装とのドリフトが構造的に発生します。
スクリプトを書き換える人とメタを書き換える人が分かれた瞬間に同期が崩れるためです。
P4. 冪等性と副作用を明示する
エージェントは retry します。 idempotent: false のツールを retry すると副作用が二重に発生します。
なぜそうするか:
副作用が見えていれば、レビュー側のエージェントが「この順序で呼んでよいか」「retry してよいか」を検証できます。
P5. 実装層は stateless にする
実装層スクリプトはプロジェクト固有のパス・構造に依存してはなりません。
文脈は環境変数で注入します。
# 実装層スクリプトの冒頭
: "${PROJECT_ROOT:?PROJECT_ROOT must be set}"
: "${DOCS_DIR:?DOCS_DIR must be set}"
なぜそうするか:
配布可能性の条件です。
実装層がハードコードを含んだ瞬間、他プロジェクトに移植不可能になります。
文脈の注入は主語層(ラッパー)の責務として分離します。
三層アーキテクチャ
構造
主語層(プロジェクト固有ラッパー)
myproject.sh
↓ 環境変数で文脈注入
経路層(共有ディスパッチャ)
dispatcher.sh
↓ exec lib/<noun>-<verb>.sh
実装層(stateless ツールライブラリ)
lib/docs-fetch.sh, lib/docs-index.sh, ...
各層の責務
| 層 | 責務 | プロジェクト依存度 |
|---|---|---|
| 主語層 | プロジェクトのアイデンティティ宣言、環境変数の設定 | 高(プロジェクト固有) |
| 経路層 |
<noun> <verb> の文法ルーティング、横断機能(--json, --dry-run, exit code 規約)の強制 |
ゼロ(中立) |
| 実装層 | 実際の操作ロジック | フォルダ構造などが変われば高(プロジェクト固有) |
含意
- 主語層は意志を持つ主体の宣言: 環境変数群はプロジェクトの自己認識のスナップショット
- 経路層は mechanism only, no policy: 何をするかは知らず、どう繋ぐかだけを知る
- 実装層の純粋性が配布可能性を担保: 他プロジェクトに lib/ ごと持っていける
ラッパーは数行で書けます。
#!/usr/bin/env bash
# myproject.sh
export PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
export DOCS_DIR="${PROJECT_ROOT}/docs/patterns"
exec "${PROJECT_ROOT}/scripts/dispatcher.sh" "$@"
別プロジェクトで使いたければ、ラッパーだけを差し替えます。 同じロジックであれば経路層と実装層は1行も書き変えずに再利用可能です。
CLI 文法
<subject>.sh <noun> <verb> [options]
例:
myproject.sh docs fetch --keywords cdk lambda --json
myproject.sh docs index --rebuild
myproject.sh impl context --keywords react hook --files src/hooks/useFoo.ts
必須オプション(経路層で強制)
| オプション | 用途 |
|---|---|
--json |
構造化出力(エージェントがパース可能) |
--dry-run |
副作用なしで事前検証 |
--yes / -y
|
確認プロンプトをスキップ |
Exit Code 規約(経路層で強制)
| code | 意味 |
|---|---|
| 0 | success |
| 1 | precondition violation(自律修復可) |
| 2 | runtime error |
| 3 | user-action-required(エスカレーション) |
経路層で強制することで、実装層の規約違反を構造的に防ぎます。
Usage と Registry の役割分離
--help で出る Usage 文字列と、logs/tool-registry.json の中身は、似ていますが射程が違います。
| 形式 | 表現範囲 | 読み手 | 答える問い |
|---|---|---|---|
| Usage 文字列 | 構文(syntax) | 人間・エージェント両方 | 「どう書けば動くか」 |
| Registry JSON | 意味論(semantics) | エージェントのプランナー | 「何が前提で、何が変化するか」 |
Usage に書けないがレジストリに書ける情報
- preconditions(呼び出し前提)
- idempotent フラグ(retry 安全か)
- side_effects(どこに書くか、何を変えるか)
- exit code の意味論的マッピング
これらを Usage に詰め込むと人間可読性が壊れます。 読み手が違うと最適形式が違うためです。
Registry が真価を発揮する場面
エージェントが実行計画を立てる時に効きます。
- 「docs fetch の前に docs index が必要」
- 「両者は同じインデックスファイルに書き込むから並列実行不可」
- 「この呼び出し順序は side_effects 的に矛盾している」
Usage を grep しても到達できません。 precondition グラフと side_effects グラフを構造化データで持たないと、計画立案が自然言語推論タスクに退化します(= トークンコスト増大)。
レジストリの用途は「help の機械可読版」ではなく「help の上位レイヤー」です。
Precondition の配置
lib/docs-fetch.sh ← TOOL_META をここに書く(正)
↓
gen-tool-registry.sh ← パースして生成
↓
logs/tool-registry.json ← エージェントが読む(派生の生成物)
CI で生成物と実装の差分が出たら「レジストリが古い」として落とします。 人間が両方をメンテナンスする状態は構造的にドリフトを生むため、片方を派生の生成物として固定します。
判断フロー: 新しいスクリプトを書くとき
スクリプトを追加する
↓
どの層に置くべきか?
プロジェクト固有 → 主語層(ラッパー側)
ルーティング → 経路層(ディスパッチャ側)
ロジック本体 → 実装層(lib/側)
↓
実装層の場合、stateless か?
NO → 環境変数で受け取る形に書き直す
↓
TOOL_META を書いたか?(preconditions, idempotent, side_effects)
NO → 書く
↓
エラーメッセージに修復コマンドが入っているか?
NO → 入れる
↓
exit code は semantic か?
NO → 修正する
↓
レジストリ生成スクリプトを実行して logs/tool-registry.json を更新
適用例: impl context ツール
実装エージェントが作業に入る前に、変更対象ファイルやキーワードから関連ドキュメント・規約を取得するツールです。
docs/patterns/ と docs/rules/impl/ は、特定経路でのみ参照される設計だと、直接質問・サブエージェント起動など他経路で発見されません。
impl context を常に叩くことで、どの経路から作業が始まっても参照が保証されます。
基本形(実装層スクリプト参考)
#!/usr/bin/env bash
# TOOL_META
# noun: impl
# verb: context
# preconditions: [DOCS_DIR exists]
# idempotent: true
# side_effects: []
set -euo pipefail
# 環境変数による文脈注入(主語層から受け取る)
: "${PROJECT_ROOT:?PROJECT_ROOT must be set}"
export DOCS_DIR="${PROJECT_ROOT}/docs/patterns"
# Precondition 検証
if [[ ! -d "$DOCS_DIR" ]]; then
cat >&2 <<EOF
Error: DOCS_DIR not found: $DOCS_DIR
[LAYER: precondition] [RECOVERABLE: yes]
→ Fix: bash scripts/docs-init.sh
EOF
exit 1
fi
# 与えられた情報から一致するドキュメントを取得する処理
# (引数パース・本処理・出力整形は省略)
# 戻り値:
# 実装内部で失敗 → exit 2 (runtime)
# 権限不足等で外側で弾かれた → exit 3 (platform)
ケース1: 対象フォルダが未整備のプロジェクトで実行
- 状況:
myproject.sh impl contextを実行したがDOCS_DIRのディレクトリが存在しない - 判断: LAYER=precondition, RECOVERABLE=yes → 修復コマンドを出力して exit 1
- 結果: エージェントが
docs-init.shを自律実行して再試行する。retry ループに入らない
precondition violation を明示的に分離していない場合、エージェントは「ファイルがない」というだけで判断を迫られ、推論で修復方法を組み立て始めます。 LAYER と修復コマンドが添えてあれば、推論ゼロで自律修復が成立します。
ケース2: 他プロジェクトへの展開
- 状況: 別プロジェクトでも同じ
impl contextを使いたい - 対応: そのプロジェクト用のラッパー(主語層)を1ファイル書き、
PROJECT_ROOT・DOCS_DIRを該当プロジェクトのパスに向ける - 結果: 経路層・実装層は1行も書き換えずに動作する
実装層が PROJECT_ROOT に依存せず stateless で書かれているからこそ可能です。
もし lib/impl-context.sh の中で cd /home/user/myproject のようなハードコードがあれば、移植時にスクリプト本体を改変する必要が出ます。
ケース3: 内部処理が失敗
- 状況:
impl contextが内部で呼ぶドキュメント検索処理が JSON パース失敗で落ちた - 判断: LAYER=runtime, RECOVERABLE=yes(インデックス再生成すれば直る可能性) → exit 2
- 結果: エージェントは「これは自分で再生成 retry できる runtime error」と判断し、
docs index --rebuildを試みる
別パターンとして、
- 状況:
impl contextがDOCS_DIRを読もうとしたが権限不足で弾かれた - 判断: LAYER=platform, RECOVERABLE=user-action-required → exit 3
- 結果: エージェントは「これは自分では直せない」と判断し、ユーザーにエスカレーション
exit code を success/fail の2値で返していると、エージェントは「自分で直せる/直せない」を本文から推論で判定するしかありません。
semantic exit code が、判定コストを構造的にゼロにしています。
自己診断の問い
設計に迷ったら、以下を問います。
- アクターは解釈を要求されていないか?
- 主語と道具が分離されているか?
- 環境からアクターに「次の行動」が知覚されるか?
これらに NO がついた場合、Affordance か Niche のどこかが破れています。
参考リンク
-
AutoHarness: improving LLM agents by automatically synthesizing a code harness (Google DeepMind, 2026年3月)
- LLMにハーネス(制約コード)を自動生成させ、下位モデル + ハーネスが上位モデルを上回ることを示した論文。Kaggle GameArena チェスでは Gemini-2.5-Flash の敗因 78% が違法手であり、「LLMはルールを知っているが従えない」ギャップが定量的に示された
- 本ガイドラインの「制約をコードに外部化する」設計思想(TOOL_META、semantic exit code、stateless 実装層)の発想元
-
How to cut Claude Code costs by 3x (using Karpathy's context engineering principles)
- コンテキストが不完全な場合、より有能なモデルはギャップをスキップするだけでなく、より多くのトークンを費やしてギャップを推論し、より多くの検出クエリを実行する