AI エージェントの指示やハーネスを変えてスコアを比べても、評価環境に穴があると結論を誤る。ISUCON2026 を無人で解いた準備記録から、比べる前に確かめる 6 点と手順を解説する。

本記事はプロモーションを含みます。
AI エージェントに渡す指示や skill、hook を変えて、同じ課題を解かせてスコアを比べる。よくなったら採用し、悪くなったら戻す。この比べ方は、評価する環境が正しいことを前提にしている。
前提が崩れていると、エージェントは筋の通った行動をしているのに、比べた結論が誤る。結論を先に書くと、比べる前に次の 6 点を確かめる。
| 確かめること | 怠ると起きること |
|---|---|
| 実行ごとにまっさらな状態で起動しているか | 手元の設定が混ざり、構成どうしを比べられない |
| 同じ構成を測り直したときの幅 | 偶然の上振れを改善と数える |
| 評価環境の上限と、本番との違い | 環境の不備をアプリの限界と読み、逆向きに直す |
| 追試が、初期化の前のデータを見ているか | 再起動でデータが消える構成を最高点として残す |
| watchdog が「止まった」と「入力待ち」を分けているか | 止まった run を見逃す、空のターンに費用を払う |
| 作り込んだ構成を、素の構成と比べたか | 足した手順が足を引っ張っていても気付かない |
ISUCON2026 で優勝しました(準備編)
Zenn

