目次を表示する

ハーネスエンジニアリング

第5章 ベストプラクティス ── 実証された七つの鉄則

第5章 ベストプラクティス ── 実証された七つの鉄則


本章では、複数の実践者が独立して同じ結論に達しているプラクティスを七つ取り上げる。「理論的に正しそう」ではなく「実際に試されて、繰り返し報告されている」ものに絞った。各鉄則に背景・実装・注意点を添える。


鉄則1:AGENTS.md は「60行以内の失敗記録」として管理する

背景

AGENTS.md の価値は記述量ではなく 信号密度 だ。書いてあるすべてが「実際の失敗から生まれた」ものなら、エージェントにとって無視できない重みを持つ。「一般的なベストプラクティス」が混じった瞬間に薄まる。

Hashimotoが強調したのはこの点だ。「このファイルの各行は実際の失敗に対応している」という言い方は、行数の多さではなく一行一行の重さを意味している。

実装

# AGENTS.md の理想的な構造

## ⚡ 最重要(必ず読む)
- ビルド: `bun run build`(npmではない)
- テスト: `bun test -- --filter=<変更したファイル名>`(フルスイートを走らせない)
- 型チェック: `bun run typecheck`

## 🚫 過去の失敗から(各行が実際の失敗に対応)
- `fetch()` を直接使わない → `src/lib/api-client.ts``apiClient` を使う
- `prisma.user.findMany()` は使わない → `src/repositories/user.ts` のリポジトリ経由で
- `any` 型は使わない。型が不明な場合は `unknown` を使い型ガードを書く
- テスト内でモックを使わない → 実際のDBに接続するテスト用クライアントを使う

## 📁 プロジェクト構造
- ドメインロジック: `src/domain/`
- インフラ層: `src/infra/`(ドメイン層からの依存は禁止)
- 詳細は `docs/architecture.md` を参照

注意点

  • 60行を超えはじめたら「この行は実際の失敗に対応しているか?」で棚卸しをする
  • 見ているだけで使っていないルールは削除する
  • 新しい失敗が起きたらセッション終了前に追記する習慣をつける

鉄則2:アーキテクチャを「文章で頼む」のではなく「機械的に強制する」

背景

エージェントはアーキテクチャルールをドキュメントから学んでも、利便性のためにショートカットを選ぶことがある。人間と同じだ。「やってはいけない」と知っていてもやってしまう。

OpenAIのチームはこれをカスタムリンターで解決した。ポイントはエラーメッセージの設計だ。違反を検出するだけでなく、修正方法もメッセージに含める。エージェントはそのメッセージを読んで自律的に修正する。

実装

# カスタムリンター例:ドメイン層からインフラ層への依存を禁止する

import ast
import sys

DOMAIN_PATH = "src/domain"
INFRA_IMPORT = "src.infra"

def check_file(filepath: str) -> list[str]:
    violations = []
    if DOMAIN_PATH not in filepath:
        return violations

    with open(filepath) as f:
        tree = ast.parse(f.read())

    for node in ast.walk(tree):
        if isinstance(node, ast.Import | ast.ImportFrom):
            module = getattr(node, 'module', '') or ''
            if INFRA_IMPORT in module:
                violations.append(
                    f"{filepath}:{node.lineno}: "
                    f"❌ ドメイン層からインフラ層への依存は禁止です。\n"
                    f"   修正: インフラ層への依存が必要な場合は、\n"
                    f"   src/domain/ports/ にインターフェースを定義し、\n"
                    f"   src/infra/ でそれを実装してください(依存性逆転の原則)"
                )
    return violations
# .github/hooks/pre-commit または hooks 設定

- name: architecture-check
  run: python scripts/arch_lint.py $(git diff --cached --name-only --diff-filter=A)
  on_fail: show_output_and_abort

注意点

  • エラーメッセージは「ダメ」だけでなく「代わりにこうする」まで書く
  • 既存のコードに後から適用する場合は --report-only モードで始め、違反数を把握してから段階的に強制する

鉄則3:計画フェーズと実装フェーズを必ず分離する

背景

「計画を確認して承認するまで、エージェントにコードを書かせてはいけない」(Boris Tane)。

これはコード品質の問題だけではない。スコープの問題だ。曖昧な指示を受けたエージェントは「最短で完了する解法」を自分で選ぶ。その選択が開発者の意図と合致していることは保証されない。

実装

