AIエージェントを動かしたことがある開発者なら、一度は経験するはずだ。デモでは完璧に動いたのに、長いタスクや本番環境で唐突に壊れる。 しかもモデルのせいではなく、実行環境の設計の問題で。

その「実行環境」こそがハーネスだ。Claude Code完全ガイド2026:インストールから本番運用まで

ハーネスエンジニアリングが問うのは、モデル自体が信頼できないときに、それでもシステムが工学的に振る舞えるかどうかだ。

2026年4月、agentway.dev が Claude Code のソースコードを直接解析した108ページの設計書 “Harness Engineering: A Design Guide to Claude Code” を公開した。単なるユーザー向けドキュメントではない。src/query.tssrc/services/tools/toolOrchestration.tssrc/memdir/memdir.ts などの実装ファイルを解析し、Claude Code が内部的にどう動いているかを逆引きした技術書だ。

この記事ではその内容を、実装ファイルパスと定数テーブルつきで体系化する。

この記事でわかること

  • Claude Code の5層ハーネスモデル(ソース実装ファイル付き)
  • queryLoop() の状態機械設計とインプットガバナンスの全フェーズ
  • コンテキストバジェット定数テーブル(MEMORY.md 上限・autocompact 閾値等)
  • Prompt Too Long / max_output_tokens の具体的リカバリパス
  • マルチエージェントでの状態隔離・検証独立・親子abort伝播
  • CLAUDE.md の階層設計とチーム導入ステップバイステップ
  • Appendix A の7チェックリスト(すぐ使える形式)

なぜハーネスエンジニアリングが必要か——5層モデルの解剖

「モデルが賢ければハーネスは不要」という誤解

多くのAIプロジェクトが抱える前提がある。「モデルが高性能ならコントロール問題は解決する」という考え方だ。

この前提が崩れる理由は、モデルの問題ではなく 実行の構造 にある。Claude Code ソースを見ると、設計者がこの問題に正面から向き合った跡が見える。

  • モデルは一貫した責任を持てない(モデルは間違える)
  • ツールは外部世界に結果を残す(amplification of consequence)
  • コンテキストは膨張する(context rot)
  • 状態は後続ターンに汚染される(state contamination)
  • ユーザーは割り込む(interrupts)
  • エラーは繰り返す(failure is structural, not occasional)

「賢さ」でこれらを解決しようとすると、賢さが不十分なとき(あるいは賢さが違う方向に向かうとき)にシステムが崩壊する。ハーネスが構造として問題を解決するのは、そのためだ。

Claude Code の5層ハーネスモデル

Claude Code ハーネス5層モデル — コントロールプレーン・クエリループ・ツール管理・権限・エラーリカバリの5層構造とソースファイル対応

Claude Code ソースは5つの明確なハーネス層を持つ。これらは独立して設計されているのではなく、上位層が崩れると下位層全体が信頼できなくなる依存関係にある。

名称 核心実装 役割
1 コントロールプレーン constants/prompts.ts, utils/systemPrompt.ts プロンプトの多層合成・行動境界定義
2 クエリループ query.ts:241 継続的な状態機械・入力ガバナンス
3 ツール管理 services/tools/toolOrchestration.ts 並行性安全制御・順序付き実行
4 権限・割り込み hooks/useCanUseTool.tsx, BashTool/prompt.ts allow/deny/ask 三値判断
5 エラー&リカバリ query.ts:453+, compact/autoCompact.ts 構造的エラー処理・サーキットブレーカー

第1層が崩れると: プロンプトが「誰が最後に書いたか」次第になり、クエリループも権限も機能しなくなる。

第5層が崩れると: エラーを catch ブロックで握りつぶすだけになり、問題を後続ターンに転がし続ける。

第1の原則はここから来る: エージェントシステムの核心能力は制約された実行だ。

プロンプトはコントロールプレーン——ペルソナ装飾ではない

「システムプロンプト=キャラクター設定」という误解

「あなたは優秀なAIアシスタントです」——このスタイルのシステムプロンプトを書いたことがある人は多いだろう。Claude Code の設計はこれを明確に否定している。

src/constants/prompts.ts:444getSystemPrompt() は、一本の文字列ではなく セクションの配列 を返す。これはアーキテクチャ上の主張だ。「プロンプトは構造を持つ実行規約であって、ひと続きのキャラクター説明ではない」という。

優先順位のある多層構成

src/utils/systemPrompt.ts:28buildEffectiveSystemPrompt() が明示する合成順序:

優先度: override > coordinator > agent > custom > default
└─ appendSystemPrompt は常に最後に付加

重要な設計判断: proactive モード のとき、agent プロンプトは default を置き換えない。default の後ろに追加される。「職務記述書が一般規約を上書きできない」という組織設計の類比だ。

proactive_mode + agent_prompt:
  base = default + agent  (置き換えではなく拡張)
  return base + appendSystemPrompt

この構造を破ると、最後に書いた人がすべてのルールを上書きできる「落書き板」になる。

4つのプロンプトセクション

Claude Code のシステムプロンプトは少なくとも4カテゴリに分離されている:

