84
71

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ハーネスエンジニアリングを自分のプロジェクトで実装してみた

84
Last updated at Posted at 2026-03-11

はじめに

Morpho社の調査が興味深いデータを出しています。SWE-benchにおいて、ハーネス設計の違いでスコアが最大22ポイント変動する一方、モデルの入れ替えではわずか1ポイントしか変わりませんでした。

つまり「どのモデルを使うか」より「どんな環境でエージェントを走らせるか」の方が、成果に対する影響が圧倒的に大きいということです。

2026年3月、逆瀬川ちゃん(@gyakuse)のブログ記事「Claude Code / Codex ユーザーのための誰でもわかるHarness Engineeringベストプラクティス」がはてブ672usersを記録しました。日本でもこの概念への関心が一気に高まっています。

以前、OpenAIの公式ブログを解説した記事「人間はコードを1行も書かない」という縛りで5ヶ月間プロダクトを作り続けた結果 ― ハーネスエンジニアリング」を書きました。前回は概念紹介が中心でしたが、今回は自分のプロジェクトで実際にMinimum Viable Harnessを構築した実践レポートです。

Harness Engineeringとは

まず、よく混同されるプロンプトエンジニアリングとの違いを整理します。

観点 プロンプトエンジニアリング Harness Engineering
対象 LLMへの入力テキスト エージェントが動作する環境全体
手法 プロンプトの文言最適化 構造的制約・自動チェック・フィードバックループ
持続性 セッションごとに揮発 リポジトリにコミットされ累積する
強制力 依頼(お願い) 強制(Hook/CI/リンターで機械的にブロック)
スケーラビリティ チーム共有が困難 リポジトリ共有で自動的に全エージェントに適用

ポイントは「強制力」の違いです。プロンプトでいくら「anyを使うな」と書いても、エージェントは忘れます。一方、リンターでブロックすれば物理的に使えません。

Stripe Minionsチームの言葉が本質を突いています。

優れた開発者インフラを構築せよ。エージェントは自動的にその恩恵を受ける。

エージェント専用のインフラを作るのではなく、人間の開発者にとっても良い環境を作る。それがそのままエージェントの性能を引き上げます。

Stripeの事例では、人間用に構築した開発環境(devbox)をそのままエージェントに使わせています。エージェント専用の特別なツールを作ったわけではありません。良い開発者体験は、良いエージェント体験でもあるということです。

実装1: 50行CLAUDE.mdの設計

ポインタ設計の原則

逆瀬川ちゃんの記事でも強調されていた原則があります。「CLAUDE.mdはポインタであり、ドキュメントではない」。これを自分のプロジェクトで徹底しました。

ポインタ設計の3原則は以下の通りです。

  1. 実行可能なソースへの参照を書く(npm test を実行せよADRは /docs/adr/ にある
  2. 現在のシステム状態を記述しない(コード+テストが真実の源泉)
  3. ポインタが存在しないパスを参照した場合は明示的に失敗させる

Vercelの事例が参考になります。約40KBのエージェントプロンプトを8KBに圧縮しても、評価テストのパス率は100%を維持しました。一方、スキル検索方式では79%にとどまりました。情報量を減らす方が性能が上がるという、直感に反する結果です。

Before/After

自分のCLAUDE.mdをどう変えたか、具体例を示します。

Before(120行超):

# プロジェクト概要

このプロジェクトはNext.js 15 + TypeScript 5.7で構築された
SaaSアプリケーションです。データベースにはPostgreSQL 16を使用し、
ORMにはDrizzle ORMを採用しています。

## ディレクトリ構造

src/
├── app/          Next.js App Router
│   ├── api/      APIルート
│   ├── (auth)/   認証関連ページ
│   └── (dashboard)/ ダッシュボード
├── components/   UIコンポーネント
│   ├── ui/       shadcn/uiベース
│   └── features/ 機能別コンポーネント
├── lib/          ユーティリティ
├── db/           データベース関連
│   ├── schema/   Drizzleスキーマ
│   └── migrations/ マイグレーション
...(以下、コーディング規約が60行続く)

After(48行):

# プロジェクト

Next.js 15 + TypeScript 5.7 SaaSアプリ

## コマンド

- テスト: `pnpm vitest run`
- 型チェック: `pnpm tsc --noEmit`
- リント: `pnpm biome check src/`
- フォーマット: `pnpm biome format --write src/`
- DB マイグレーション: `pnpm drizzle-kit migrate`

## 設計判断

- ADR: `/docs/adr/` 配下を参照
- APIルート追加時: ADR-003を確認
- 新コンポーネント追加時: ADR-007を確認

## 禁止事項

リンターが強制するため記述不要。`biome.json` を参照。

120行が48行に縮まりました。ディレクトリ構造の記述は削除しています。tree コマンドを実行すれば済む情報をCLAUDE.mdに書くのは重複であり、陳腐化の原因になるためです。

削除した情報の多くは「コードを読めばわかること」でした。コーディング規約はリンター設定に、ディレクトリ構造はファイルシステムに、APIの仕様はテストに書かれています。CLAUDE.mdにはそれらへの「ポインタ」だけを残します。

ポインタが指す先が存在しない場合、エージェントは明示的にエラーを出します。これが重要です。古いパスへのポインタが残っていれば、エージェントが「このパスが見つかりません」と報告してくれるため、CLAUDE.mdのメンテナンスが容易になります。記述的なドキュメントが古くなった場合、エージェントは黙って無視するだけなので問題の発見が遅れます。

実装2: PostToolUse Hookでリンター自動実行

フィードバック速度の階層

エージェントに対するフィードバックは、速ければ速いほど効果が高くなります。以下の図で速度の階層を示します。

チェックは可能な限り最速の層に移動させるべきです。CIで落ちるエラーをPostToolUse Hookで検出できれば、エージェントはその場で自己修正します。CIまで待つと、コンテキストが失われた状態で再実行することになります。

Hook設定の実装

Claude Codeの settings.json に以下のHookを設定しました。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'FILE=$(jq -r .tool_input.file_path); npx biome format --write \"$FILE\"; npx oxlint --fix \"$FILE\" 2>&1 | head -20'"
          }
        ]
      }
    ]
  }
}

