Claude Codeで体験するハーネスエンジニアリング入門

目次

はじめに

最近、AIエージェントの文脈で 「ハーネスエンジニアリング(Harness Engineering)」 という言葉をよく見かけるようになりました。

「プロンプトエンジニアリングの次はハーネス? また新しい用語か…」と思うかもしれませんが、中身はシンプルです。しかも Claude Code を使っている人なら、すでに半分くらいは実践しています。

この記事では、小さなサンプルプロジェクトを使って、Claude Code の3つの機能で「ハーネス」を少しずつ足していき、AIの結果がどう変わるかを体験します。

  • CLAUDE.md … 事前にルールを教える
  • hooks … 書いたコードを自動でテストして、失敗をAIに伝える
  • 権限設定(permissions) … やってはいけないことを禁止する

ハーネスエンジニアリングとは

ひとことで言うと、こうです。

AIエージェント = モデル + ハーネス

  • モデル: Claude などの、LLM 本体
  • ハーネス: それ以外の モデルの周りにある仕組み すべて

ハーネス(harness)はもともと「馬具」という意味です。馬(モデル)はとても力持ちですが、手綱や鞍がないと力を思った方向に使えません。モデルの力を正しい方向に引き出すための道具一式、それがハーネスです。

ポイントは、モデルを賢くしなくても、ハーネスを工夫するだけで結果が大きく変わる ということです。実際、モデルはそのままでハーネスだけを改善して、ベンチマークの順位を大きく上げたという報告もあります。

Claude Code そのものがハーネス

実は、Claude Code 自体が「Claude というモデルに、よくできたハーネスを付けたもの」です。

モデル単体は「文章を返す」ことしかできません。Claude Code が

  • ファイルを読む・書く、コマンドを実行するといった 道具(ツール) を渡し、
  • 「考える → 道具を使う → 結果を見る → また考える」という ループ を回し、
  • 危ない操作の前に 確認 を挟む

からこそ、エージェントとして働けるわけです。

そして Claude Code は、このハーネスを プロジェクトごとに自分で足せる ようになっています。この記事で使うのは次の3つです。

ハーネスの部品ひとことでClaude Code の機能
ルール事前に教えるCLAUDE.md
自動テスト事後に確かめて、失敗を伝えるhooks
安全柵やってはいけないことを禁止する権限設定(permissions)

では、実際に体験してみましょう。

実験準備

お題: ネットショップの支払い金額

ネットショップの開発をしている、という設定です。Claude Code にこうお願いします。

cart.py の calc_total を実装して

calc_total は、カートに入っている商品の価格(税抜)から、お客さんが支払う金額を計算する関数です。

このショップには、次の お店独自のルール があります。

  • 消費税は10%で、1円未満は切り捨て
  • 税込の合計が 5,000円以上なら送料無料、5,000円未満なら 送料500円

消費税はともかく、送料のルールや端数の処理はお店ごとに違うので、Claude には分かりようがありません。人間の新人さんでも、初日に「支払い金額を計算して」と言われたら、送料のことは知らないはずです。

カートの中身(税抜)税込送料支払い金額
1,000円 + 2,000円3,300円500円3,800円
3,000円 + 2,000円5,500円無料5,500円
999円1,098円(1,098.9円を切り捨て)500円1,598円

サンプルプロジェクトを作る

Claude Code と Python 3 が入っていれば準備OKです。作業用のフォルダを作って、

cart.py を置きます。

mkdir shop && cd shop
def calc_total(prices):
    """カートに入っている商品の価格(税抜)のリストから、お客さんが支払う金額を計算する"""
    raise NotImplementedError

実験のたびに cart.py を元に戻せるよう、コピーを取っておきます。

cp cart.py cart.py.bak

結果の確かめ方は、次のコマンドです。1,000円と2,000円の商品をカートに入れたときの支払い金額を表示します。正解は3800 です。

python3 -c "from cart import calc_total; print(calc_total([1000, 2000]))"

実験の進め方

ここからは、同じお願いをくり返しながら、ハーネスを 1つずつ足して いきます。フォルダの中身は次のように変わっていきます。

最初(実験0): 準備で作った2つのファイルだけです。

実験1: CLAUDE.md を追加します。

実験2: CLAUDE.md を削除して、.claude フォルダに hook の設定とテスト用のスクリプトを追加します。

