目次を表示する

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

第6章 アンチパターン ── やりがちな失敗と脱出法

第6章 アンチパターン ── やりがちな失敗と脱出法


ハーネスエンジニアリングの失敗パターンは、プロンプトの失敗とは性質が違う。一発で気づく失敗ではなく、気づいたときには深く根を張っている慢性的な失敗だ。「なんか最近エージェントの品質が下がってきた気がする」「ハーネスを整備したはずなのに改善しない」という症状から始まることが多い。

本章では七つのアンチパターンを解剖する。それぞれに症状・根本原因・脱出法を添えた。


アンチパターン1:失敗前の先回り設計

症状:ハーネスの構築を始めようとして、まず「想定される失敗一覧」を頭の中で考え、それに対するルールや検証スクリプトをまとめて作った。しかし数週間後、作ったインフラの半分は使われておらず、実際に起きた失敗への対処が足りていない。

根本原因:「何が失敗するか」を事前に正確に予測できるという思い込みだ。ソフトウェア開発において、実際の失敗は予想外の場所で起きる。事前設計されたハーネスは「想像された問題」への対処だ。実際の問題への対処ではない。

Anthropicのドキュメントはこれを最大のアンチパターンとして明示している。

「ハーネスを事前設計する誘惑に抵抗せよ。多くのチームが複数回のリライトを経て学んだのは『シンプルに始め、実際の失敗を観察し、実際の問題への対応としてインフラを追加する』という原則だ」

脱出法:ハーネスは「後から育てるもの」として設計する。

flowchart LR
    A[シンプルな状態で始める] --> B[エージェントを実際に動かす]
    B --> C{失敗を観察}
    C -- 失敗した --> D[ハーネスに追加]
    D --> B
    C -- 問題なし --> E[次のタスクへ]

新しいプロジェクトで最初に用意するハーネスは最小限でよい。AGENTS.md(10行程度)、基本的なビルド・テストコマンドの定義、それだけだ。実際の失敗が起きてから対処する。


アンチパターン2:「百科事典型」AGENTS.md

症状:AGENTS.md が500行を超えている。「もっとルールを追加すれば品質が上がるはずだ」と信じて追記を続けているが、むしろエージェントの動作が不安定になってきた。

根本原因:「情報が多いほど良い」という直感は、人間向けのドキュメント設計からきている。人間はドキュメントを目次から参照し、必要な部分だけを読む。エージェントはコンテキストウィンドウに入ったすべてを均等に処理する。

巨大な AGENTS.md は二重の問題を起こす。

  1. 希薄化:重要なルールと重要でないルールが同じ重みで処理され、重要なルールが埋もれる
  2. 圧迫:コンテキストウィンドウに使える容量が減り、実際の作業への集中度が下がる
実際のエージェントの認知に起きること

500行のAGENTS.md を読んだエージェント
「全部処理しました。でも重要なルールを
 重要でないルールと区別できていません」

vs

30行の AGENTS.md を読んだエージェント
「このプロジェクト固有の重要な点を把握しました。
 作業に集中できます」

脱出法:棚卸しの判断基準は一つだ。「このルールを書かなかったとき、エージェントは実際に違反したか?」違反した記憶がないルールは削除する。

# 棚卸し前(500行の AGENTS.md の一部)

- TypeScriptでは any を使わないこと       ← モデルがデフォルトで守っている
- 関数は単一責任の原則に従うこと           ← モデルがデフォルトで守っている
- 変数名は camelCase にすること            ← モデルがデフォルトで守っている
- コメントは日本語で書くこと               ← プロジェクト固有、残す
- fetch() を直接使わないこと               ← 失敗履歴あり、残す

# 棚卸し後(20行)

- コメントは日本語で書くこと
- fetch() を直接使わない → src/lib/api-client.ts の apiClient を使う

定期的な棚卸しをカレンダーに入れる。月次でも十分だ。


アンチパターン3:フルテストスイートの実行

症状:テストが通るたびに4,000行の出力がチャットに表示される。エージェントはテストを実行するたびに「All tests passed」と言うが、その後の応答品質が下がった気がする。長いセッションの後半になると、エージェントが奇妙なことを言い始める。

根本原因:テストスイートの成功ログがコンテキストウィンドウを圧迫している。