このHookにより、エージェントがファイルを書き込むたびにBiomeとOxlintが自動で走ります。matcherWrite|Edit を指定しているため、ファイル読み取りやBash実行では発火しません。

動作の流れはこうです。エージェントがファイルを編集すると、Hookがフォーマットを自動修正し、リンターがエラーを検出します。エラー出力はエージェントのコンテキストに注入され、エージェントはそれを読んで次のアクションで修正します。人間が介入する必要はありません。

なぜBiome + Oxlintか

ESLintではなくBiome + Oxlintを選んだ理由は速度です。Biomeは ESLintの50〜100倍の速度で動作します。PostToolUse Hookはミリ秒レベルで完了する必要があるため、高速ツールの選定が必須でした。

ESLintで1秒かかるチェックが、Biomeでは10〜20ミリ秒で完了します。この差はHook実行時に体感的に大きな違いを生みます。

エラーメッセージの設計

リンターのエラーメッセージを「教育ツール」として設計する考え方も取り入れました。カスタムルールのエラーメッセージには以下の構造を持たせます。

ERROR: [何が間違っているか]
  WHY: [理由 + ADRリンク]
  FIX: [具体的な修正手順]
  EXAMPLE:
    // Bad: const data: any = fetchUser()
    // Good: const data: User = await fetchUser()

エージェントはエラーメッセージを読んで自己修正します。メッセージが具体的であるほど、修正精度が上がります。「ルール違反です」だけでは不十分で、「なぜダメか」「どう直すか」まで含めることが重要です。

これはOpenAIの原文でも強調されていた点です。カスタムリンターを使えば、エラーメッセージにADRへのリンクや具体的な修正コードを埋め込めます。汎用リンターの「Expected type but got any」よりも、プロジェクト固有の文脈を含むメッセージの方がエージェントの修正精度は高くなります。

実装3: Plan-Execute分離ワークフロー

一発解決の罠

エージェントは「一発解決」に傾きがちです。大きなタスクを渡すと、全体を一度に解決しようとしてコードが破綻します。これを防ぐために、Plan-Execute分離を導入しました。

具体的な運用

Claude CodeのPlanモードを以下の手順で運用しています。

  1. タスクをClaude Codeに渡し、Planモードで計画を出力させる
  2. 計画を人間がレビューし、単一機能ごとのサブタスクに分解されているか確認する
  3. 承認後、サブタスクを1つずつ実装させる
  4. 各サブタスクの実装後にテストを実行し、通過を確認してから次に進む

この運用で重要なのは「サブタスクの粒度」です。「認証機能を実装」ではなく、「サインアップフォームのUIを実装」「メールバリデーションを追加」「パスワードハッシュ処理を実装」のように、1つのサブタスクが1つのテストファイルに対応する粒度まで分解します。

Anthropicの記事「Effective harnesses for long-running agents」では、この分離をさらに推し進めています。初期化エージェントが環境をセットアップし、コーディングエージェントが1セッションにつき1機能を実装する二段構成です。各セッションで進捗ログを更新し、次のセッションが引き継ぐ仕組みになっています。

自分のプロジェクトではそこまでの自動化はしていませんが、「1タスク=1PR」「各PRにテストを含める」というルールで同様の効果を得ています。

やってみた結果

Before: 頻発していた問題