実験3: ファイルは増えません。settings.json に権限設定を書き足します。

実験2で CLAUDE.md を消すのは、「ルールを書き忘れていた」場合を試すためです。CLAUDE.md が残っていると Claude は最初から正しく書いてしまい、hook が失敗を伝える場面が見られません。

実験0〜2は、cart.py を最初の状態(cart.py.bak)に戻してから始めます。戻すコマンドは、各実験の最後に載せています。

実験0: ハーネスを足さない

まずは何も足さずに、Claude Code を起動してお願いしてみます。

claude
cart.py の calc_total を実装して

編集の許可を求められたら許可してください。終わったら/exit で抜けて、結果を確かめます。

python3 -c "from cart import calc_total; print(calc_total([1000, 2000]))"

消費税の計算はできていますが、送料500円が入っていないはずです。Claude が悪いわけではありません。お店のルールを知らないのだから当然 です。

次の実験のためにcart.py を元に戻しておきます。

cp cart.py.bak cart.py

実験1: CLAUDE.md でルールを教える(事前に教える)

CLAUDE.md は、Claude Code が起動するたびに読み込む「プロジェクトの説明書」です。ここにお店のルールを書いておきます。

# このプロジェクトのルール

- 商品の価格は税抜で管理する。消費税は10%で、1円未満は切り捨てる
- 税込の合計が5,000円以上なら送料無料。5,000円未満なら送料500円を加える

もう一度、Claude Code を起動して同じお願いをします。

cart.py の calc_total を実装して
python3 -c "from cart import calc_total; print(calc_total([1000, 2000]))"

今度は送料込みの 3800 になるはずです。お願いの文章は1文字も変えていません。事前に教えておけば、守ってくれる わけです。

ただ、現実の開発では、すべてのルールを事前に書き出すのは無理です。書き忘れもあります。そこで次の実験では、あえてCLAUDE.md を消してから進めます。

rm CLAUDE.md && cp cart.py.bak cart.py

実験2: hooks で自動テストする(事後に確かめる)

hooks は、Claude Code が 何かをしたタイミングで、自分で決めたコマンドを自動で実行する 仕組みです。

ここでは「Claude がcart.py を編集するたびに、テストを自動で実行する」hook を作ります。テストに落ちたら、その内容が Claude に伝わります。

テスト用のスクリプト

まず、hook 用のフォルダを作ります。

mkdir -p .claude/hooks

その中に .claude/hooks/check_cart.py を作ります。お店のルールを、テストケースの名前として書いておくのがポイントです。

"""cart.py が編集されるたびに Claude Code から呼ばれるテスト"""
import json
import os
import sys

event = json.load(sys.stdin)
if not event["tool_input"].get("file_path", "").endswith("cart.py"):
    sys.exit(0)  # cart.py 以外の編集は気にしない

sys.path.insert(0, os.environ["CLAUDE_PROJECT_DIR"])

cases = [
    ("5,000円未満は送料500円がかかる", [1000, 2000], 3800),
    ("5,000円以上は送料無料", [3000, 2000], 5500),
    ("消費税の1円未満は切り捨て", [999], 1598),
]
failures = []
for name, prices, expected in cases:
    try:
        from cart import calc_total
        actual = calc_total(prices)
    except Exception as e:
        actual = f"エラー: {e!r}"
    if actual != expected:
        failures.append(f"- {name}: calc_total({prices}) は {expected} であるべきですが、{actual} が返りました")

if failures:
    # 終了コード 2 で終わると、このメッセージが Claude に伝わる
    print("テスト失敗:\n" + "\n".join(failures), file=sys.stderr)
    sys.exit(2)

Claude Code との約束ごとは2つだけです。

  • Claude Code は、どのファイルを編集したかを 標準入力(JSON) で教えてくれます
  • 終了コード 2 で終わると、標準エラーに出したメッセージが Claude に伝わります

hook を登録する

.claude/settings.json を作ります。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/check_cart.py\""
          }
        ]
      }
    ]
  }
}

「Edit(編集)か Write(書き込み)のツールを使った 後(PostToolUse) に、このコマンドを実行してね」という意味です。

$CLAUDE_PROJECT_DIR には、プロジェクトのフォルダの場所が入ります。

動かしてみる