4,000行のテスト出力が毎回注入されると、会話履歴の大半がそれに占領される。エージェントはコンテキストウィンドウに入っているすべての情報を処理しようとするため、本来のタスクへの注意が分散する。長いセッションの後半に「忘れた」ような振る舞いをするのはこのためだ。

脱出法:「関連するテストのみ・エラーのみを出力」に変える。

# ❌ 悪い例
bun test   # → 4,000行の出力

# ✅ 良い例:変更ファイルに関連するテストのみ実行
bun test -- --filter=auth   # auth 関連のみ

# ✅ さらに良い例:エラーのみを出力
bun test -- --filter=auth 2>&1 \
  | grep -E "(FAIL|Error|✗)" \
  || echo "Tests passed"

AGENTS.md に「テストの実行方法」として明記する。

## テスト実行
-`bun test -- --filter=<変更したファイル名>` で関連テストのみ実行
-`bun test`(フルスイート)は実行しない
- 理由: フルスイートの出力がコンテキストウィンドウを圧迫するため

アンチパターン4:完成の早期宣言を許す

症状:エージェントが「完成しました」と言ったのでPRを出したら、レビュアーから「テストが書かれていない」「エラーケースが処理されていない」というコメントが来た。エージェントに確認すると「すみません、見落としていました」と言う。

根本原因:エージェントには「完成した」と判断する明確な基準がない。完成条件が定義されていなければ、エージェントは主観的に「これくらいでいいだろう」と判断して終了する。人間も同じ問題を抱えているが、エージェントはその傾向が特に強い。

確認されている要因:

  • エージェントは「先に進みたい」バイアスを持つ(長い会話の後半ほど強くなる)
  • テストを書くことを「完成」の定義に含めていない場合、書かない
  • UI確認が必要なのにブラウザを開かずに「完成」とする

脱出法:完成条件を明示的に定義し、チェックリスト形式で管理する。

# タスク完了チェックリスト(AGENTS.md に記載)

タスクを「完了」と報告する前に、以下をすべて確認すること:

- [ ] 実装したコードにユニットテストを書いた
- [ ] テストが実際に通ることを確認した(テストを実行した)
- [ ] TypeScript の型エラーがないことを確認した
- [ ] UI を含む変更の場合、ブラウザで実際の動作を確認した
- [ ] エラーケース(バリデーション失敗、APIエラー等)を処理した
- [ ] feature-list.json の対応タスクを "done" に更新した

これらを満たさずに「完了」と報告することは認められない。

プロンプトに「テストをスキップして完了とすることは受け入れられない」という強い言語を含めることも有効だ。


アンチパターン5:手動スロップクリーンアップの常態化

症状:エージェントが生成したコードを、毎回人間がクリーンアップしている。不要なコメント、冗長な変数名、Dead Code。「まあこのくらいは直してあげよう」という習慣が定着している。

根本原因:「スロップ(slop)」——低品質生成物——への対処が人間の手作業に依存している。これはスケールしない。エージェントを使う量が増えるほど、クリーンアップにかかる人間の時間が増える。

脱出法:クリーンアップを自動化する。

短期:スロップを防ぐプロンプトを AGENTS.md に追加する。

## コード品質について
- 実装時に使わなかったコード(コメントアウトされたコード、未使用変数)は残さない
- TODOコメントは残さない(完成してからコミットする)
- 説明的すぎるコメントは書かない(コードが自明な場合)

長期:ガベージコレクションエージェントを定期実行する。

# スロップ検知・クリーンアップエージェントの定期実行スクリプト

# weekly-cleanup.sh
claude "コードベース全体をスキャンして以下を検出・修正してください:
1. コメントアウトされたコードの削除
2. 未使用のimport文の削除
3. Dead codeの削除(使われていない関数・変数)
4. TODOコメントがあれば一覧を report.txt に出力(削除はしない)
変更があればPRとして作成してください。"

アンチパターン6:次のモデルを見越した過剰エンジニアリング

症状:「GPT-5(または次世代Claude)が出たら、このハーネスは不要になるだろうな」と思いながら複雑な仕組みを構築している。または逆に「どうせすぐ変わるから、ハーネスに投資しても無駄」と何もしていない。

根本原因:モデルの改善速度とハーネスの寿命の読み間違いだ。

