ウェブの防御はシンプルな区分を前提に築かれてきた。人間は一方、自動化はもう一方という区分だ。AIエージェントはそのどちらでもない。人間の依頼に応じて動作するが、サーバー上で実行され、機械的な速度で読み取り、しばしばクラウドのIPから接続してくる。ウェブを利用するAIエージェントを構築するチームにとって、その結果として生じるのは誰も想定していなかった失敗だ。エージェントは壁に突き当たり、その後どうなるかはほとんどのツール層が無視している細部次第で変わる。
このガイドでは、なぜエージェントがカテゴリーの間に落ち込むのか、ツール呼び出しの内側から見るとその壁がどう見えるのか、そしてそれを正直に処理するエージェントをどう作るかを扱う。
クローラーでも人間でもない
ベンダー各社は今やこうしたエージェントを独立したクラスとして説明している。OpenAIはChatGPT-Userについて「自動的な方法でウェブをクロールするために使われるものではない」と述べている。Googleは、Google-Agentは「ユーザーの要求に応じてウェブをナビゲートし操作を実行する」ために使われると述べている。Anthropicには、人々がClaudeに質問する際に使われるClaude-Userというエージェントがある。
ルールはこれに追いついておらず、ベンダー間でも見解は一致していない。
| ベンダー | ユーザー起点のフェッチとrobots.txt |
|---|---|
| OpenAI | 「robots.txtのルールは適用されない場合がある」 |
| 「概してrobots.txtのルールを無視する」 | |
| Anthropic | そのボットはrobots.txtの指示に従う |
RFC 9309は、robots.txtのルールは「アクセス許可の一形態ではない」と付け加えており、もとよりこれだけで決着がつくものではなかった。一方でトラフィックは増加を続けている。CloudflareはAIの「ユーザー操作」によるクロールが2025年に15倍以上に増加したと報告しており、Thalesの2026年版Bad Bot Reportは2025年の全トラフィックに占めるボットの割合を53%超としている。サイト側は自動化をフィルタリングする理由をこれまで以上に持っており、エージェントはそのフィルターに引っかかっている。
ツール呼び出しから見た壁の姿
| 返ってくるもの | 意味すること | エージェントが取るべき行動 |
|---|---|---|
| チャレンジページ付きの200 | ボットチェックであり、ページではない | ページを見られなかったと報告する |
| 401またはログインページ | コンテンツにアカウントが必要 | ユーザーにサインインを依頼する |
| 402 | サイトが支払いを求めている | 支払いをする前にユーザーに確認する |
| 403 | サーバーが拒否している | それを報告し、ページを叩き続けない |
| 429 | リクエストが多すぎる | Retry-Afterに従うか、停止する |
| 404または410 | ページが存在しない | 再試行しない |
危険なのは最初の行だ。403は少なくとも失敗のように見える。チャレンジページはしばしば整形されたHTML本文を伴う200として届くため、単純なフェッチツールはそれをまるで記事であるかのようにモデルへ渡してしまう。
これが問題になるのは、モデルが不良なツール出力に対してどう振る舞うかによる。2026年9月のツール拡張エージェントに関するプレプリントは、ツールがstatus:errorを返した場合、不誠実さは皆無(0.0%)だったが、status:okとともに編集済み、破損、古い、不正な形式、空、または切り詰められた値が返された場合には45.3%に達したことを発見した。この研究はウェブページではなく内部システムを検証したものなので、ブロックページに当てはめるのはこちらの推測である。それでも教訓は通用する。正直なエラーは、何の役にも立たない成功よりはるかに安全だ。チャレンジページがAIツールの出力を損なう仕組みについても取り上げている。
真実を語るフェッチツール
この修正はツール層に属する。以下の関数は、モデルに渡す前に各レスポンスを分類し、ページが本物である場合にのみページのテキストを返す。必要なのはrequestsだけだ。各ケースを返すローカルのテストサーバーに対して、Python 3.9で実行した。
from __future__ import annotations
from dataclasses import asdict, dataclass
import requests
# Text that marks a page as a wall, not content. Extend it from your own logs.
WALL_MARKERS = ("verify you are human", "unusual traffic", "enable javascript and cookies",
"attention required", "access denied", "captcha")
LOGIN_MARKERS = ("sign in to continue", "log in to continue")
@dataclass
class FetchResult:
outcome: str # ok, wall, login, payment, rate_limited, not_found, error
status: int | None
text: str # only ever page content, empty unless outcome is ok
retry_after: str | None
note: str # what the model should be told
NOTES = {
"wall": "The site showed a bot check. You did not see this page. Say so, do not guess its content.",
"login": "This page needs a login. Ask the user to sign in or to provide the content.",
"payment": "The site asks for payment (HTTP 402). Ask the user before paying anything.",
"rate_limited": "The site asked you to slow down. Wait, or tell the user it is unavailable now.",
"not_found": "The page does not exist. Do not retry it.",
"error": "The fetch failed. You did not see this page. Say so, do not guess its content.",
"ok": "",
}
def classify(status, body):
low = body.lower()
if status == 402:
return "payment"
if status == 429:
return "rate_limited"
if status in (404, 410):
return "not_found"
if any(m in low for m in WALL_MARKERS): # walls often arrive with a 200
return "wall"
if status in (401, 403):
return "login" if status == 401 or any(m in low for m in LOGIN_MARKERS) else "wall"
if status == 200 and any(m in low for m in LOGIN_MARKERS):
return "login"
return "ok" if status == 200 else "error"
def agent_fetch(url, agent_name="example-agent/1.0 (+https://example.com/agent)"):
"""A fetch tool that never hands the model a block page as if it were content."""
try:
r = requests.get(url, headers={"User-Agent": agent_name}, timeout=(10, 30))
except requests.RequestException as exc:
return FetchResult("error", None, "", None, f"{NOTES['error']} ({type(exc).__name__})")
outcome = classify(r.status_code, r.text)
return FetchResult(outcome, r.status_code, r.text if outcome == "ok" else "",
r.headers.get("Retry-After"), NOTES[outcome])
出力は、ステータス、分類結果、モデルがページのテキストを受け取るかどうか、Retry-Afterの順で示される。
/article 200 ok content
/challenge 200 wall -
/blocked 403 wall -
/members 200 login -
/priced 402 payment -
/busy 429 rate_limited - 120
/gone 404 not_found -
/broken 503 error -
(no server)None error -
9つのケースのうち、モデルにページテキストを渡すのは1つだけだ。/challengeの行は、200を返すために単純なフェッチが誤って処理してしまうケースである。マーカーのリストは出発点として扱い、自分たちのログから育てていくこと。どんなリストもすべての壁を捉えきることはできないからだ。ツールをMCPサーバー経由で提供している場合、仕様にはこのための場所がある。実行エラーはisError: trueで報告され、クライアントは「言語モデルにツール実行エラーを提供すべき(SHOULD)」とされている。
CAPTCHAを解こうとしない
この点ではベンダー各社の足並みが珍しく揃っている。OpenAIは、Operatorはログイン、支払い情報、CAPTCHAについてユーザーに引き継ぎを依頼するよう訓練されていると説明した。AnthropicのChrome版Claudeのドキュメントは、ログインページやCAPTCHAに遭遇すると「一時停止し、手動での対応を依頼する」と述べている。Googleが推奨するコンピューター操作の指示には、「CAPTCHA(いかなる種類であれ)」を含むリストについて「解こうとしたり回避しようとしたりすることは絶対にあってはならない」とある。
CAPTCHAはサイトが人間を求めているサインであり、正しい対応は人間に引き継ぐことだ。それはより良い製品にもつながる。何が起きたかを人間が把握し、自分で判断できるからだ。
身元こそが通り道
より長期的な答えは、エージェントがサイト側で検証可能な方法で自分が何者であるかを示すことだ。Web Bot Authでは、クライアントがHTTP Message Signaturesを使ってリクエストに暗号署名できる。OpenAIはすでにクラウドブラウザからのリクエストに署名しており、Signature-Agentヘッダーをhttps://chatgpt.comに設定している。Googleもこのプロトコルを実験していると述べている。Cloudflareは2025年8月に署名済みエージェントのカテゴリーを導入し、2026年7月以降は検証済みボットとの違いを直接アクセスと仲介アクセスとして区別して追跡している。IETFの作業はまだドラフト段階であり、変更が見込まれる。
今日エージェントを構築しているなら、連絡先情報を含む記述的なUser-Agentを送信し、エージェントが何を行うかを公開し、署名への対応を計画しておくこと。
法的な状況は未確定
最初の大きな訴訟は、この問題がどれほど開かれているかを示している。Amazonは2026年11月、エージェント型ブラウザをめぐってPerplexityを提訴し、米国のComputer Fraud and Abuse Actおよびそのカリフォルニア州版違反を主張した。意見書によれば、争いの核心はPerplexityがユーザーエージェント文字列を使用しないという判断にあった。2026年8月、第9巡回区控訴裁判所は予備的差止命令を取り消し、AmazonがPerplexityがその法律のもとで自社のコンピューターに「アクセスした」ことを示せる可能性は低いと判断した。
裁判所はこの判断がどこまで及ぶかについて慎重だった。「我々はエージェント型AIを統治する新たな法体系を確立するものではない」。また、この結論はAmazonが利用規約を通じてアクセスを規制する能力を損なうものではないとも述べた。つまりこれは、エージェントがサイトの意向を無視してよいという許可証ではなかった。これは一般的な情報であって法的助言ではなく、この訴訟は地方裁判所に差し戻されている。
壁を前提に構築する
- 壁は通常のことだと想定する。 エージェントの計画は、到達できないページを前提に設計し、別のソースやユーザーへの確認といったフォールバックを用意する。
- モデルが何かを目にする前に分類する。 上記のようなツールを使い、どのサイトが失敗するかを把握できるように結果をログに残す。
- まず公式ルートを提示する。 サイトにAPIやデータ製品がある場合はそれを使う。ブラウザセッションより信頼性が高い。
- セッションの一貫性を保つ。 途中でIPが変わると複数ステップのタスクは破綻する。ウェブを閲覧するAIエージェント向けプロキシについてのガイドでローテーションとスティッキーセッションを扱っている。
- プロキシの用途を理解する。 レジデンシャルプロキシは、エージェントが公開ページを、正しい国から、一貫したアドレスで、地元の訪問者と同じように見るのを助ける。それはサイトが許可する範囲を変えるものではなく、拒否したサイトを突破する手段でもない。サイトがログイン、支払い、あるいは人間を求めているなら、それを尊重すること。
結論
エージェントは、ウェブにそのためのカテゴリーが用意される前にウェブへ到達してしまい、今のところ壁はその仕事の一部である。それを通常の結果として扱うこと。ツール層で分類し、モデルとユーザーに真実を伝え、CAPTCHAは人間に差し戻し、エージェントの身元を示すこと。「そのページを見ることができませんでした」と言えるエージェントは、推測するエージェントよりもはるかに価値がある。
出典と参考文献
- OpenAI, bots documentation: developers.openai.com
- OpenAI, ChatGPT Work’s cloud browser allowlisting: help.openai.com
- OpenAI, Introducing Operator (23 January 2025): openai.com
- Google, user-triggered fetchers: developers.google.com
- Google, Gemini computer use: ai.google.dev
- Anthropic, on crawling and site owners: support.claude.com
- Anthropic, Claude in Chrome: code.claude.com
- Cloudflare, signed agents (28 August 2025, docs updated November 2026): blog.cloudflare.com, developers.cloudflare.com
- Cloudflare Radar 2025 Year in Review: blog.cloudflare.com
- Thales, Bad Bot Report 2026: cpl.thalesgroup.com
- IETF, Web Bot Auth protocol draft: datatracker.ietf.org
- RFC 9309, Robots Exclusion Protocol: rfc-editor.org
- RFC 9110 and RFC 6585, HTTP status codes: rfc-editor.org, rfc-editor.org
- Model Context Protocol, tools specification: modelcontextprotocol.io
- Sethi et al., Fabrication After Tool Failure (preprint, September 2026): arxiv.org/abs/2609.14758
- Ninth Circuit, Amazon.com Services v. Perplexity AI, No. 26-1444 (4 August 2026): uscourts.gov