この記事では、元記事で起きた事故を、自分のエージェントや prompt を比べるときの点検項目に置き換える。ISUCON に限らず、エージェントに課題を繰り返し解かせて構成を選ぶ場面なら同じことが起きる。
普段使っている Claude Code には、user skill、plugin、MCP サーバー、~/.claude/CLAUDE.md、auto memory が入っている。これが評価の run に混ざると、run ごとに条件が変わり、構成どうしを比べられない。元記事の筆者は、切り離す手段を 1 つずつ試して確かめている。
| 混ざるもの | 元記事で採った手段 | 確かめた結果 |
|---|---|---|
| user skill・plugin・auto memory | CLAUDE_CONFIG_DIR を評価専用のディレクトリに向ける | 消えた。そのディレクトリで事前に 1 回ログインしないと起動できない |
| MCP のツール | --strict-mcp-config | 消えた |
~/.claude/CLAUDE.md | --setting-sources project,local | 残った。settings.json にしか効かず、ファイル自体を空にした |
親ディレクトリの CLAUDE.md | 作業ディレクトリを開発用の repo の外に、run ごとに作って捨てる | 読まれなくなる |
最後の行は、Claude Code が作業ディレクトリと、その上にあるすべてのディレクトリの CLAUDE.md を読むためだ (公式ドキュメント)。評価用のディレクトリを開発用の repo の中に作ると、開発用の指示が評価の run にも載る。
元記事の手段を起動の形にすると、こうなる。権限は --dangerously-skip-permissions を使わず、作業ディレクトリに置く .claude/settings.json に allow と deny を書く。
mkdir -p "$HOME/.agent-eval"
run_dir=$(mktemp -d "$HOME/.agent-eval/run.XXXXXX")
cp -r harness/. "$run_dir/"
cd "$run_dir"
CLAUDE_CONFIG_DIR="$HOME/.agent-eval/config" \
claude -p "$(cat prompt.md)" --strict-mcp-config --setting-sources project,local
この形は元記事の記述と claude --help から組んだもので、筆者の手元では動かしていない。元記事の筆者は、run の前に同じ起動方法で読み込まれたファイルの一覧を出させ、想定どおりかを確かめる検査も入れている。手段を足した後は、効いたかを毎回確かめる。
--bare も同じ目的に使える。CLI リファレンスでは、hook、skill、plugin、MCP サーバー、auto memory、CLAUDE.md の自動での読み込みを飛ばすと書かれている (CLI reference)。ただし claude --help によると、認証は ANTHROPIC_API_KEY か --settings で渡す apiKeyHelper だけになる。Stop hook のように hook で動きを足すハーネスを比べるなら、何が読まれ何が読まれないかを先に確かめてから選ぶ。
元記事では、同じ commit で 58 回回したベンチマークのスコアに 21% の幅があった。実装を変えなくても最高点が更新されるので、終盤の最高点の更新だけでは改善を確かめられない。
エージェント自体の選ぶ手もばらつく。同じ bare の構成で同じ問題を 3 回解かせた結果が、212,472,000 / 240,412,000 / 19,409,000 点だった。低かった回では、エージェントが自分で入れた手を、規則に反するかもしれないという懸念から 1 回も測らずに外していた。
構成を比べる前に、変えていない構成を何回か測って幅を出しておく。その幅に収まる差は、改善とも悪化とも数えない。
#!/usr/bin/env bash
set -euo pipefail
# 使い方: ./measure.sh <回数> <スコアを 1 行で出すコマンド...>
n=$1
shift
for _ in $(seq "$n"); do
"$@"
done | sort -n | awk '
{ s[NR] = $1 }
END {
min = s[1]; max = s[NR]; med = s[int((NR + 1) / 2)]
printf "n=%d min=%d median=%d max=%d range=%.1f%%\n", NR, min, med, max, (max - min) / med * 100
}'
乱数でスコアを返すコマンドで動作を確かめた結果がこれだ。
$ ./measure.sh 10 sh -c 'echo $((1000 + RANDOM % 200))'
n=10 min=1088 median=1123 max=1198 range=9.8%
元記事の筆者も、1 回の結果で結論を広げすぎないようにしたと書いている。effort を xhigh と high で 1 本ずつ比べた run でも、どちらも 1 回なので effort の影響かは分からないとしている。
元記事で、評価環境の不備がエージェントの判断を誤らせた例が 2 つある。
LimitNOFILE が無く、ベンチマーカーの fd の上限が既定の 1024 のままだった。負荷走行の一部が失格になり、エージェントはそれをアプリの性能の上限と解釈して、アプリの処理を絞ったどちらも、エージェントは与えられた環境で筋の通った行動をしている。直すのは環境の側だ。
fd の上限の既定は、systemd の DefaultLimitNOFILE= が 1024:524288 (soft が 1024) と定めている (systemd-system.conf)。筆者の手元 (Manjaro) でもこの値だった。
$ systemctl show -p DefaultLimitNOFILE,DefaultLimitNOFILESoft
DefaultLimitNOFILE=524288
DefaultLimitNOFILESoft=1024
評価に使うサービスごとに、効いている値を見る。<unit> はベンチマーカーやアプリの unit 名に置き換える。
systemctl show -p LimitNOFILE,LimitNOFILESoft <unit>
grep 'open files' /proc/"$(systemctl show -p MainPID --value <unit>)"/limits
足りなければ systemctl edit <unit> で drop-in に書き、サービスを再起動する (systemd.exec)。
[Service]
LimitNOFILE=65536
上限のほかにも、元記事では評価環境の側で次のものが結果を動かしていた。
unattended-upgrades) が負荷走行と重なって動いていた。イメージを作り直して止めた確かめる順は、エージェントの構成をいじる前だ。元記事の筆者も、評価環境の正しさと本番との違いは、もっと序盤に確認しておくべきだったと振り返っている。
ISUCON には、競技の後にサーバーを再起動し、アプリが動き続けるかを確かめる追試がある。元記事の評価環境もこれを再現していたが、負荷走行の最初に初期化を呼ぶ作りで、再起動前のデータを 1 つも見ていなかった。
その結果、データをメモリだけに持ち、再起動で消える構成が 199,818,000 点の記録として残った。記録を洗い直すと、本来なら失格になる run がほかにも見つかっている。
直し方は順序だ。
元記事では、再起動の順序を毎回ランダムにした理由を、規則の「主催者がサーバーを再起動する順序は保証しない」という文面に合わせたためとしている。ISUCON 以外でも、「確かめる手順の前に、確かめたい状態を消す処理が走っていないか」は同じ形で起きる。テストが通るのに壊れている例は「テストもCIも緑なのに壊れた実装を通す穴」にまとめた。
無人で長く走らせるなら、止まったら止める watchdog が要る。元記事では、この watchdog が両方向に誤った。
/exit を送り、run が 1 時間 9 分で終わった。別の構成では、入力待ちを停止と誤認した watchdog が約 3 時間で continue を約 370 回送り、トークンを API の単価で換算した額の 66% がこの空のターンの分だったwatchdog が見るのは、ファイルの更新のような間接の信号でなく、エージェントのターンの状態にする。元記事の本番の構成では、watchdog は API エラーで終わったターンにだけ continue を送り、競技終了の 10 分前に /exit を送る形になっている。
評価ランナーを動かす手元の Mac でも、元記事では run が止まっている。バッテリーで動いていた Mac がスリープして約 4 時間、まとめて止まった。Claude のデスクトップアプリを終了したら、スリープを止めていたのがそのアプリだったと分かった。以後は評価ランナーが caffeinate でスリープを止めている。
無人で回すスクリプトの落とし穴は「自動実行のbashが突然落ちる・失敗を見逃す4つの罠」と「AIエージェントが叩くとシェルが止まる・壊れる4つの原因」にも書いた。
元記事の full は、作業を 4 つのフェーズに分け、仮説と検証を記録し、1 手ごとに commit して測るよう指示していた。同じ ISUCON13 で比べると、最高点はこう並んだ。
| 最高スコア | 1h | 4h | 8h |
|---|---|---|---|
| bare | 697,671 | 867,611 | 873,282 |
| full (並列) | 3,718 | 453,380 | 688,906 |
差の理由として、元記事は 3 つを挙げている。
元記事の筆者は full の開発を止め、bare を土台に、評価で失敗した場面ごとに手当てを 1 つずつ足した。API エラー 1 回で run が終わる場面には continue を送る watchdog、持ち時間を残して手を止める場面には続けさせる Stop hook、という形だ。
普段の開発で同じ結果になるかは分からない、と元記事の筆者は書いている。それでも、手順や道具を足す前に、足さない構成と同じ環境で比べる価値はある。比べなければ、足したものが効いているのか足を引っ張っているのかが分からない。
ISUCON の課題そのもの、つまり計測して律速を見つけて直す流れを人の手で学ぶなら、この本が定番だ。エージェントが出した手を読んで良し悪しを判断するにも、この知識が要る。
CLAUDE.md から切り離して起動し、切り離せたかを毎回確かめる