Claude Codeなら --plan フラグ(あるいはチャットでの明示的な計画依頼)を使う。

❌ 悪い例
「認証周りをリファクタリングして」→ エージェントが即座にコードを書き始める

✅ 良い例
「認証周りをリファクタリングする計画を立ててください。
 変更するファイル一覧と、各ファイルで何を変更するかを
 箇条書きで示してください。実装はまだ始めないでください」

Anthropicのアーキテクチャでは、これを「初期化エージェント」として自動化している。

// 初期化エージェントが出力する feature-list.json の例
{
  "features": [
    {
      "id": "auth-001",
      "title": "JWTトークンのリフレッシュ処理を共通化する",
      "files_to_modify": [
        "src/auth/token-service.ts",
        "src/middleware/auth.ts"
      ],
      "test_plan": "有効期限切れトークンでリクエストした場合に自動リフレッシュされることをE2Eテストで確認",
      "status": "pending"
    }
  ]
}

このJSONをユーザーが確認・承認してから、コーディングエージェントが実装フェーズに入る。

注意点

  • 計画フェーズを必要以上に細かくすると、却って進行が遅くなる。「何を変えるか(ファイル単位)」「なぜ変えるか」「どう確認するか」の三点があれば十分
  • 計画を修正したいときは、承認の前に修正する。実装後の差し戻しは高コスト

鉄則4:フィードバックは「エラーのみ出力」に設計する

背景

テストが4,000行の成功ログを出力するとき、エージェントのコンテキストウィンドウの大半がそれに占領される。結果として、エージェントは本来のタスクを忘れ、テスト出力を読み解くことに計算資源を使い、ハルシネーションを起こす。

成功は沈黙する。エラーだけが声を上げる。

実装

# ❌ 悪い例:すべての出力を渡す
bun test

# ✅ 良い例:エラーのみを出力する
bun test 2>&1 | grep -E "^(FAIL|Error|✗|×)" || echo "All tests passed"
# TypeScript型チェックも同様
bun run typecheck 2>&1 | grep -v "^$" | grep -E "(error TS|Found [0-9]+ error)" \
  || echo "Type check passed"

フックとして設定する場合:

// .claude/settings.json(Claude Codeのフック設定例)
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash scripts/check-errors-only.sh $CLAUDE_TOOL_INPUT_FILE_PATH"
          }
        ]
      }
    ]
  }
}

注意点

  • 「エラーのみ出力」はエージェント向けの設定だ。人間向けのCI/CDレポートとは分けて管理する
  • 一部のフレームワークはエラーと警告を混在させる。警告もノイズになるので、できれば分離する

鉄則5:サブエージェントを「コンテキストファイアウォール」として使う

背景

長時間・複雑なタスクでは、中間処理のノイズが親スレッドのコンテキストに蓄積する。10回のファイル検索結果、3回のテスト実行ログ、2回のビルドエラーと修正。これらがすべて親のコンテキストに残ると、判断精度が低下する。

サブエージェントは別のコンテキストウィンドウで作業し、結果のみを親に返す。これがコンテキストファイアウォールだ。

実装

タスク分解とコンテキスト隔離の構造

親エージェント
├── タスク計画と全体管理

├── サブエージェントA(検索・調査)
│   ├── コードベース全体を検索
│   ├── 関連ファイルを読み込む
│   └── → 「関連ファイルは以下の3件」とサマリーだけを返す

├── サブエージェントB(実装)
│   ├── 仕様を受け取る
│   ├── 実装する
│   ├── テストを書く
│   └── → 「実装完了。変更ファイル: auth.ts, auth.test.ts」を返す

└── サブエージェントC(検証)
    ├── ビルドを実行
    ├── テストを実行
    └── → エラーがあれば内容を、なければ「OK」だけを返す

Claude Codeではサブエージェント起動を Task ツールで行う。各サブエージェントは独立したコンテキストウィンドウを持ち、完了時に要約された結果だけを返す。

注意点

  • サブエージェントへの指示は具体的にする。曖昧な指示は曖昧な結果を生む
  • サブエージェントの結果を親がそのまま使うのではなく、「何が変わったか」「何が残っているか」を確認するステップを入れる

鉄則6:長期実行タスクには状態ファイルを用意する

背景

エージェントはセッションをまたいで記憶を持てない。数日かかる実装タスクで、毎回「どこまで進んでいるか」をコンテキストに説明するコストは高く、精度も下がる。状態を外部化する。

