生成AI(LLM)を自社プロダクトや業務フローに組み込み、「AIエージェント」として自律動作させる試みが急速に広がっています。しかし開発が進むにつれ、「プロンプトをどれほど調整してもハルシネーションが止まらない」「無限ループや誤操作で高額なトークン費用が発生した」といった深刻な壁に直面する現場が急増しています。
こうした「AIの暴走・不確実性」をプロンプトエンジニアリングだけに頼らず、決定論的なソフトウェア制御構造(ガードレール、フック、状態管理、サンドボックス)で安全に囲い込む技術——それが今世界的に注目を集める「ハーネスエンジニアリング(Harness Engineering)」です。
本記事では、ハーネスエンジニアリングの基本概念から、F1カーや馬具に例えた分かりやすいアナロジー、実際の泥臭いトラブルと解決パターン、Python/TypeScriptでの実装ハンズオンまでを徹底図解で解説します。AIエージェントのポテンシャルを100%引き出し、安全に運用したいエンジニア必見の完全ガイドです。
1. プロンプトの工夫だけでは限界が来る理由
「いくらプロンプトを調整しても、ハルシネーションが止まらない」現場の悲鳴
生成AI(LLM)をプロダクトや社内ツールに組み込み、開発を進めているエンジニアの多くが、ある時期を境に共通の壁に突き当たります。
「『絶対に存在しない関数を捏造しないでください』とプロンプトに赤字で書いたのに、平然と存在しないAPIを呼び出してエラーになる」
「『出力は必ずJSON形式にしてください』と指示したのに、たまに先頭に余計な解説文をつけてレスポンスがクラッシュする」
「『過去の会話をすべて踏まえて回答して』とシステムプロンプトを肥大化させたら、レスポンスが遅くなった上に重要な制約を無視し始めた」
こうした課題に対して、従来の私たちは「プロンプトエンジニアリング(Prompt Engineering)」で対処しようとしてきました。プロンプトにFew-Shot(例示)を追加し、システム指示を推敲し、思考のステップ(Chain of Thought)を丁寧に記述する——。
確かに単発のチャットボットであれば、プロンプトの工夫で精度を80%から90%に引き上げることができるかもしれません。しかし、残りの10%の不確実性が、商用プロダクトやミッションクリティカルなシステムにおいては致命傷となります。どれほど完璧なプロンプトを書いても、LLMが確率的な言語モデルである以上、ハルシネーションや指示無視を100%ゼロにすることは不可能です。
単一入出力から「自律型AIエージェント(動的ループ)」へ進化したことで生じた新たなリスク
さらに事態を複雑にしているのが、AIの利用形態が「単発の質疑応答(1-shot Chat)」から「自律型AIエージェント(Agentic Loop)」へと急速にシフトしていることです。
AIエージェントとは、人間が与えた目標(Goal)に対して、自ら計画(Plan)を立て、ツール(Tools)を実行し、結果を観察(Observation)しながら完了まで試行錯誤を繰り返すシステムです。
この自律ループの中で、AIモデルが一度でも判断ミスやハルシネーションを起こすとどうなるでしょうか?
- 存在しないコマンドを実行してエラーになり、そのエラーメッセージを誤解してさらに間違ったコマンドを呼び出す「エラーの泥沼ループ(無限ループ)」。
- 本来アクセスしてはならないデータベースやファイルを誤って変更してしまう「権限の逸脱・破壊的変更」。
- 無限ループに陥ったまま数百回のAPI呼び出しを繰り返し、一晩で数万円〜数十万円のAPI費用を溶かしてしまう「トークン費用の爆発」。
これらはもはや、プロンプトの文言を少し修正するだけで解決できる領域を超えています。「プロンプト=AIに対するお願い(希望指示)」であり、決定論的なソフトウェア開発においては、お願いではなく「絶対に破れないガードレール(制御構造)」が必要なのです。
2. ハーネスエンジニアリングとは何か?(アナロジーで理解する)

