Tips & Tricks

ハーネスエンジニアリング完全ガイド|Claude Codeに学ぶAIエージェントの作り方

プロンプトだけでLLMは使いこなせない。ツール・文脈・制御ループを束ねる「ハーネス」を、動くコードで解説。Claude Code実装例から自作エージェントまで。

「ChatGPTにプロンプトを投げるだけ」の時代は終わりました。 2025年以降、AIエンジニアリングの焦点は急速に ハーネスエンジニアリング (harness engineering) に移っています。Anthropic の社内ブログでも、OpenAI のエージェント研究でも、最も頻繁に登場するキーワードの一つです。

でも「ハーネス」って何?と聞かれて即答できる人はまだ少ない。この記事では 動くコードClaude Code 自体の設計 を題材に、ハーネスエンジニアリングをじっくり解説します。読み終わる頃には、自分のエージェントをゼロから組めるようになっているはずです。

ハーネスとは「AIの足場」である

ハーネス (harness) は元々「馬具」や「安全帯」を意味する英単語です。ソフトウェア業界では「テストハーネス (test harness)」のように 「何かを動かすための外側の仕組み」 を指します。

AI の文脈では、LLM を取り巻くラッパー層 のことです。もう少し具体的に言うと、LLM が実世界のタスクをこなすために必要な以下の要素を束ねたものです。

  • ツール群: ファイルを読む、コマンドを実行する、APIを叩く…
  • 文脈管理: 何を記憶し、何を忘れ、何を圧縮するか
  • 制御ループ: いつ呼び、いつ止め、いつ再試行するか
  • 権限と安全装置: 破壊的操作を勝手にやらせない仕組み
  • メモリ: セッションをまたいで保持する知識

プロンプトは、このハーネスに投入する「1つの入力」に過ぎません。ハーネスが貧弱だと、どんなに巧妙なプロンプトを書いても性能は頭打ち です。これが近年「プロンプトエンジニアリングだけでは足りない」と言われる理由です。

なぜハーネスが重要なのか: OODAループで考える

LLM 単体でできるのは「テキストの続きを生成する」ことだけ。実世界のタスクを解くには、軍事戦略でいう OODAループ (Observe → Orient → Decide → Act) を回す必要があります。

フェーズ内容担当
Observe (観測)環境の状態を取得する (ファイル読み、DB問い合わせ)ハーネス
Orient (状況判断)取得情報を整理してLLMに渡すハーネス
Decide (決定)次に何をするか判断するLLM
Act (実行)決めたことをやる (コマンド実行、API呼び出し)ハーネス

ご覧の通り 4フェーズ中3フェーズがハーネスの担当 です。LLM が得意なのは Decide だけ。残りを支える足場の品質が、エージェント全体の品質を決めます。

実例で理解する: 「3つのハーネスレベル」

同じ「ブログ記事を生成して」というタスクを、3段階のハーネスで解いてみます。

レベル1: 素のAPI呼び出し (ハーネスほぼなし)

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();

const res = await client.messages.create({
  model: "claude-opus-4-6",
  max_tokens: 4096,
  messages: [{ role: "user", content: "ブログ記事を書いて" }],
});
console.log(res.content[0].text);

結果: 汎用的で中身のないテキスト が返ってくるだけ。トピックも構造も毎回バラバラ。

レベル2: ツールを与える (中レベルハーネス)

const tools = [
  {
    name: "read_existing_posts",
    description: "既存ブログ記事の一覧とタイトルを取得",
    input_schema: { type: "object", properties: {} },
  },
  {
    name: "write_post",
    description: "MDXファイルを書き出す",
    input_schema: {
      type: "object",
      properties: {
        slug: { type: "string" },
        frontmatter: { type: "object" },
        body: { type: "string" },
      },
      required: ["slug", "frontmatter", "body"],
    },
  },
];