実装

プロジェクトルートに配置する状態ファイル群

init.sh               # 環境を素早く起動するスクリプト
claude-progress.txt   # セッション間でエージェントが引き継ぐ作業ログ
feature-list.json     # JSON形式の機能チェックリスト(status: pending|in_progress|done)
# init.sh の例
#!/bin/bash
echo "=== 環境初期化 ==="
bun install
echo "=== 現在の進捗 ==="
cat claude-progress.txt
echo "=== 未完了タスク ==="
cat feature-list.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
pending = [f for f in data['features'] if f['status'] != 'done']
for f in pending:
    print(f'[ ] {f[\"id\"]}: {f[\"title\"]}')
"
# claude-progress.txt の例(エージェントが各セッション末に更新)

## 2026-03-28 セッション終了時
完了: auth-001(JWT リフレッシュ共通化)、auth-002(ログアウト処理修正)
進行中: auth-003(セッション管理のリファクタリング)
  - src/auth/session.ts の read 側は完了
  - write 側の実装が残っている(session.ts:142から)
次回開始: auth-003 の write 側の実装から
注意: テストDB の接続文字列は .env.test を参照

注意点

  • claude-progress.txt の更新はセッション終了前にエージェントに指示する。忘れると引き継ぎが失敗する
  • feature-list.json のステータスは done の確認を厳格にする。「動いているはず」ではなく「テストが通った」を条件にする

鉄則7:「退屈な技術」を意識的に選ぶ

背景

LLMエージェントの推論精度は、訓練データの量に依存する。Rustで書かれた主要OSS、TypeScriptの標準的パターン、Pythonの定番ライブラリ——これらは訓練データに豊富に含まれるため、エージェントの判断精度が高い。

逆にニッチなフレームワーク、最新すぎるライブラリ、独自DSLは判断精度を下げる。エージェントはドキュメントを読んで補完しようとするが、完全ではない。

Böckelerはこれを「AI保守性(AI maintainability)」という概念で整理した。

技術選定の評価軸(AI保守性を加えた場合)

従来の評価軸              追加する評価軸
─────────────────         ─────────────────────
パフォーマンス            訓練データへの含有量
エコシステムの成熟度      AIエージェントの認知度
チームの習熟度            エラーメッセージの明確さ
ライセンス                LLMがドキュメントを自力参照できるか

実装

具体的には以下のような判断基準だ。

✅ AI保守性が高い選択肢の例
- ORMはPrismaかDrizzle(実績多数、エラーメッセージが明確)
- フロントエンドフレームワークはReact or Next.js(圧倒的な訓練データ量)
- テストはVitest or Jest(パターンが豊富)
- HTTPクライアントはfetch or axios(ほぼすべての訓練データに含まれる)

⚠️ 注意が必要な選択肢
- 最新リリースのライブラリ(訓練データに含まれていない可能性)
- マイナーなフレームワーク(エラーメッセージが検索ヒットしない)
- 独自の抽象化層(エージェントが学習できない)

注意点

  • 「退屈な技術を選ぶ」はプロジェクト要件を無視した言説ではない。同等の選択肢があるなら、より広く使われているものを選ぶ、という原則だ
  • この判断はモデルのバージョンアップで変わりうる。今後の主流ライブラリは常に更新される

七つの鉄則まとめ

quadrantChart
    title ベストプラクティスの効果と実装コスト
    x-axis 実装コスト低い --> 実装コスト高い
    y-axis 効果小さい --> 効果大きい
    quadrant-1 今すぐやる価値が高い
    quadrant-2 計画して取り組む
    quadrant-3 余裕があれば
    quadrant-4 費用対効果を見極める
    AGENTS.md管理: [0.2, 0.75]
    計画と実装の分離: [0.15, 0.85]
    エラーのみ出力: [0.3, 0.7]
    アーキテクチャ強制: [0.65, 0.9]
    サブエージェント設計: [0.7, 0.75]
    状態ファイル管理: [0.45, 0.65]
    退屈な技術選択: [0.1, 0.5]

右上の「アーキテクチャ強制」と「サブエージェント設計」はコストがかかるが効果が大きい。左上の「計画と実装の分離」と「AGENTS.md管理」はコストが低く効果が高い。今日から始めるなら、左上の二つだ。

次章ではこれらの鉄則を知らなかった、あるいは知っていても踏んでしまうアンチパターンを解剖する。