ハーネスエンジニアリングの基本定義
「ハーネスエンジニアリング(Harness Engineering)」とは、確率的に挙動するAIモデルの周囲を、決定論的なソフトウェア(ツール、状態管理、フック、ガードレール、サンドボックス等)で囲み、自律的かつ安全に目的を達成させるための制御・実行環境の設計技法です。
AIエージェントの性能や信頼性は、AIモデル自体の頭脳(LLMのパラメータや賢さ)だけで決まるわけではありません。「モデルの賢さ」と「モデルを囲むシステム制御環境(ハーネス)」の掛け算によって決まります。
ext{AIエージェントの品質・信頼性} = ext{AIモデル(モデル能力)} imes ext{ハーネス(制御実行環境)}
どんなに最新で高性能なLLMであっても、ハーネスが脆弱であれば簡単に暴走し、成果物を破壊します。逆に、軽量で高速なモデルであっても、強固なハーネスがあれば驚くほど正確で安定した成果を出し続けることができます。
日常・工学のアナロジーで理解する「ハーネス」
1. F1レーシングカーのアナロジー
- AIモデル(LLM) = 1,000馬力を誇る「F1マシンの強力なエンジン」。
- ハーネス(Harness) = サーキットの「防護壁(ガードレール)」、「ABS・トラクションコントロール」、「ピットからの無線制御システム」、「データ計測器」。
どれだけ馬力のあるエンジンがあっても、サーキットに防護壁がなく、ブレーキ制御システムやピットとの通信回路がなければ、最初のコーナーでクラッシュして終わりです。ハーネスがあるからこそ、ドライバー(AI)は全開でアクセルを踏み込むことができます。
2. 馬と馬具(Harness)のアナロジー
そもそも「Harness(ハーネス)」の原義は、馬を馬車につなぎ留め、騎手の意図通りに制御するための「馬具(手綱、轡、鞍)」です。野性味あふれるパワフルな馬(LLM)に馬具(ハーネス)を装着することで初めて、安全かつ効率的に人や荷物を運ぶエネルギーとして活用できるのです。
プロンプト vs コンテキスト vs ハーネスの決定的な違い
AI開発における3つの技術概念の整理をしておきましょう。
| 概念 | 役割 | 制御の仕組み | 特徴・限界 |
|---|---|---|---|
| プロンプトエンジニアリング | AIへの指示文・ペルソナの最適化 | 自然言語による指示(ソフトな制約) | 確率的。指示を無視・忘却するリスクがある。 |
| コンテキストエンジニアリング | 必要な情報・資料の動的注入(RAG等) | 検索結果やログのテキスト挿入 | 情報を与えすぎるとコンテキスト肥大化・埋もれが発生。 |
| ハーネスエンジニアリング | AIの行動空間・入出力・状態の決定論的制御 | プログラム(Hooks、Sandbox、バリデーター等) | 決定論的。AIの暴走を物理的・プログラム的に阻止。 |
ハーネスが担う3つのコア役割
ハーネスは、大きく分けて以下の3つの機能をシステム内に提示します。
- 入出力ガードレール(Input/Output Guardrails)
AIモデルに入力されるデータおよびAIモデルから出力されるデータをプログラムで検査・フィルタリングします。スキーマ違反、不正コマンド、機密情報の漏洩を即座にブロックします。
- 状態管理(State Management)
AIエージェントの試行錯誤の履歴、現在の達成度、使用トークン数、変数などを決定論的なファイルやデータベースで保持し、スレッドやコンテキストのリセットが起きても状態を復元できるようにします。
- 自己修正フィードバック回路(Self-Correction Loop / Critique)
AIの出力やコード実行時のエラーログを自動検知し、「何が原因で失敗したか」を構造化されたフィードバックとしてAIに送り返し、自律的にコードや入力を修正させます。
3. 現場で起きた「痛い失敗」とハーネスによる解決パターン