async function runAgent(userGoal: string) {
  let messages = [{ role: "user", content: userGoal }];
  while (true) {
    const res = await client.messages.create({
      model: "claude-opus-4-6",
      max_tokens: 4096,
      tools,
      messages,
    });
    if (res.stop_reason === "end_turn") break;

    // ツール呼び出しをハーネスが実行
    const toolUse = res.content.find((c) => c.type === "tool_use");
    const result = await executeTool(toolUse.name, toolUse.input);
    messages.push({ role: "assistant", content: res.content });
    messages.push({
      role: "user",
      content: [{ type: "tool_result", tool_use_id: toolUse.id, content: result }],
    });
  }
}

結果: 既存記事と重複しないトピックで、正しい形式のMDXが生成される。ツールを与えただけで品質が激変します。

レベル3: Claude Code 並みの完全ハーネス

  • 自動ループ (ユーザー承認、エラー再試行)
  • 文脈圧縮 (長い会話を要約してトークン節約)
  • サブエージェント委譲 (翻訳は別コンテキストで)
  • プロンプトキャッシング (固定部分は再送しない)
  • Hooks (コミット前に自動 lint)

これらを自前で組むのは大変。だからこそ Claude Code を「実装の見本」として研究する価値があります。

Claude Code のハーネス構造を分解する

Claude Code は Anthropic 社内で最も磨かれたエージェントハーネス です。以下の5層で理解できます。

第1層: ツール設計

Read / Edit / Write / Bash / Glob / Grep / Agent などが用意されています。注目すべきは ツールの粒度

  • Grep は単なる grep ではなく ripgrep のラッパー。正確で高速
  • Edit は全文書き換えではなく 特定文字列の置換。差分が最小
  • Agent はサブエージェントを spawn し、コンテキストを隔離

ツール品質がエージェント品質に直結します。「動けば何でもいい」ではダメで、冪等性・明確なエラーメッセージ・最小責務 を意識して設計する必要があります。

第2層: 文脈の階層化

~/.claude/CLAUDE.md         ← グローバル規約
./CLAUDE.md                 ← プロジェクト規約 (自動ロード)
~/.claude/memory/           ← 長期記憶 (複数セッション横断)
  ├── user_profile.md
  ├── feedback_xxx.md
  └── project_xxx.md
会話履歴                     ← 直近のやり取り
タスク/プラン                ← 現セッションの進捗

寿命と役割が違うので、書き込み先を間違えると情報がすぐ失われたり、逆に古い情報が残り続けたりします。「このセッションだけ」はタスク、「何度も使う」はメモリ と使い分けましょう。

第3層: サブエージェント委譲

Agent ツールで別コンテキストのエージェントを spawn できます。

# メインは指示だけ、重労働は subagent に
Agent(
  subagent_type: "general-purpose",
  prompt: "blog/harness.mdx を英語+8言語に翻訳し、
           各 blog-{lang}/ に保存して完了報告"
)

こうすると メインの文脈が詳細ログで汚れない。長時間のビルドログ、翻訳の中間結果、検索結果など「成果物だけ欲しい作業」を丸投げできます。

第4層: Hooks (決定論的処理)

.claude/settings.json でツール呼び出しの前後にシェルコマンドを差し込めます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "npx tsc --noEmit" }
        ]
      }
    ]
  }
}

ファイル編集後に自動で型チェックが走ります。「毎回LLMに頼むより決定論的に処理すべきこと」 はフックで吸収するのが定石です。

第5層: 権限モード

{
  "permissions": {
    "allow": ["Read", "Grep", "Glob"],
    "deny": ["Bash(rm -rf*)", "Bash(git push --force*)"],
    "ask": ["Write", "Edit", "Bash"]
  }
}

破壊的コマンドを明示的に拒否し、書き込み系は承認を求める。事故は「勝手に実行された時」に起きる ので、ここの設計が運用の安全性を決めます。

落とし穴5選

1. ツールを増やしすぎる 30個のツールを与えると、モデルが選択に迷い精度が落ちます。5-15個に絞る のが経験則。足りない機能はサブエージェント側に持たせましょう。