flowchart LR SP["System Prompt
(多層合成)"] --> ID["アイデンティティ&ミッション
src/constants/prompts.ts:175
インタラクティブエージェント定義"] SP --> SYS["システムルール
src/constants/prompts.ts:186
ユーザーに見える情報・拒否された
アクションの再試行禁止"] SP --> ENG["エンジニアリング制約
src/constants/prompts.ts:199
不要な最適化禁止・未検証の
完了報告禁止"] SP --> MEM["メモリ&CLAUDE.md
utils/claudemd.ts:1153
プロジェクト・ローカル・チーム・
自動メモリの多層統合"] style SP fill:#1565C0,color:#fff style ID fill:#1A3A5C,color:#E8EAED style SYS fill:#1A3A5C,color:#E8EAED style ENG fill:#1A3A5C,color:#E8EAED style MEM fill:#1A4A2E,color:#E8EAED

プロンプトとキャッシュコスト

多くのプロンプト議論がパフォーマンスを無視するのに対し、Claude Code は実用的だ。src/constants/systemPromptSections.ts:16 でプロンプトセクションを2種類に分類している:

  • cacheable systemPromptSection — キャッシュ再利用可能
  • DANGEROUS_uncachedSystemPromptSection — キャッシュを破壊する

動的なセクション(ターンごとに変化するもの)と静的なセクションを意図的に分離し、不要なキャッシュ無効化を防ぐ。/clear または /compact の後は clearSystemPromptSections() でセクション状態をリセットする。

第2の原則: プロンプトは明示的な制御構造に組み込まれた場合にのみ価値を持つ。

クエリループ——エージェントシステムの心臓部

queryLoop()の4フェーズ構造 — 入力ガバナンス・モデル実行・ツール実行・判断と継続のサイクル(src/query.ts:241)

「一問一答」ではなく「継続的状態機械」

「Claude は一回の API 呼び出しで動く」——この前提でハーネスを設計すると、本番で必ず壊れる。

Claude Code の中心は src/query.ts:219query() だが、本体は src/query.ts:241queryLoop() だ。この関数が持つ State オブジェクトを見れば、設計意図が明確になる:

// src/query.ts:203–217 (skeleton)
state = {
  messages,                       // 会話履歴
  toolUseContext,                  // ツール実行状態
  autoCompactTracking,             // コンパクト追跡
  maxOutputTokensRecoveryCount,    // max_tokens リカバリ回数
  hasAttemptedReactiveCompact,     // リアクティブコンパクト試行済みフラグ
  pendingToolUseSummary,           // 保留中ツール要約
  stopHookActive,                  // stop-hook 有効フラグ
  turnCount,                       // ターン数
  transition,                      // 遷移状態
}

この State は各イテレーションで丸ごと更新される。「スクリプトは『このステップが終わったか』だけ問う。エージェントシステムは『次のステップが失敗したステップの状態から継続できるか』も問わなければならない」——設計書の核心的な主張だ。

フェーズ1: 入力ガバナンス(モデル呼び出し前に整備する)

queryLoop() が最初にすること: モデルに渡す前にコンテキストを整備する。

多くのシステムとの違いはここにある。「大量のコンテキストをそのまま渡してモデルが大事なものを選ぶ」のではなく、「ランタイムが先に整理する」。

モデル呼び出し前の実行順序:
1. メモリプリフェッチ      query.ts:297  — MEMORY.md インデックス読み込み
2. スキル検出プリフェッチ  query.ts:323  — 利用可能スキル一覧
3. メッセージスライス      query.ts:365  — compact境界以降のみ選択
4. tool result budget     query.ts:369  — ツール結果を予算内に収める
5. history snip           query.ts:396  — 履歴を切り詰め
6. microcompact           query.ts:412  — 軽量コンパクト
7. context collapse       query.ts:428  — コンテキスト圧縮
8. autocompact            query.ts:453  — 自動コンパクト(最後の手段)

これは「制御を渡す前に現場を整備する」という伝統的なエンジニアリングの発想だ。洗練とは言えないが、安定性は高い。

フェーズ2: モデル実行はループの一フェーズ

入力ガバナンスの後、query.ts:652 付近でモデル呼び出しに入る。重要なのは、出力を イベントストリームとして消費する こと。最終的な大きなブロブを一度に受け取るのではない。

// skeleton
events = stream_model(state)
for e in events:
  if e.is(tool_use):     schedule(e, state.toolUseContext)
  if e.is(api_error):    return surface(e)
if interrupted:          drain_tools_with_synthetic_results(state); break

「割り込み」の処理が特徴的だ。 ユーザーが実行を中断したとき、進行中のツール呼び出しに合成結果(synthetic result)を付与して閉鎖する。これにより tool_use/tool_result の対応表(ledger)が必ず閉じた状態を保つ。dangling tool_use ブロックを残さない。

フェーズ3: ツールの並行制御

ツール実行は toolOrchestration.ts が管理する。partitionToolCalls() がツール呼び出しを分類し、isConcurrencySafe() で並行安全性を判定:

  • 安全なツール: バッチ実行(並列)
  • 危険なツール: 逐次実行(シリアル)

並行パスでは、コンテキスト変更をバッファしてオリジナルブロック順序で再適用する(toolOrchestration.ts:31–63)。これはファイルシステム・ターミナル・Git という不可逆的な外部世界に触れるツールを扱うための保守的設計だ。

無制限な並列化はブラスト半径を拡大する
ツールを並列実行するほど「何かが失敗したとき」の影響範囲が広がる。Claude Code がここで保守的な戦略を取っているのは、ファイルシステムへの変更・シェル実行・Gitリポジトリへの操作が「元に戻せない結果」を生むからだ。

3つのループ不変条件

assert state.turnCount_{t+1} >= state.turnCount_t   # ターン数は単調増加
assert every emitted tool_use has a tool_result      # ledgerは必ず閉じる
assert hasAttemptedReactiveCompact => no further compact  # 再コンパクトループ防止

これらのうち1つでも崩れると、クエリループ全体が信頼できなくなる。

第3の原則: クエリループはエージェントシステムの心臓部だ。入力ガバナンス・ストリーム消費・ツールスケジューリング・リカバリ・停止条件はすべてハートビートに属する。

ツール・権限・割り込み——モデルが世界に触れる層

「言える」から「残せる」へのリスク転換

モデルがテキストのみ出力するとき、最悪でも「過信した文体」になる。ツールを呼べるようになると、リスクは 言語リスクから実行リスク に転換する。

Claude Code の答えは明確だ: ランタイムがツールの実行可否を決める。モデルが決めるのではない。

allow / deny / ask の三値判断

src/hooks/useCanUseTool.tsx が実行可否を三値で決定する:

// permission decision skeleton
decision = hasPermissionsToUseTool(tool, input, ctx)
match decision:
  allow: exec(tool, input)
  deny:  reject(reason)
  ask:   route_to(coordinator | reviewer | operator)

// 不変条件
assert decision  {allow, deny, ask}   // 三値、絶対に崩さない
assert ask => never auto-escalates to allow  // askは自動的にallowにならない

「ask」が自動的に「allow」にエスカレートしないことが重要だ。三値が二値(allow/deny)に縮退するとき、人間の承認ポイントが意味を失う。

Bash に高密度制約が必要な理由

Bash は最も危険なツールだ。ファイル・プロセス・ネットワーク・Git リポジトリに直接アクセスでき、シェルリダイレクトとパイプラインのセマンティクスまで持つ。ドメイン境界がほぼない。

src/tools/BashTool/prompt.ts:42 以降には詳細なオペレーションルールが列挙されている:

git config をランダムに変更しない
フックをスキップしない (--no-verify 禁止)
git add . は避ける
pre-commit失敗後に --amend で前のコミットを畳み込まない
明示的に指示されない限りコミットしない
デフォルトでpushしない

「細かすぎる」と感じる人もいる。しかし高リスクなインターフェースは高密度の制約が必要だ。能力が強いほど制御は細かくする ——これはハーネスエンジニアリングの核心原則の一つだ。

StreamingToolExecutor と割り込み処理

src/services/tools/StreamingToolExecutor.ts がツール実行をライフサイクル管理する。特に重要なのは 割り込み時のセマンティクス:

割り込みが来たとき、現在実行中のツール呼び出しに合成結果を付与して完結させる。これにより:

  1. 会話の ledger(tool_use と tool_result の対応表)が閉じた状態になる
  2. 次のターンが「中断された実行」の残骸から始まらない
  3. エラーリカバリが正しく機能する前提条件が満たされる

第4の原則: ツールはモデル能力の自然な延長ではなく、スケジューリング規律に従う管理された実行インターフェースだ。

コンテキストガバナンス——メモリ・CLAUDE.md・コンパクトの設計

「コンテキストを増やすほど賢くなる」という危険な神話

エージェントシステムはライブラリではない。コンテキストは「倉庫に保管」するものではなく、高コストで膨張しやすく自己汚染する予算だ。

flowchart TD CG["コンテキストガバナンス"] --> LM["長期記憶: CLAUDE.md
stable rules / team norms"] CG --> PM["永続メモリ: MEMORY.md + body files
cross-session facts"] CG --> SM["セッションメモリ: SessionMemory
within-session continuity"] CG --> TM["一時対話: chat history
current turn context"] LM -->|"claudemd.ts:1153
層別ロード"| M["モデル入力"] PM -->|"memdir.ts:187
インデックス参照"| M SM -->|"SessionMemory/prompts.ts
継続ブリーフ"| M TM -->|"slice / snip / compact"| M style CG fill:#1A3A5C,color:#fff style M fill:#1A4A2E,color:#fff

CLAUDE.md 階層:「誰の命令か」で優先度が変わる

src/utils/claudemd.ts の先頭でメモリ層が明確に定義されている:

managed memory  → /etc/claude-code/CLAUDE.md         (最高優先: システム管理者)
user memory     → ~/.claude/CLAUDE.md                 (ユーザー設定)
project memory  → CLAUDE.md / .claude/CLAUDE.md       (リポジトリ設定)
              → .claude/rules/*.md                   (ルールファイル群)
local memory    → CLAUDE.local.md                     (ローカル上書き、git管理外)

現在のディレクトリに近いファイルほど優先度が高く、モデルのアテンション前部に配置される。安定したチームルールを一時的なチャットと混ぜない のが設計の意図だ。

@include は許可された拡張子のテキストファイルのみ対象にする。バイナリ・巨大ドキュメント・プロンプトインジェクションリスクのあるファイルの誤包含を防ぐ実践的な抑制だ。

MEMORY.md はインデックス、本文ではない

src/memdir/memdir.ts の設計判断: ENTRYPOINT_NAMEMEMORY.md だが、直接コンテンツを書く場所ではない。インデックスだ。

正しい使い方:
  1. 具体的メモリ内容を専用ファイルに書く(例: feedback_testing.md)
  2. MEMORY.md に1行ポインタを追加する

  MEMORY.md の役割 = 目次
  body file の役割 = 本文

なぜこの分離が重要か。MEMORY.md は頻繁にロードされる。肥大化するとコンテキストがインデックス重みで圧迫される。

buildMemoryLines() が設定するハードリミット:

定数名 目的
MAX_ENTRYPOINT_LINES 200 MEMORY.md の最大行数
MAX_ENTRYPOINT_BYTES 25,000 MEMORY.md の最大バイト数

超過時: truncateEntrypointContent() が自動切り捨て+警告を追加する。

セッションメモリ: 「チャット履歴の保存」ではない

src/services/SessionMemory/prompts.ts が定義するデフォルトセクション:

- Current State(最重要)
- Task specification
- Files and Functions
- Workflow
- Errors & Corrections(優先保存)
- Codebase and System Documentation
- Learnings
- Key results
- Worklog

アップデート時のルール: 「ノートについて話すな」「テンプレート構造を変えるな」「Current State を最新に保て」「各セクションを密度高く、予算内に保て」。

これはチャット履歴をコピーするのではなく、セッションを継続ブリーフに蒸留する設計だ。

セッションメモリのバジェット定数:

定数名 目的
MAX_SECTION_LENGTH 2,000 セクションあたりの最大長
MAX_TOTAL_SESSION_MEMORY_TOKENS 12,000 セッションメモリ合計予算

超過時: Current State と Errors & Corrections を優先して積極的な凝縮を要求する。

autocompact: コンテキストガバナンスは予算管理だ

src/services/compact/autoCompact.ts の実装:

getEffectiveContextWindowSize()
  = コンテキストウィンドウサイズ
  - MAX_OUTPUT_TOKENS_FOR_SUMMARY (20,000)  ← compact自体のコスト予約
  - AUTOCOMPACT_BUFFER_TOKENS (13,000)       ← 早期警告バッファ

重要な設計哲学: 酸素が尽きてから行動するのではなく、余裕があるうちに compact を実行する。準備なしのシステムは通常ケースでは倹約に見えるが、リスク決済を後のターンに先送りしているだけだ。

autocompact のバジェット定数一覧:

定数名 ソース
MAX_OUTPUT_TOKENS_FOR_SUMMARY 20,000 compact/autoCompact.ts
AUTOCOMPACT_BUFFER_TOKENS 13,000 compact/autoCompact.ts
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES 3 compact/autoCompact.ts
MAX_ENTRYPOINT_LINES 200 memdir/memdir.ts
MAX_ENTRYPOINT_BYTES 25,000 memdir/memdir.ts
MAX_SECTION_LENGTH 2,000 SessionMemory/prompts.ts
MAX_TOTAL_SESSION_MEMORY_TOKENS 12,000 SessionMemory/prompts.ts

AutoCompactTrackingStatecompactedturnCounterturnIdconsecutiveFailures を追跡する。失敗は追跡され、制限される。

サーキットブレーカー: MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 を超えると遮断。これは「大量のAPIコールが繰り返しのautocompact失敗で無駄になった」という具体的な過去の教訓から来ている設計だ。

compactConversation(): 「要約」ではなく「制御された再起動」

src/services/compact/compact.tscompactConversation() は、多くの人が想像する「会話を要約する」を超えている。

compact 前の整理:

  • stripImagesFromMessages(): 画像・ドキュメントを [image] [document] マーカーに置換
  • stripReinjectAttachments(): どうせ再注入されるアタッチメントを削除(トークン節約)

compact 後の再構築(これが重要):

  • 古い readFileState をクリア
  • post-compact ファイルアタッチメントを再生成
  • プランアタッチメントを再注入
  • プランモードアタッチメントを再注入
  • 呼び出し済みスキルアタッチメントを再注入
  • deferred tools・エージェントリスト・MCP命令デルタを再注入
  • セッション開始フックと post-compact フックを実行
  • compact境界メッセージ(pre-compact トークン数・境界メタデータ)を記録

これは「チャット要約」ではなく制御された再起動だ。古いコンテキストを新しい動作基盤に変換する。この再構築を省略したシステムは、compact後に「なんとなく覚えている」状態になり、ツール状態・プラン状態・アタッチメント状態を失って再発見コストを払うことになる。

per-skill truncation beats dropping: スキルコンテンツも切り詰めても、全体を削除するより先頭の重要制約を保持する方が良い。これがガバナンスであり、単なる絞り込みとの違いだ。

第5の原則: コンテキストは作業メモリだ。ガバナンスはシステムが作業を継続できる状態を保つためにある。

エラーとリカバリ——失敗はメインパスだ

3つのエラーリカバリパス — PTL(Prompt Too Long)・max_output_tokens・autocompact失敗のそれぞれのリカバリ手順とサーキットブレーカー定数

「例外」ではなく「構造的条件」として設計する

多くのソフトウェアが成功パスをメインテキストとして、失敗パスを例外として扱う。エージェントシステムにこのアプローチは通じない。

トークン上限ヒット・プロンプト長超過・ツール拒否・ユーザー割り込み・フックブロック・APIリトライ——これらは「たまに起きること」ではない。長時間セッションでは構造的に発生する

3つのリカバリパスの詳細

PTL (Prompt Too Long) リカバリ:

on prompt_too_long:
  if stagedCollapse > 0:          # Step 1: 段階的コンテキスト縮小
    recoverFromOverflow()
  elif not hasAttemptedReactiveCompact:  # Step 2: 強制compact(1回のみ)
    tryReactiveCompact()
  else:                           # Step 3: エラーサーフェス
    surface(e)
    skip_stop_hooks()             # stop-hookをスキップ

# 破壊性: Step1 < Step2 < Step3

リアクティブコンパクトを「1回のみ」に制限しているのは、hasAttemptedReactiveCompact フラグが管理する。compact がループを作ることを防ぐ不変条件: assert hasAttemptedReactiveCompact ⇒ no further compact

max_output_tokens リカバリ:

on max_output_tokens:
  if cap < MAX:                   # Step 1: 上限引き上げ
    raise(maxOutputTokensOverride)
    retry()
  else:                           # Step 2: 継続メタメッセージ
    append(meta_continue_msg)
    retry()

ここで重要なのは「要約より継続を優先」する設計哲学だ。切断後にユーザーを失敗状態に閉じ込めず、「続きはここから」という形で再開させる。

autocompact 失敗リカバリ:

AutoCompactTrackingState:
  compacted: bool
  turnCounter: int
  turnId: str
  consecutiveFailures: int       # ← 失敗を追跡

# circuit breaker
if consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES:
  break  # サーキットブレーカー発動、エラーを報告

停止条件は複数が必要

多くのシステムが停止条件を1つしか持たない。これは問題だ。完了と失敗が同じ停止条件を共有すると、失敗したまま「完了」として終わるか、永遠に終わらないかのどちらかになる。

Claude Code は4つの停止セマンティクスを持つ: 完了 / 失敗 / リカバリ / 継続。これらは transition state として管理される。

第6の原則: エラーパスはメインパスだ。PTL・max_output_tokens・割り込み・フックループ・compact失敗は長時間エージェントにとって通常の気象現象だ。

マルチエージェントと検証——不確実性を分割する

マルチエージェント設計 — Coordinator-Workerパターン・状態隔離・検証独立・親子abort伝播の詳細図(src/utils/forkedAgent.ts)

マルチエージェントの本当の価値は速度ではない

「並列実行で速くなる」——これはマルチエージェントの最も表面的な価値だ。より深い価値は不確実性の分割にある。

リサーチ・実装・検証・統合を異なる責任コンテナに分割することで:

  • 状態が隔離される(汚染が伝播しない)
  • 役割が分離される(自己評価バイアスが構造的に防がれる)
  • コーディネーターが理解を再収束させる

forkAgent(): キャッシュ整合性を保った分岐

src/utils/forkedAgent.tsforkAgent():

// skeleton
params = CacheSafeParams {
  systemPrompt,
  userContext,
  systemContext,
  toolUseContext
}
ctx = createSubagentContext(parent)  // 可変状態はデフォルト隔離
hooks.fire(SubagentStart, { agent_id, agent_type })
defer hooks.fire(SubagentStop, { agent_transcript_path })

// 不変条件
assert parent.abort => propagate(child.abort)  // 孤立タスク防止

CacheSafeParams がプロンプトキャッシュの整合性を保つ。子エージェントが親のmutable stateを汚染しない。

親abort → 子abort の伝播が必須: これがないと、親エージェントが停止しても子エージェントがAPIを消費し続ける「孤立タスク」が生まれる。

コーディネーターの責任:転送ではなく統合

src/coordinator/coordinatorMode.ts が示すコーディネーターの責任:

やるべきこと: Worker出力の矛盾を解消し、優先順位付けし、一貫した最終出力に統合する。

やってはいけないこと: Worker出力をそのままユーザーに転送する。

「マルチエージェントを使っているが全員が似たような作業をして、誰も統合や検証を担当していない」——これは並列化された混乱だ。コーディネーターが統合責任を持たないと、マルチエージェントの複雑さだけが増えて品質は向上しない。

検証は独立フェーズでなければならない

src/skills/bundled/verify.ts が実装する独立検証ステージ。

実装者は自分の変更を自然と信頼しすぎる。モデルはさらにそうなる傾向がある。だから重要な作業では、検証を独立した専用フェーズにする必要がある。

Verifier エージェントの設計原則:

  • 懐疑的なシステムプロンプトでチューニング
  • Generator/Implementer とは別のコンテキストで動作
  • 「問題がなければ承認」という明確な判断権限を持つ
  • Generator に差し戻す権限も持つ
Verifierの評価軸を事前に定義する
「なんでも批判してください」という曖昧な指示は機能しない。機能要件充足・エッジケース・エラーハンドリング・パフォーマンスなど、評価軸を事前に定義すると Verifier の一貫性が上がり、Generator との往復が効率化する。

ライフサイクルフックとオブザーバビリティ

src/utils/hooks/hooksConfigManager.ts が公開するライフサイクルイベント:

SessionStart      — 組織レベルコンテキストの注入
SessionEnd        — アーカイブ・クリーンアップ
SubagentStart     — サブエージェント起動時
SubagentStop      — トランスクリプトパス記録
PreCompact        — compact境界前の記録
PostCompact       — compact後の再初期化
FileChanged       — ファイル変更イベント
DirectoryChange   — ディレクトリ変更イベント

これらにより「正しいタイミングで処理する」が実現する。SubagentStop フックでトランスクリプトを記録し、PreCompact フックでコンパクト前の状態を保存する。

メモリの陳腐化と維持

src/memdir/memoryTypes.tsMemoryType を定義し、stale-memory verification discipline が実装される。

stale memory は検証の義務がある: 特定のファイルパス・関数名・フラグを名指しするメモリは、「書かれた時点での主張」だ。関数が変名され、ファイルが削除されている可能性がある。推薦する前にファイルの存在を確認し、関数名を grep する必要がある。

第7・8の原則: マルチエージェントは不確実性を分割するためにある / 検証は実装から独立しなければならない。

チーム導入——個人の技術を組織の制度に変える

「個人の熟練度」と「組織の成熟度」の違い

熟練ユーザーがエージェントを使いこなせるのは、継続的な監視・背景知識・状況判断に依存しているからだ。チームで展開すると、この前提が崩れる。

「誰が危険なコマンドを知っているか」「どのメモリが陳腐化しているか」「どのスキルがサブエージェントを分岐させるか」「どのステップが承認を省略できるか・できないか」——これらが一部の専門家の頭の中にある限り、チームでの安全な使用は不可能だ。

段階的ロールアウトチェックリスト

設計書 Chapter 8 の「Builder Checklist」を実用的な形式で示す:

Week 1: 最小制御境界の確立
□ layered CLAUDE.md を稼働 — team / personal / project すべてバージョン管理済み
□ 共有検証定義 — lint / type / test コマンドが CLAUDE.md に記載済み
□ 禁止ゾーン — 危険なディレクトリ・コマンドクラスをリポジトリレベルで制約
□ 許容タスクのスコープを文書化

Week 2: ワークフローの形式化
□ 承認を結果の重大度でティア分け — allow / deny / ask とその判断基準
□ 最初の3スキル — 前提条件・事後条件・検証定義つき
□ 「既知の問題ありで完了」ポリシーを明示化

Week 3: 観測可能性の確立
□ stop / post-tool-use フックを導入 — トランスクリプト + タスク出力を記録
□ 月次メモリメンテナンス体制
□ 基本リプレイ(Git diff / PR / CI)の gap-free 確認

Gate: 新メンバーが専門家なしに使える状態になったら次フェーズへ

CLAUDE.md の使い分け設計

チームレベル CLAUDE.md に含めるもの:

  • リポジトリレベルのハード制約(禁止ディレクトリ・危険コマンドクラス)
  • 共有検証期待値(最低限実行すべきチェック)
  • コラボレーション規律(未要求変更の上書き禁止・コミット前の確認など)
  • 出力規律(レビュー報告の形式など)

含めてはいけないもの:

  • 急速に変化する一時的なプロセス
  • 狭いタスクサブセットにしか関係しない指示
  • コマンド・スキル・スクリプトで表現する方が適切な詳細

チーム CLAUDE.md が百科事典になると、安定性と信頼性を同時に失う。「このルールは現在有効か、それとも数ヶ月前の残骸か」が判断できなくなる。機能するのは「基盤」として安定しているもので、「掲示板」ではない。

検証定義はスキル数より先に決める

最もよくある失敗: チームが「完了の共有定義」を持っていない。

  • ある人は「動けば完了」だと思う
  • ある人は「テストなしで半分しか確認していない」で承認する
  • ある人は「モデルの説明が妥当に見える」で完了とする

この状況では、賢いシステムが最も弱いバーを満たすことを学ぶ。スキルが増えるほど、曖昧さのスピードが上がるだけだ。

最初に決めるべき3つ:

  1. どのタスククラスが独立した検証を必要とするか
  2. 検証に最低限含めるアクション(テスト・ローカル実行・ログ・人間承認)
  3. 「既知の問題ありで完了」が許可される条件

承認はリスクティアで設計する

ツール名ではなく結果の重大度でティア分けすることが重要だ。

ティア 対象 判断
Read 読み取り・純粋な分析・一覧表示 allow
Write ワークスペース変更・書き込み操作 ask
Irreversible Git push・外部ネットワーク・センシティブ環境 deny または ask

不可逆なアクションには decision ∈ {deny, ask} が必須:

// approval rule template
rule {
  name: "git_push"
  match: { tool: "bash", args_pattern: "git push*" }
  risk_tier: irreversible
  decision: ask
  ask_to: reviewer
  audit: transcript + PR_comment + CI_log
}
assert every irreversible action => decision ∈ {deny, ask}
assert ask never auto-escalates to allow

フックはロールアウトの後半に属する

フックは強力だが、「便利だから」という理由で最初に導入するのは間違いだ。

メンテナンスされないスクリプト・不明なトリガータイミング・手動ステップより高いデバッグコスト——これらが発生すると、フックは利便性より技術的負債になる。

より成熟した結論: フックはタイミングバウンドなアクションに最適で、ベースラインガバナンスが安定した後に属する。

第9・10の原則: マルチエージェントは不確実性を分割するためにある / チーム制度は個人の技術より重要だ。

10の原則——設計書の結論

108ページが最終的に圧縮される10の原則を、実装の根拠と一緒に示す。

flowchart TD P1["1. モデルを不安定な
コンポーネントとして扱う
チームメイトではない"] --> P2["2. プロンプトは
コントロールプレーンの一部
ペルソナ装飾ではない"] P2 --> P3["3. クエリループが
心臓部
一問一答ではない"] P3 --> P4["4. ツールは
管理された実行I/F
能力の自然延長ではない"] P4 --> P5["5. コンテキストは
作業メモリ
倉庫ではない"] P5 --> P6["6. エラーパスが
メインパス
afterthoughtではない"] P6 --> P7["7. リカバリは
継続を最適化する
要約優先ではない"] P7 --> P8["8. マルチエージェントは
不確実性を分割する
速度のためだけではない"] P8 --> P9["9. 検証は
独立でなければならない
自己評価はバイアスがかかる"] P9 --> P10["10. チーム制度が
個人技術より重要
習熟依存は拡張しない"] style P1 fill:#1A3A5C,color:#E8EAED style P5 fill:#1A4A2E,color:#E8EAED style P10 fill:#4A1A3A,color:#E8EAED

設計書の最終文:

ハーネスエンジニアリングが問うのは、モデル自体が信頼できないときに、それでもシステムが工学的に振る舞えるかどうかだ。

Claude Code ソースが本当に教えているのは自制心だ: 不安定さを既知の入力として受け入れ、その入力を前提にプロンプト・ループ・ツール・メモリ・コンパクト・リカバリ・検証・チームワークフローを設計する。

ハーネスより興奮、制度より賢さ、確信より検証——これらを逆転させたチームが、ハーネスエンジニアリングの入り口に立っている。

Appendix A: チェックリスト7選——実行可能な制約に変える

原則は言葉にしやすく、チェックリストにしにくい。以下は設計書 Appendix A の内容を日本語で実用的な形式にしたものだ。

A.1 エージェントランタイム設計チェックリスト

□ 各ターンを独立したQ&Aでなく、明示的なクエリループがあるか
□ リカバリ・予算・コンパクション・フック・ターンカウンタを記録する
  クロスターンStateオブジェクトがあるか
□ モデル出力を最終ブロブではなくイベントストリームとして処理するか
□ 割り込みツール呼び出しを合成結果で閉鎖してledgerを完結させられるか
□ 完了・失敗・リカバリ・継続を別々の停止セマンティクスとして定義しているか
□ オーバーフロー後の緊急対応だけでなく、長時間セッション用のコンテキスト
  予算管理があるか

2〜3項目が明確に答えられない場合: システムは「デモ可能」段階で「ワークフロー可能」段階ではない。

A.2 プロンプト設計チェックリスト

□ アイデンティティ・行動ルール・ツール制約・出力規律が別々に整理されているか
□ プロンプトソースの優先順位が明示的か
  (default → project → custom → append → agent)
□ 危険なオペレーション・未承認行動・検証規律が暗示ではなく明示ルールか
□ プロンプトがランタイム強制で処理すべき責務を抱えていないか
□ チームが緊急テキストを追記するだけでなく安定維持できるか

実践的テスト: セクションを1つ削除してもシステムの構造的振る舞いがほぼ変わらないなら、そのセクションはおそらく装飾だ。

A.3 ツールと権限設計チェックリスト

□ ツール呼び出しが直接モデル呼び出しではなく統合オーケストレーターを経由するか
□ 並行実行には安全性の明示的証明が必要か(デフォルト許可でないか)
□ allow / deny / ask のセマンティック分岐があるか
□ 高リスクツールが通常ツールと異なる扱いを受けているか
□ 割り込み・フォールバック・兄弟失敗が明示的な閉鎖セマンティクスを持つか
□ Bash と ReadTool がほぼ同じガバナンスでないか(同じなら不十分)

A.4 コンテキストガバナンスチェックリスト

□ 長期ルール・永続メモリ・セッション継続・一時対話が層別されているか
□ インデックスファイルと本文ファイルが分離されているか(インデックス膨張防止)
□ メモリ・セッションメモリ・スキルアタッチメントにトークン予算があるか
□ compact出力スペースを事前予約しているか(ウィンドウ満杯まで待たない)
□ compact後に作業セマンティクス(プラン・スキル・ファイル・ツール状態)を復元するか
□ compact自体が失敗したときのリカバリ戦略があるか

コンテキストガバナンスの良いシステムはやや倹約に見える。その倹約は強みだ。

A.5 エラーリカバリチェックリスト

□ 回復可能なエラーが即座にサーフェスされる前にリカバリブランチを通るか
□ リカバリパスが低破壊から高破壊の順に段階的か
□ リアクティブコンパクト・stop-hook・リトライループが互いを噛まないガードがあるか
□ max_output_tokens後は要約より継続を優先するか
□ 自動リカバリにカウンタ・リトライ上限・サーキットブレーカーがあるか
□ 割り込みを明示的な閉鎖セマンティクスが必要な失敗状態として扱うか

決して止まらないリカバリシステムは、決して動かないリカバリシステムと同様に危険だ。

A.6 マルチエージェントチェックリスト

□ forkデザインがキャッシュ安全パラメータでprompt-cache整合性を保つか
□ mutable状態がデフォルトで子エージェントから隔離されるか
□ リサーチ・実装・検証・統合の役割が明確に分離されているか
□ コーディネーターがWorker出力を転送するだけでなく統合・合成しているか
□ 検証が実装から独立しているか
□ エージェントライフサイクルが観測可能・割り込み可能・回収可能か
□ 親のabortが子に伝播して孤立タスクを防ぐか

マルチエージェントを主張するシステムが全エージェントに似た作業をさせて誰も統合・検証を担当していないなら、それは並列化された混乱だ。

A.7 チーム導入チェックリスト

□ 何が属し何が属さないかの明確な指針つき layered CLAUDE.md があるか
□ スキル数を増やす前に検証定義が標準化されているか
□ 承認境界が結果と環境の重大度でティア分けされているか
□ 主要な制度が静的ドキュメントではなく適切なフックタイミングに紐づいているか
□ トランスクリプト・タスク出力・フックイベントがリプレイ証拠として保持されているか
□ 陳腐化したメモリ・廃止ルール・無効スキルのメンテナンスポリシーがあるか

エージェントを持続的に使えるチームは、少数の専門家に依存するチームではなく、一般メンバーが正しく動けるチームだ。

FAQ

**Q: ハーネスエンジニアリングはプロンプトエンジニアリングとどう違いますか?** プロンプトエンジニアリングが「何を言わせるか」を扱うのに対し、ハーネスエンジニアリングは「どう実行させるか・状態をどう管理するか・失敗したときどう回復するか」を扱います。プロンプトはハーネスの一部です(コントロールプレーン)が、ハーネスはクエリループ・ツール管理・権限制御・コンテキストガバナンス・エラーリカバリを含む構造全体を指します。
**Q: Claude Code のソースを直接読むのと本書の違いは?** ソースを直接読むと「何があるか」はわかりますが「なぜこう設計されているか」は分かりにくいです。本書はソース解析から「設計判断の理由」を抽出し、再利用可能な原則として体系化しています。特に `query.ts` のような2000行超のファイルでは、設計意図の理解が直接読解より重要です。
**Q: Claude Code 以外のエージェントシステムに適用できますか?** はい。5層モデルの原則(コントロールプレーン・クエリループ・ツール管理・権限・リカバリ)は Claude Code 固有ではなく、エージェントシステム一般に適用できます。ただし具体的な定数値(MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES=3 など)と実装ファイルパスは Claude Code 固有です。
**Q: 「ハーネスが厚い」と「薄い」のどちらが良いですか?** どちらが良いかではなく、モデルの能力と用途によって変わります。Claude Opus 4.6 レベルの高性能モデルでは、以前は必須だった Planner スプリント構造が不要になった事例があります。モデルが新しく高性能になるほどハーネスを薄くできる傾向がありますが、エラーリカバリ・権限制御・コンテキスト予算は能力に関係なく必要です。
**Q: autocompact が連続失敗するとどうなりますか?** `MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3` を超えると、サーキットブレーカーが発動してエラーをユーザーに報告します。過去に「繰り返しのautocompact失敗で大量のAPIコールが無駄になった」という具体的な教訓から設計された定数です。失敗は許可されますが、無限には許可されません。

まとめ: ハーネスは興奮より先に来る

設計書が108ページをかけて言っていることは、最終的にシンプルだ。

モデルは間違える。ツールは結果を残す。コンテキストは膨張する。状態は後続ターンを汚染する。ユーザーは割り込む。失敗は繰り返す。

これらを「賢さ」で解決しようとすると、賢さが足りないとき(または賢さが違う方向に向かうとき)に崩壊する。ハーネスが構造として解決するのは、そのためだ。

Claude Codeのハーネスエンジニアリング実装パターンも合わせて参照すると、5層モデルの各層をどう実装に落とし込むかがより具体的になる。また、本番環境へのハーネス設計では、Generator/Evaluator分離・ラチェット原則・Context Rot防止の実践的な設計パターンを詳細に解説している。

ハーネスエンジニアリングの入門は、賢いモデルを探すことより先に始まる。どう実行を制約するか・どう失敗を構造として扱うか・どうチームの制度として定着させるか——その問いへの答えを設計に組み込んだとき、エージェントシステムは真の意味で信頼できるものになる。

参照ソース