Böckelerはこれを「build to delete」マインドセットで整理した。

「手書きのロジックはすべて、次のモデルがリリースされたときの負債になりうる。ハーネスの設計は『使い捨てが前提』であるべきだ」

しかし逆のアンチパターンも危険だ。「どうせ変わる」を理由にハーネスに何も投資しないと、現在の開発品質が永遠に低いままになる。

脱出法:「今の失敗を防ぐ最小限のハーネスを作り、削除しやすい設計にする」が原則だ。

過剰エンジニアリングの判断基準

❌ 削除・変更が困難なハーネス
- 複数箇所から参照される複雑な抽象化
- 特定のモデルの弱点を前提にした回避策
- 人間がメンテする必要がある状態管理

✅ 削除・変更が容易なハーネス
- AGENTS.md の1行(削除は1秒)
- 独立した検証スクリプト(削除しても他に影響しない)
- Gitフック(`.git/hooks/` を消すだけ)

モデルのバージョンアップ時は「このハーネスはまだ必要か?」を確認するレビューを行う。不要になったものは積極的に削除する。


アンチパターン7:マルチエージェント競合の無視

症状:複数のエージェントを並列実行したところ、同じファイルに矛盾する変更が加えられ、マージ地獄になった。あるいは一方のエージェントの変更が他方によって上書きされた。

根本原因:マルチエージェント実行は、同一リポジトリへの並列書き込みという本質的に難しい問題を持つ。ファイルロックもなく、変更の調整機構もない状態でエージェントを並走させると、競合は必然だ。

脱出法:ファイル単位での作業領域の分割が最も確実だ。

マルチエージェントの安全な設計

❌ 危険な設計
エージェントA: src/auth/ を担当(しかし実装中に src/utils/ も変更)
エージェントB: src/utils/ を担当(しかし実装中に src/auth/ も変更)
→ 競合

✅ 安全な設計(ファイルスコープを明示)
エージェントA: 「src/auth/ 以外のファイルは変更しないこと」
エージェントB: 「src/utils/ 以外のファイルは変更しないこと」
→ 競合しない

Git Worktreeを使って物理的にリポジトリを分離するのも有効だ。

# Git Worktree を使った並列作業
git worktree add /tmp/agent-a-work -b feature/auth
git worktree add /tmp/agent-b-work -b feature/utils

# エージェントAは /tmp/agent-a-work で作業
# エージェントBは /tmp/agent-b-work で作業
# 完了後にマージ(通常の PR フローで)

アンチパターン分類マップ

七つのアンチパターンを問題の発生源で分類する。

quadrantChart
    title アンチパターン分類マップ
    x-axis 設計の問題 --> 運用の問題
    y-axis 気づきやすい --> 気づきにくい
    quadrant-1 運用習慣で防げる見えにくい問題
    quadrant-2 設計段階で防げる見えにくい問題
    quadrant-3 設計を見直せばすぐわかる問題
    quadrant-4 運用で表面化する問題
    AP1 先回り設計: [0.2, 0.7]
    AP2 百科事典型AGENTS.md: [0.25, 0.65]
    AP3 フルテストスイート: [0.6, 0.55]
    AP4 早期完了宣言: [0.55, 0.75]
    AP5 手動クリーンアップ: [0.7, 0.4]
    AP6 過剰エンジニアリング: [0.15, 0.8]
    AP7 マルチエージェント競合: [0.45, 0.3]

左上の「設計段階で防げる見えにくい問題」——AP1(先回り設計)とAP6(過剰エンジニアリング)——が最も危険だ。投資コストが高く、効果がなく、気づくのが遅い。新しいプロジェクトでハーネスを始めるとき、この二つに陥らないことが最初の関門だ。


まとめ:失敗から学ぶことが設計の材料

ハーネスエンジニアリングのアンチパターンのほとんどは「エージェントの失敗を人間が補っている」という構造から来ている。人間が毎回クリーンアップし、毎回完成を確認し、毎回スコープを修正しているなら、ハーネスが機能していないサインだ。

失敗パターンを知ることは、改善の材料だ。「また同じ問題が起きた」というとき、それをハーネスに落とし込む機会として捉える。次章ではハーネスエンジニアリングへの批判と懐疑論、そしてこの概念がエンジニアリングにとって何を意味するかを論じる。