ハーネス導入前、エージェントの出力には以下の問題が繰り返し発生していました。

  • TypeScriptで any を乱用する(型推論に失敗すると逃げる)
  • 既存ファイルの修正ではなく、類似名の新ファイルを生成する(ゴーストファイル)
  • 過剰なコメントを挿入する(// ユーザーを取得する のような自明なコメント)
  • プロジェクト規約に反するフォーマットで出力する

特にゴーストファイルは厄介でした。userService.ts を修正すべき場面で userServiceNew.ts を新規作成し、既存のimportが壊れるパターンです。レビューで気づかなければ、同じ機能の実装が2つ存在する状態でマージされます。

After: 改善された点

ハーネス導入後、以下の変化がありました。

PostToolUse Hookの効果: ファイルを書き込むたびにリンターが走り、型エラーやフォーマット違反がエージェントのコンテキストに即座に注入されます。エージェントは次のアクションで自己修正するようになりました。any を使った瞬間にリンターエラーが返るため、エージェントが適切な型定義を探して修正します。

CLAUDE.mdのポインタ化の効果: 「ディレクトリ構造はこうなっている」という記述を削除したことで、エージェントが古い情報に引きずられなくなりました。代わりに tree コマンドや find コマンドで実態を確認するようになり、ゴーストファイルの生成が減少しました。

Plan-Execute分離の効果: 大きなタスクで「一発解決」を試みる暴走が減りました。サブタスクごとにテストを通すため、段階的に動作確認しながら進む実装スタイルが定着しています。

定量的な比較は難しいですが、体感として「エージェントの出力を手直しする時間」が半分以下になりました。特にPostToolUse Hookの効果が大きく、フォーマットの手直しがほぼゼロになったのは明確な改善です。

AI生成コードのアンチパターン

OX SecurityやSnykの調査データと、自分の経験を照合した結果、以下の5つが主要なアンチパターンです。

  1. TypeScript any 乱用 --- 型推論に失敗すると any に逃げる。型安全性が崩壊し、ランタイムエラーの温床になります
  2. コード重複 --- 既存コードを検索せず新規生成する。AI利用でコード重複が4倍に増加したという調査データもあります
  3. ゴーストファイル --- 既存ファイルの修正ではなく類似名の新ファイルを作る。utils.ts の横に utilsNew.ts が並ぶ状態です
  4. コメント過多 --- AIリポジトリの90〜100%で発生。// 配列をループする のような自明なコメントが大量に挿入されます
  5. セキュリティ脆弱性 --- Snykの調査では、AIが生成したスキルの36.8%にセキュリティ上の問題が含まれていました

自分の環境では特に(1)(3)(4)が顕著に発生していました。PostToolUse Hookのリンター自動実行で(1)と(4)は大幅に改善しています。(3)はCLAUDE.mdに「既存ファイルの修正を優先し、新規ファイル作成は最小限にする」というポインタを追加して抑制しています。

(5)のセキュリティ脆弱性については、依存パッケージのバージョン固定やlockfileの管理で対応しています。エージェントが追加した依存パッケージは、pnpm audit をCIに組み込むことで検出可能です。

Minimum Viable Harness ロードマップ

「全部やらないと意味がない」と思う必要はありません。段階的に導入できます。

時期 やること
第1週 50行以内のCLAUDE.md(ポインタのみ)を作成。Lefthook pre-commitを設定。PostToolUse Hook(自動フォーマット)を導入。最初のADRを書く
第2〜4週 エージェント失敗のたびにテストまたはルールを追加。Plan→Review→Executeワークフローを確立。E2Eテストツールを導入。Stop Hook(テスト通過を完了条件にするゲート)を設定
2〜3ヶ月目 修正指示付きカスタムリンターを作成。ADRとリンタールールのマッピングを整備。記述的ドキュメントをテスト+ADRに置き換える

第1週の作業は半日で完了します。PostToolUse Hookの設定は settings.json に数行追加するだけです。まず第1週の内容だけ導入して、効果を実感してから次の段階に進むことをおすすめします。

まとめ

ハーネスの「旬」は数ヶ月〜1年かもしれません。LLMの能力が向上すれば、現在のハーネスの一部は不要になるでしょう。

しかし、決定論的ツールによる品質強制、テスト駆動の仕様定義、ADRによる設計判断の記録といった原則自体は、普遍的なソフトウェアエンジニアリングのベストプラクティスです。LLMが進化しても、これらの価値は変わりません。

コンプライアンスの観点でも重要性が増しています。EU AI法が2026年8月に全面施行され、TEVV(テスト・評価・検証・妥当性確認)が義務化されます。ハーネスで構築したテスト基盤やADRは、そのままコンプライアンスのエビデンスとして機能します。

Martin Fowlerも自身のブログでハーネスエンジニアリングを取り上げ、コンテキストエンジニアリング・アーキテクチャ制約・ガベージコレクションの3要素に分類しています。この3つの視点で自分のハーネスを点検してみると、改善すべき箇所が見えてきます。

モデルの入れ替えで1ポイント、ハーネスの改善で22ポイント。投資対効果は明白です。まずは50行のCLAUDE.mdとPostToolUse Hookから始めてみてください。

参考リンク

84
71
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
84
71

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?