2. プロンプトキャッシュを活かせていない Claude API の cache_control を使わないと、長い system prompt を毎回フルで送って課金が跳ねます。5分TTLを意識して、変わらない部分はキャッシュ に。

messages: [{
  role: "system",
  content: [
    { type: "text", text: longStaticInstructions,
      cache_control: { type: "ephemeral" } },   // ← これ
    { type: "text", text: dynamicContext },
  ],
}]

3. エラーメッセージが LLM に読めない形式 ツールが Error: undefined とだけ返すと、モデルは自己修復できません。「何が悪くて、どう直せば良いか」まで書く

throw new Error(
  `ファイル '${path}' が存在しません。` +
  `scripts/ ディレクトリのファイル一覧は: ${list.join(", ")}`
);

4. 人間の承認を省略する 破壊的操作 (削除、force push、DB更新) を自動承認すると、ある日必ず事故ります。「書き込み系は ask、削除系は deny」 をデフォルトに。

5. メモリを整理しない 時間経過で古くなった情報が残ると、エージェントが間違った前提で動きます。メモリも 定期的に断捨離 が必要です (Claude Code なら /compact や手動編集)。

自作ミニハーネスを動かしてみよう

最後に、ブラウザで動く最小ハーネスを置いておきます。Node.js + TypeScript。

// mini-harness.ts
import Anthropic from "@anthropic-ai/sdk";
import { readFileSync, writeFileSync } from "fs";

const client = new Anthropic();

const tools = [
  { name: "read_file",
    description: "テキストファイルを読む",
    input_schema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
  { name: "write_file",
    description: "テキストファイルを書き出す",
    input_schema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] } },
];

const executors = {
  read_file: ({ path }) => readFileSync(path, "utf-8"),
  write_file: ({ path, content }) => { writeFileSync(path, content); return `written ${path}`; },
};

async function loop(goal: string, maxSteps = 10) {
  const messages: any[] = [{ role: "user", content: goal }];
  for (let i = 0; i < maxSteps; i++) {
    const res = await client.messages.create({
      model: "claude-opus-4-6", max_tokens: 4096, tools, messages,
    });
    messages.push({ role: "assistant", content: res.content });
    if (res.stop_reason === "end_turn") return res.content;

    const toolUse = res.content.find((c: any) => c.type === "tool_use") as any;
    if (!toolUse) return res.content;
    const result = executors[toolUse.name](toolUse.input);
    messages.push({
      role: "user",
      content: [{ type: "tool_result", tool_use_id: toolUse.id, content: String(result) }],
    });
  }
}

await loop("README.md を読んで TL;DR.md に3行で要約して保存して");

これだけで 「既存ファイルを読み、新しいファイルを書き出す」 ミニエージェントが完成します。ここに Grep ツール、Bash ツール、Agent ツールを足していけば、Claude Code の縮小版が作れます。

まとめ: プロンプトを書く人から、ハーネスを設計する人へ

従来の視点これからの視点
良いプロンプトを書けば良い出力良いハーネスがあれば良い出力
モデルを選ぶモデル + ツール + 文脈 + 権限を設計
単発の質問継続的なループ運用

Claude Code は、この視点の転換を体感するのに最適な教材です。使うだけでなく、仕組みを分解して自分のエージェントに取り入れる。これが 2026 年以降のAIエンジニアに求められる姿勢です。

まずは上のミニハーネスをコピペして動かしてみてください。10分後には、あなた専用のエージェントの第一歩が踏み出せます。

関連記事

参考資料

#claude-code #agent-sdk #harness #prompt-engineering #llm #anthropic

Claude Codeをもっと活用しませんか?

実務で使えるプロンプトテンプレート50選。コピペですぐ使えます。

無料プレゼント

無料PDF: Claude Code 5分でわかるチートシート

メールアドレスを登録するだけで、A4 1枚のチートシートPDFを今すぐお送りします。

個人情報は厳重に管理し、スパムは送りません。

Masa

この記事を書いた人

Masa

現役DX室長|Claude Code でゼロから多言語AI技術メディア運営中。実務直結の自動化、AI開発相談・研修受付中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。