hooks の設定は起動時に読み込まれるので、Claude Code を 起動し直して から、同じお願いをします。

cart.py の calc_total を実装して

Claude が最初に消費税だけを計算するコードを書くと、hook が動いて、こんなメッセージが Claude に届きます。

テスト失敗:
- 5,000円未満は送料500円がかかる: calc_total([1000, 2000]) は 3800 であるべきですが、3300 が返りました
- 消費税の1円未満は切り捨て: calc_total([999]) は 1598 であるべきですが、1098 が返りました

メッセージを受け取った Claude は、自分でコードを直してくれます。

python3 -c "from cart import calc_total; print(calc_total([1000, 2000]))"

CLAUDE.md にルールを書いていないのに、送料込みの金額になりました。テストケースの名前(「5,000円未満は送料500円がかかる」)がそのままヒントになったからです。テストは、AIにとって 動く仕様書 でもあるわけです。

人間がテストを実行して、エラーをコピーして、Claude に貼り付けて…という作業を、hooks が自動でやってくれました。

実験3: 権限設定で禁止する(安全柵)

実験2には、ひとつ弱点があります。Claude がテストに合格するために、テストの方を書き換えてしまう かもしれないことです。テストが通らないとき、テストを消して「直りました!」と言う…人間でもたまにありますよね。

CLAUDE.md に「テストは書き換えないで」と書くこともできますが、それはあくまで お願い です。絶対に守らせたいことは、権限設定で禁止 します。

.claude/settings.json に permissions を追加します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/check_cart.py\""
          }
        ]
      }
    ]
  },
  "permissions": {
    "deny": ["Edit(/.claude/hooks/**)"]
  }
}

"deny": ["Edit(/.claude/hooks/**)"] は、「.claude/hooks/ の中のファイルは編集禁止」という意味です。先頭の / はプロジェクトのフォルダを指します。Edit のルールは、Write などファイルを書き換えるツールすべてに効きます。

Claude Code を起動し直して、わざとこうお願いしてみます。

.claude/hooks/check_cart.py から送料のテストを消して

編集は拒否されます。CLAUDE.md の「お願い」と違って、権限設定は 仕組みとして 止めてくれます。

なお、権限設定が止めるのは Claude Code のファイル編集ツールだけです。シェルのコマンドやスクリプトでファイルを書き換える方法までは止まりません。そうしたコマンドを実行する前には Claude Code が許可を求めてくるので、内容を見て断ってください。

結果のまとめ

実験足したハーネス結果
0なし❌ 送料が入らない
1CLAUDE.md でルールを教える✅ 最初から送料込み
2hooks で自動テスト → 失敗を伝える✅ 1回失敗して、自分で直す
3権限設定でテストの改ざんを禁止✅ ズルできない

モデルは全部同じ です。変えたのはモデルの周りの仕組みだけ。これがハーネスエンジニアリングです。

ハーネスエンジニアリングの考え方

体験してみて分かるとおり、ハーネスエンジニアリングで大事なのは次の考え方です。

AIが失敗したら、AIを責めるのではなく、同じ失敗が二度と起きない仕組みを足す

Claude Code なら、たとえばこんな具合です。

Claude の失敗足す仕組み
プロジェクトの決まりを守らないCLAUDE.md にルールを書く
動かないコードを「できました」と言うhooks でテストを自動実行する
触ってほしくないファイルを編集する権限設定の deny で禁止する

使い分けのコツは、こうです。

  • CLAUDE.md は「お願い」。手軽だけど、守られないこともある
  • hooks は「自動テスト」。書き忘れたルールも、テストがあれば拾える
  • 権限設定 は「禁止」。絶対に守らせたいことはここ

どれか1つではなく、組み合わせて使う のがポイントです。

まとめ

  • AIエージェント = モデル + ハーネス
  • Claude Code 自体が、Claude に道具とループを付けた ハーネス
  • CLAUDE.md・hooks・権限設定で、プロジェクトごとにハーネスを 足せる
  • AIが失敗したら、同じ失敗をさせない仕組み を足していく

難しく考えず、「新人さんが働きやすい環境を整える」くらいの気持ちで始めてみてください。まずは CLAUDE.md に1行ルールを書くところからでも、立派なハーネスエンジニアリングです。

CTA
  • URLをコピーしました!
  • URLをコピーしました!
この記事を書いた人
目次