取り組みの背景
夜中に走らせていた記事自動投稿のエージェントが、ある朝、存在しないカテゴリへのリンクを大量に張ったまま push 直前まで進んでいたことがあります。幸い、最後に通している検証スクリプトがそれに気づき、作業をやり直してくれました。賢いモデルに替えても、この種の事故はゼロにはなりません。効いたのは、エージェントの賢さではなく、その外側に張っておいた「枠組み」の方でした。
この枠組みを体系立てたものが「ハーネスエンジニアリング」です。HashiCorp の創業者 Mitchell Hashimoto 氏が提唱した概念で、AI エージェントが安定して成果を出すための環境・制約・フィードバックループを設計する技術体系 を指します。
多くの開発チームが「AI エージェントに自動化を任せたら、勝手におかしなことをしてしまった」「AI が何度も同じ間違いを繰り返す」という問題に直面しています。これらは実は、AI エージェント自体の問題ではなく、その AI を取り囲む「ハーネス」の設計不足 が原因なのです。
ハーネスエンジニアリングとは何か
定義と 4 つの役割
ハーネスエンジニアリングは、以下の 4 つの柱で構成されます。
Constrain(制約) — AI に「してはいけないこと」を明確に指定する。例: 削除操作の禁止、ファイルパーミッション変更の禁止
Inform(情報提供) — AI に「必要な文脈」を効果的に渡す。例: ファイルの存在確認、テスト結果、コミット履歴
Verify(検証) — AI の出力が「安全で正しいか」を自動チェックする。例: Linter、型チェック、ユニットテストの実行
Correct(修正) — 検証で失敗した場合、エラーメッセージをフィードバックして AI に自動修正させる
これらのステップが組み合わさることで、AI が自律的に学習し、改善される環境 が成立します。
私自身、個人開発で運用しているサイト群とアプリの自動化で、この4本柱を意識する前と後では安定度がまったく違いました。具体的には、push 前に必ず通す検証スクリプト群(リンク整合性・設定ファイルの妥当性・robots.txt の機械検証)を「Verify」として整備し、違反が出たら作業をやり直す「Correct」のループを固定しています。エージェントの賢さを上げる工夫より、この外側の整備の方が、結果への効き方が大きいというのが実感です。
4 つの柱の詳細解説
1. Constrain(制約)
AI に対して「これ以上は行くな」という境界線を引く。これはハーネスエンジニアリングの基本です。
例えば自動コードレビューの場合:
許可される操作 :コメント追加、修正提案、リントエラーの指摘
禁止される操作 :直接ファイル削除、コミット強制push、依存ライブラリの無断更新
制約がないと、AI は「より多く修正すればするほど良い」と判断し、過剰指摘や破壊的な変更を起こします。
# AI エージェントのポリシー(YAML定義)
constraints :
forbidden_operations :
- delete_file
- force_push
- modify_permissions
- update_dependencies_without_approval
allowed_operations :
- add_comment
- suggest_changes
- run_linter
- propose_refactoring
2. Inform(情報提供)
AI が正しい決断を下すために必要な文脈 を用意します。これは「良い質問」と同じくらい重要です。
自動レビュー AI に与えるべき情報:
// AI への文脈提供の例
const codeReviewContext = {
// ファイルの基本情報
filename: "src/auth/login.ts" ,
language: "typescript" ,
// 変更内容
changes: {
added_lines: 45 ,
removed_lines: 12 ,
diff: "..." , // unified diff
},
// プロジェクトの文脈
project_standards: {
coding_style: "Airbnb" ,
test_coverage_minimum: 80 ,
security_requirements: "OWASP Top 10 compliant" ,
},
// テスト結果
test_results: {
unit_tests: "PASS" ,
type_check: "PASS" ,
security_scan: "FAIL (1 medium, 0 critical)" ,
},
// コミット履歴(このファイルの最近の変更)
recent_commits: [
"fix: SQL injection vulnerability" ,
"feat: add MFA support" ,
"refactor: extract auth logic" ,
],
};
この情報を与えることで、AI は「単なる文法チェック」を超えた「文脈に基づくレビュー」ができます。
3. Verify(検証)
AI の出力を自動チェック します。これがハーネスエンジニアリングの「保険」です。
検証ステップの例:
#!/bin/bash
# AI が修正を提案した後、自動検証を実行
echo "=== Verification Step ==="
# 1. Linter チェック
echo "Running ESLint..."
npx eslint src/auth/login.ts --fix
if [ $? -ne 0 ]; then
echo "FAIL: Linter errors detected"
exit 1
fi
# 2. 型チェック
echo "Running TypeScript compiler..."
npx tsc --noEmit
if [ $? -ne 0 ]; then
echo "FAIL: Type errors detected"
exit 1
fi
# 3. ユニットテスト実行
echo "Running unit tests..."
npm test -- src/auth/login.test.ts
if [ $? -ne 0 ]; then
echo "FAIL: Tests failed"
exit 1
fi
# 4. セキュリティチェック
echo "Running security scan..."
npx snyk test --severity-threshold=high
if [ $? -ne 0 ]; then
echo "FAIL: Security vulnerabilities found"
exit 1
fi
echo "✓ All verifications passed"
重要:検証に失敗したら、単に「NG」と判定するのではなく、その情報を AI にフィードバックする ことで、AI は自動修正に進むことができます。
4. Correct(修正)
検証で検出されたエラーを、AI が自動的に修正する プロセス。
// 修正プロセスの擬似コード
async function autoReviewFlow ( prContent : string ) : Promise < void > {
// Step 1: Constrain - どんな操作が許可されているか確認
const allowedActions = validateConstraints (prContent);
// Step 2: Inform - AI に必要な文脈を提供
const context = gatherContext (prContent);
// Step 3: Verify - AI の提案を検証
const validation = await runVerificationSuite (context);
if ( ! validation.passed) {
// Step 4: Correct - エラーをフィードバックして修正を試みる
const feedbackMessages = formatVerificationErrors (validation);
const fixAttempt = await ai. generateFix ({
originalCode: prContent,
errors: feedbackMessages,
constraints: allowedActions,
});
// 修正版を再検証
const retryValidation = await runVerificationSuite (fixAttempt);
if (retryValidation.passed) {
console. log ( "✓ Auto-fix successful" );
return fixAttempt;
} else {
console. log ( "✗ Auto-fix failed, requiring human review" );
return null ;
}
}
}
自動レビューフローの実装(5ステップ)
以下は、実際に使える自動コードレビューの完全フローです:
Step 1: PR を監視し、AI レビュー開始
// GitHub Actions ワークフロー
import { Octokit } from "@octokit/rest" ;
const octokit = new Octokit ({ auth: process.env. GITHUB_TOKEN });
async function triggerReview ( owner : string , repo : string , pr : number ) {
const { data : prData } = await octokit.pulls. get ({ owner, repo, pull_number: pr });
console. log ( `Reviewing PR #${ pr }: ${ prData . title }` );
// AI レビュー処理へ
}
Step 2: 変更内容と文脈を取得
async function gatherChangeContext ( owner : string , repo : string , pr : number ) {
// PR の変更ファイル一覧を取得
const { data : files } = await octokit.pulls. listFiles ({ owner, repo, pull_number: pr });
// 各ファイルの diff を取得
const changes = await Promise . all (
files. map ( async ( file ) => ({
filename: file.filename,
diff: file.patch,
additions: file.additions,
deletions: file.deletions,
}))
);
return changes;
}
Step 3: AI による初期レビュー
async function performAIReview ( changes : any [], context : any ) {
const reviewPrompt = `
You are a code reviewer. Review the following changes:
${ changes . map ( c => `File: ${ c . filename } \n ${ c . diff }` ). join ( " \n --- \n " ) }
Constraints:
- Only suggest improvements, do NOT modify files directly
- Flag security issues (SQL injection, XSS, etc.)
- Check for performance issues
- Verify test coverage (minimum 80%)
- Suggest refactoring only if it significantly improves readability
Respond with JSON format:
{
"severity": "CRITICAL|HIGH|MEDIUM|LOW",
"type": "security|performance|style|testing",
"line": <line_number>,
"suggestion": "<human-readable suggestion>"
}
` ;
const review = await ai. analyze (reviewPrompt);
return review;
}
Step 4: 自動検証ステップ
#!/bin/bash
# verify.sh - 検証を自動実行
set -e
echo "Running verification suite..."
# Linting
npm run lint 2>&1 | tee lint-output.txt || LINT_STATUS = $?
# Type checking
npm run typecheck 2>&1 | tee typecheck-output.txt || TYPE_STATUS = $?
# Unit tests
npm test -- --coverage 2>&1 | tee test-output.txt || TEST_STATUS = $?
# Security scanning
npx snyk test 2>&1 | tee security-output.txt || SECURITY_STATUS = $?
# Summary
if [ -z " $LINT_STATUS " ] && [ -z " $TYPE_STATUS " ] && [ -z " $TEST_STATUS " ]; then
echo "✓ All checks passed"
exit 0
else
echo "✗ Some checks failed"
exit 1
fi
Step 5: AI による修正と PR コメント
async function autoFixAndComment ( owner : string , repo : string , pr : number , issues : any []) {
// CRITICAL/HIGH 問題のみ修正を試みる
const critical = issues. filter ( i => i.severity === "CRITICAL" || i.severity === "HIGH" );
if (critical. length > 0 ) {
const fixPrompt = `
These critical issues were found:
${ critical . map ( i => `- ${ i . suggestion }` ). join ( " \n " ) }
Generate a fix that:
1. Addresses the issues above
2. Does NOT break existing tests
3. Maintains code style consistency
` ;
const fix = await ai. generateFix (fixPrompt);
// PR にコメント
await octokit.issues. createComment ({
owner, repo, issue_number: pr,
body: `## 🤖 Auto-Review Results \n\n ${ formatReviewResults ( issues ) }` ,
});
}
}
実運用の落とし穴と対策
落とし穴 1: 「過剰指摘」(Over-specification)
症状 : AI が細かい指摘を大量にしすぎて、開発者の作業が止まる。
対策 :
# 重要度フィルタリング
severity_levels :
CRITICAL : # セキュリティ脆弱性、テスト失敗
action : block_merge
HIGH : # パフォーマンス問題、コード規約大幅違反
action : comment_and_request_fix
MEDIUM : # スタイル改善、軽度の構造改変
action : comment_only
LOW : # 単語のスペル、コメント修正
action : ignore
落とし穴 2: 「振り子現象」(Oscillation)
症状 : AI が A → B → A という循環修正を繰り返す。
例:
AI: 「この変数名はtempよりbufferの方がいい」
AI(次の実行): 「この変数名はbufferよりtempの方がいい」
対策 :
// 修正履歴を追跡し、同じファイルの同じ行で 2 回以上の修正を防ぐ
const modificationHistory : Map < string , Set < number >> = new Map ();
function preventOscillation ( filename : string , lineNumber : number ) : boolean {
if ( ! modificationHistory. has (filename)) {
modificationHistory. set (filename, new Set ());
}
const lines = modificationHistory. get (filename) ! ;
if (lines. has (lineNumber)) {
console. log ( "⚠️ Oscillation detected, skipping" );
return false ; // この行は修正しない
}
lines. add (lineNumber);
return true ;
}
落とし穴 3: 「AI が権限超過」
症状 : AI が許可されていない操作(依存ライブラリの更新、デプロイ実行など)を行います。
対策 :
# 実行前に権限チェック
ALLOWED_PATHS = (
"src/**"
"tests/**"
"docs/**"
)
FORBIDDEN_OPERATIONS = (
"rm -rf"
"npm update"
"git push --force"
"git revert"
)
for forbidden in "${ FORBIDDEN_OPERATIONS [ @ ]}" ; do
if grep -q " $forbidden " proposed_changes.txt ; then
echo "❌ Forbidden operation detected: $forbidden "
exit 1
fi
done
Antigravity 2.0 と CLI が共有する「同じハーネス」
ここ最近の Antigravity をめぐる動きは、ハーネスエンジニアリングの考え方と相性が良いと感じています。Antigravity 2.0 はデスクトップアプリ・CLI・SDK・Managed Agents API という複数の面を持ち、新しい Antigravity CLI もそれらと同じエージェントハーネスを共有する 設計になっています。コアのエージェント改善が、どの面から使っても自動的に反映される、という考え方です。
ここで私が大事にしているのは、自分側のハーネス(制約と検証)を特定の UI に紐づけない ことです。Constrain と Verify をリポジトリ側の成果物——AGENTS.md や push 前の検証スクリプト群——として置いておけば、操作の入り口がデスクトップでもターミナルでも、同じ安全網がそのまま効きます。
実際、Gemini CLI から Antigravity CLI への移行が話題になっていますが、入り口のツールが入れ替わっても、リポジトリに置いたハーネスは作り直す必要がありませんでした。エージェントを賢くする努力はベンダー側に任せ、自分は「させないこと」と「必ず通す検証」を資産として持っておく。この役割分担が、ツールの世代交代に振り回されないための要だと考えています。
4サイト並行運用で測った、ハーネスの費用対効果
私自身は個人開発で複数の技術ブログを並行運用しており、記事の生成から push までをエージェントに任せています。そこで痛感したのは、事故の大半は「賢さ」ではなく「検証」で止まる という事実です。
push 前に通している検証は、内部リンクの整合性チェック、フロントマターの妥当性チェック、robots.txt の機械検証など、数本のスクリプトに過ぎません。それでも、体感では作業をやり直すことになった事故のおよそ80%が、この検証段階で食い止められています。残りの約20%が人間の確認に上がってくる、という配分です。本番運用でいちばん怖いのは、壊れたリンクや設定ファイルの破損がそのまま公開されてしまうことなので、これらは検出した時点で作業を止め、その場で対処・回避できるようにしています。
もちろん検証スクリプトはタダではありません。1本増やすたびに、誤検知をなくすための調整と、仕様変更への追従という維持コストが発生します。私はこの維持コストを払う価値があるかどうかを基準にしており、「一度でも本番に出たら痛い種類の事故 」だけを検証に昇格させることを推奨します。表示が少し崩れる程度の問題は警告に留め、リンク切れや設定ファイルの破損のように後戻りが効きにくいものだけを、作業を止める検証に格上げしています。
制約の粒度をどう決めるか
Constrain(制約)で最初に迷うのが、どこまで縛るかという粒度の問題です。締めすぎるとエージェントは身動きが取れなくなり、緩すぎると事故が起きます。私が落ち着いたのは、「取り返しがつくかどうか 」で分ける考え方でした。
操作の性質 例 扱い
取り返しがつかない ファイル削除・force push・依存の一括更新・デプロイ 原則禁止 (人間の明示承認が要る)
取り返しはつくが影響が広い 複数ファイルの一括置換・設定変更 許可するが検証を必須にする
局所的で戻しやすい コメント追加・1ファイル内の修正提案 自由に任せる
この線引きにしてから、制約リストを細かく書き連ねる必要がなくなりました。不可逆な操作だけを名指しで止め、あとは検証に委ねる——この方針なら、新しい操作が増えても判断に迷いません。可逆な失敗はやり直せばよく、不可逆な失敗だけが本当に怖い。その当たり前の感覚を、そのまま制約に翻訳しているだけです。
人間の役割
ハーネスエンジニアリングを導入しても、すべてを自動化することはできません 。人間の役割は:
ハーネス自体の設計 :AI に何をさせるか、何をさせないか
初期判断 :CRITICAL な問題が検出されたとき、それを本当に修正すべきか
スケーラビリティの監視 :AI の提案がプロジェクトの成長に追いついているか
つまり、人間は「意思決定者」であり、AI は「提案・実行者」という役割分担です。
まとめ
ハーネスエンジニアリングは、AI エージェントが「怖い自動化ツール」ではなく「信頼できるチームメイト」になるための設計手法 です。
4 つの柱(制約・情報提供・検証・修正)を組み合わせることで、AI は自律的に学習し、プロジェクトに適応していきます。その過程で開発者の作業は大幅に軽減されます。
重要なのは、AI は「賢さ」ではなく「環境設計」で初めて価値を発揮する ということです。
最初の一歩としては、4本柱を全部作ろうとせず、「Verify だけ」を今日入れることをお勧めします。lint・型チェック・テストを1本のスクリプトにまとめ、エージェントの作業後に必ず実行する——これだけで事故の大半は検証段階で止まります。制約と自動修正は、検証が回り始めてから足せば十分間に合います。