ここでは、実際の開発現場で直面した3つのリアルな泥臭いトラブルと、それをハーネスエンジニアリングによってどのように克服したかを紹介します。
失敗例1: API呼び出しの無限ループで一晩で数万円のトークン費用が吹き飛んだ話
発生した泥臭いトラブル
ある自動調査AIエージェントを構築した際、Web検索ツールと要約ツールをループ実行させる構成にしていました。ある日、対象のWebページが404エラーを返し、ページの取得に失敗しました。
LLMは「ページが取得できなかったので、別の検索クエリで試そう」と判断しましたが、何度検索しても404エラーが発生するドメインを指してしまいました。LLMは同じ失敗を認識できず、「検索 ➔ 404エラー ➔ 再検索」という無限ループに突入。夜間に放置されたエージェントは、8時間で3,500回のAPI呼び出しを行い、約6万円のAPI費用を消費した上で何も成果を出さずに停止していました。
ハーネスによる解決策:Post-Hookと天井カウンター
プロンプトに「5回失敗したら諦めてください」と書くのをやめ、モデルの外側に決定論的なカウンターフック(Post-Hook)を配置しました。
- エージェントのツール呼び出しごとに、ハーネス側のカウンターをインクリメント。
- 同一ツール・同一エラーが3回連続した場合は、LLMの判断を待たずにハーネスがループを強制介入・強制終了(Circuit Breaker)。
- 1タスクあたりの最大実行ターン数(Max Turns)と最大消費トークン数の「物理天井」をコード側で固定。
プロンプトによる指示ではなく、プログラム側の物理制限によって、API費用の爆発と無限ループを100%防止できるようになりました。
失敗例2: 存在しないライブラリを自信満々に呼び出してコードがクラッシュ
発生した泥臭いトラブル
Pythonコードを自動生成して実行するエージェントを開発していた際、データ処理のタスクでLLMが import super_pandas_utils という存在しない架空のライブラリを含むコードを生成しました。
そのまま実行コードに渡されたため、ModuleNotFoundError が発生。LLMにエラーメッセージを渡すと、「失礼しました。修正します」と回答したものの、今度は import pandas_advanced_helper という別の架空ライブラリを提案し、エラーのモグラ叩き状態に陥りました。
ハーネスによる解決策:Sandbox検証と静的解析Pre-Hook
LLMが生成したコードを直接実行環境に渡す前に、ハーネス(Pre-Hook / Sandbox) を挟む設計に変更しました。
- LLMが生成したコードに対し、実行前にAST(抽象構文木)解析を行い、インポートされているライブラリが実行環境(
pip list/ 承認済みリスト)に存在するか静的チェック。 - 未承認ライブラリが含まれている場合、コードを実行すらさせずに「利用可能なライブラリ一覧」を添えて即座に再生成を指示。
- コード実行自体をサンドボックス環境(Docker容器/隔離環境)で行い、実行エラーログを構造化JSONとしてキャプチャしてLLMに還元。
これにより、存在しないライブラリの呼び出しによるクラッシュがゼロになり、生成コードの実行成功率が大幅に向上しました。
失敗例3: LLMが未承認のデータ更新を実行しそうになった冷や汗体験
発生した泥臭いトラブル
社内データを更新するタスクで、LLMエージェントにデータベースの検索と修正権限を与えていました。特定の条件に合致するユーザーレコードを更新する指示を出した際、LLMがSQLの WHERE 句の条件を誤解し、テーブル全件を更新する UPDATE users SET status = 'inactive'; に類似する変更コマンドを組み立てて実行しようとしました。
幸い開発環境のテストログで気づいたため事なきを得ましたが、本番環境であれば全顧客データが書き換わる大事故になるところでした。
ハーネスによる解決策:HITL(Human-in-the-Loop)ゲートキーパー
破壊的アクション(書き込み、削除、外部送信、決済等)の実行手前に、人間の承認を必須とするインターセプトフック(HITL) を導入しました。
- アクションの属性を「Read-Only(読み取り専用)」と「Destructive(非可逆的変更)」に明確に分類。
- Destructiveなアクションが呼び出された場合、ハーネスが即座に実行を一時停止(Pause)。
- 実行しようとしている具体的な操作内容と変更対象を人間に通知し、人間が「承認(Approve)」ボタンを押した場合のみ実行を再開(Resume)。
プロンプトで「注意して更新してください」と頼むのではなく、権限レベルでの分離と人間チェックをシステム的に担保しました。
4. ハーネス構築の実践パターンとコード例(ハンズオン)
ここからは、実際にTypeScript/Node.jsおよびPythonを用いてハーネス制御構造を構築するための具体的パターンとコードスニペットを解説します。コードは省略せず、そのままコピペして動作させられる完全な形で記述します。
パターン1: Pre-Hook / Post-Hook による入出力ガードレール
AIモデルの呼び出し直前(Pre-Hook)で入力値を検証・補正し、呼び出し直後(Post-Hook)で出力フォーマットや安全性をプログラムでチェックするパターンです。
Python実装例: 決定論的スキーマバリデーションとPost-Hook
import json
import re
from typing import Dict, Any, Optional
class HarnessGuardrailError(Exception):
"""ハーネスのガードレール検証で発生するエラー"""
pass
class LLMHarnessGuardrail:
def __init__(self, max_allowed_tokens: int = 2000):
self.max_allowed_tokens = max_allowed_tokens
def pre_hook_input_check(self, prompt: str) -> str:
"""
[Pre-Hook] LLM呼び出し前に入力プロンプトの長さや禁止ワードをチェック
"""
if not prompt or len(prompt.strip()) == 0:
raise HarnessGuardrailError("[Pre-Hook Error] プロンプトが空です。")
# 簡易的なトークン数/文字数安全制限
if len(prompt) > 10000:
raise HarnessGuardrailError("[Pre-Hook Error] 入力文字数が制限(10,000文字)を超過しています。")
# システムプロンプトの追加や安全サニタイズ処理
sanitized_prompt = prompt.replace("<script>", "").replace("</script>", "")
return sanitized_prompt
def post_hook_output_validate(self, raw_response: str, required_keys: list[str]) -> Dict[str, Any]:
"""
[Post-Hook] LLMの出力が正しいJSON構造であり、必要なキーを満たしているか検証
"""
# JSONブロックの抽出(LLMが```json ... ``` で囲んできた場合への対応)
json_match = re.search(r'\{.*\}', raw_response, re.DOTALL)
if not json_match:
raise HarnessGuardrailError("[Post-Hook Error] 出力からValidなJSONオブジェクトが検出できませんでした。")
json_str = json_match.group(0)
try:
parsed_data = json.loads(json_str)
except json.JSONDecodeError as e:
raise HarnessGuardrailError(f"[Post-Hook Error] JSONのパースに失敗しました: {str(e)}")
# 必須キーの存在チェック
missing_keys = [key for key in required_keys if key not in parsed_data]
if missing_keys:
raise HarnessGuardrailError(f"[Post-Hook Error] 必須キーが不足しています: {missing_keys}")
return parsed_data
# --- 利用例 ---
if __name__ == "__main__":
harness = LLMHarnessGuardrail()
# 疑似的なLLMレスポンス(キーが不足している例)
mock_llm_response = '```json
{"title": "ハーネスエンジニアリング解説", "status": "draft"}
```'
try:
# Pre-Hook
clean_input = harness.pre_hook_input_check("ハーネスエンジニアリングについて解説して")
print("Pre-Hook Check: PASS")
# Post-Hook (必須キー 'author' が欠落しているためエラー検知)
validated_data = harness.post_hook_output_validate(
raw_response=mock_llm_response,
required_keys=["title", "status", "author"]
)
print("Post-Hook Check: PASS", validated_data)
except HarnessGuardrailError as e:
print(f"ハーネスが安全にエラーをキャッチしました ➔ {e}")
パターン2: Self-Correction Loop(エラー自動検知・再試行回路)
エラーが発生した際、人間に頼らずハーネスがエラー内容を解析し、「修正用コンテキスト」としてLLMに再投入して自己修正(Self-Correction)を行わせる回路です。
TypeScript実装例: 自律再試行ループ(Circuit Breaker付き)
import { GoogleGenAI } from '@google/genai';
interface ExecutionResult {
success: boolean;
output?: any;
error?: string;
}
export class SelfCorrectionLoopHarness {
private maxRetries: number;
constructor(maxRetries: number = 3) {
this.maxRetries = maxRetries;
}
/**
* コード実行を模倣する仮の評価関数
*/
private mockExecuteCode(code: string): ExecutionResult {
if (!code.includes('const result =')) {
return {
success: false,
error: "SyntaxError: 'const result =' という変数宣言が存在しません。"
};
}
if (code.includes('invalid_function()')) {
return {
success: false,
error: "ReferenceError: invalid_function は未定義の関数です。"
};
}
return {
success: true,
output: "計算結果: 42"
};
}
/**
* 自己修正ループの実行
*/
public async runWithSelfCorrection(
initialPrompt: string,
mockLLMGenerate: (prompt: string) => Promise<string>
): Promise<string> {
let currentPrompt = initialPrompt;
let attempt = 0;
while (attempt < this.maxRetries) {
attempt++;
console.log(`
--- [Attempt ${attempt}/${this.maxRetries}] LLM呼び出し実行 ---`);
// 1. LLMによるコード生成
const generatedCode = await mockLLMGenerate(currentPrompt);
console.log(`[生成コード]:
${generatedCode}`);
// 2. ハーネスによる実行・検証
const execResult = this.mockExecuteCode(generatedCode);
// 3. 検証成功判定
if (execResult.success) {
console.log(`[ハーネス判定]: 成功! (Output: ${execResult.output})`);
return generatedCode;
}
// 4. 検証失敗 ➔ エラーログをコンテキストに還元して修正プロンプトを作成
console.warn(`[ハーネス判定]: 失敗 ➔ エラー検出: ${execResult.error}`);
currentPrompt = `
前回の試行であなたが生成したコードで以下のエラーが発生しました。
【発生したエラー】
${execResult.error}
【失敗したコード】
${generatedCode}
【指示】
上記のエラーメッセージをよく分析し、エラーの原因を修正した正しいコードのみを再生成してください。
`;
}
throw new Error(`[Circuit Breaker] 最大再試行回数 (${this.maxRetries}回) に達したため、処理を安全に終了しました。`);
}
}
// --- 利用例 (疑似実行) ---
async function main() {
const harness = new SelfCorrectionLoopHarness(3);
// 試行回数に応じてエラーを吐き、最後に成功する疑似LLM
let mockCount = 0;
const mockLLM = async (prompt: string): Promise<string> => {
mockCount++;
if (mockCount === 1) {
return "function compute() { invalid_function(); }";
} else if (mockCount === 2) {
return "function compute() { return 42; }"; // 'const result =' がない
} else {
return "const result = 42; console.log(result);";
}
};
try {
const finalCode = await harness.runWithSelfCorrection("数値を計算するJavaScriptコードを生成して", mockLLM);
console.log("
【最終完成コード】:", finalCode);
} catch (error: any) {
console.error(error.message);
}
}
main();
5. 設計思想の深化:3階層構造とLoop Engineeringへの発展

ハーネスエンジニアリングを実務でスケールさせていくためには、コードレベルのフックだけでなく、システム全体の「設定・指示の構造化(アーキテクチャ設計)」が必要になります。
Instructions / Project Rules / Skills の3階層分離管理
システムプロンプトやコンテキストにすべてのルールや知識を詰め込むと、コンテキストウィンドウが肥大化し、重要ルールの忘却(Lost in the Middle)や指示の相反が発生します。これを防ぐのが「3階層分離管理」です。
+-------------------------------------------------------+
| 1. Global Instructions (最上位・普遍的行動原則) |
| - プランモード、セキュリティ、事前Web検索原則 |
+-------------------------------------------------------+
|
v
+-------------------------------------------------------+
| 2. Project Rules (プロジェクト固有規約 / GEMINI.md) |
| - ディレクトリ構成、コーディング規約、ログ保存手順 |
+-------------------------------------------------------+
|
v
+-------------------------------------------------------+
| 3. Skills (個別カプセル化機能 / SKILL.md) |
| - リサーチ、ブログ執筆、コードレビュー、画像生成 |
+-------------------------------------------------------+
- 第1階層: Global Instructions(基本行動原則)
AIエージェント全体で絶対に遵守すべき共通ルール(例:破壊的操作の確認、ハルシネーション防止のための事前検索、出力トーン)。エントリーポイントは軽量に保ちます。
- 第2階層: Project Rules(プロジェクト個別規約)
リポジトリやプロジェクト直下の
GEMINI.mdやAGENTS.mdに記述。使用する技術スタック、ディレクトリ構造、テスト実行コマンド、ログの記録手順などを配置します。 - 第3階層: Skills(単機能に独立・カプセル化された手順書)
特定のタスク(「ブログ執筆」「Gitコミット」「AWSデプロイ」等)に必要な手順や入力・出力仕様だけを独立したフォルダ・ファイル(
SKILL.md)としてモジュール化。必要な時だけコンテキストにロード(動的ロード)します。この3階層に分離することで、コンテキストの消費を最小限に抑えつつ、相互干渉のない高精度な制御が可能となります。
次のフロンティア:「ループエンジニアリング(Loop Engineering)」へ
ハーネスエンジニアリング(動的制御環境)が完成すると、その上に「ループエンジニアリング(Loop Engineering)」のシステムが構築可能になります。
- ハーネスエンジニアリング = 1回のタスク実行を安全かつ正確にガードする制御環境。
- ループエンジニアリング = 実行ログを蓄積し、失敗原因を自己分析し、次回実行時のルールやプロンプトをAI自身が自動更新・改善し続ける永久循環サイクル。
人間が毎回手動でプロンプトを修正する時代は終わり、「AIが自らのハーネスを運用し、自ら改善ループを回し続ける時代」へと進化しているのです。
6. よくある質問(FAQ)
Q1: 最新の超高性能モデル(GPT-4o、Gemini 1.5 Pro等)を使えば、ハーネスは不要になりませんか?
A: いいえ、モデルが高性能化するほどハーネスの重要性は増します。
モデルの知能が上がるほど、AIが取れるアクションの自由度や影響範囲(外部ツール連携、コード実行、DB操作)が広がります。どれほどIQが高い人間であっても、会社の経理権限や本番サーバーの操作権限を無条件・ノーチェックで渡すことはないのと同様です。自由度が高い高性能モデルだからこそ、事故を防ぎポテンシャルを100%安全に引き出すハーネスが必要不可欠になります。
Q2: ハーネスを作り込みすぎると、オーバーヘッドや応答遅延(レイテンシ)が増大しませんか?
A: 適切な設計を行えば、むしろ全体の処理時間は短縮されます。
すべての処理で重い検証を行うのではなく、軽微な入力チェックは高速な正規表現や型チェック(Pre-Hook)で済ませ、非可逆的なアクションの時のみ厳密な検証を行う「段階的ガードレール」を設計します。無駄なハルシネーションによるエラーの再試行ループを未然に防げるため、結果としてトータルの処理時間とAPIコストは削減されます。
Q3: 個人開発や小規模なツール開発でもハーネスを組むメリットはありますか?
A: 非常に大きなメリットがあります。
特に「APIコストの無駄遣い防止」や「開発中のローカルファイルの誤削除・破棄の防止」において、最小限のPre/Post Hookやエラー時安全停止回路(Circuit Breaker)を数行書いておくだけで、開発体験(DX)と安心感が劇的に変わります。
7. まとめ:AIエージェント時代にエンジニアが目指すべき姿
「プロンプトエンジニアリング」の時代、エンジニアの役割は「AIにどう上手に語りかけるか(呪文の探求)」でした。
しかし、AIが自律的に行動する「AIエージェント」の時代において、エンジニアに求められる本質的なスキルは「AIが失敗できない堅牢なシステム(ハーネス)をいかに美しく設計するか」へとシフトしています。
- 確率的なAIの出力を、決定論的なプログラムでガードする。
- 試行錯誤のプロセスを可視化し、安全にリカバリーできる構造を作る。
- 人間とAIの役割分担(HITL)を明確に定義する。
これこそが、これからのAI時代を生き抜くフルスタックエンジニア・PMに求められるコアコンピタンス「ハーネスエンジニアリング」です。
まずは自作のエージェントやスクリプトに、数行の「入力チェック(Pre-Hook)」や「最大実行数の上限ガード」を仕込むことから始めてみませんか?その小さな一歩が、AIの真のポテンシャルを解き放つ大きな一歩となるはずです。

コメント