Skip to content
Aandeg
Aandeg8 min read

Same prompt, different answer: how each harness defines web search and fetch

An agent's web search and web fetch tools are defined by its harness, not the model. Three harnesses, three very different implementations, and why the same prompt can land on your site differently depending on who is asking.

On this page

Ask two AI agents the same question and you can get two different answers.

Not because one model is smarter - well, maybe - but because the two agents reached the web through different plumbing.

When an agent "searches the web" or "reads a page," it is calling a tool. That tool is not part of the model - it is defined by the harness: the orchestration layer that runs the agent's loop, decides which tools to use, and executes them when the model asks. If you have read the AI agent onion, this is the layer we called the tool implementation, the code that actually opens a socket to reach the web page.

Here is the part that surprises people. Every harness defines its own set of tools. Two different harnesses can hand the same model a tool called web_search but each implementation of web_search can differ:

  • different search providers,
  • different result shapes,
  • different set of limits,
  • different requests arriving at your website.

Same prompt, same model, different plumbing, different answer.

This post pulls that plumbing apart. We will define what these tools actually are, then do a deep dive into how three harnesses implement them: the Anthropic Claude API (which powers Claude Code), OpenAI's Responses API, and OpenClaw. By the end, "the agent searched the web" will stop sounding like one thing.

Two tools, one job each#

Almost every agent harness ships two tools that help it interact with web pages.

Web search turns a query into a list of candidate results. The model emits a query string, the harness runs a search, and what comes back is a ranked list of hits: titles, URLs, and usually a short snippet of each page. Crucially, search does not return the full page. It returns pointers, so the model can decide what looks worth reading.

Web fetch turns one known URL into readable text. The model emits a URL, the harness retrieves that page, strips it down to its readable content, and hands the text back. If you paste a link, the model tends to skip search and fetch it directly. If it needs to discover pages first, it searches, reads the snippets, then fetches the promising ones.

That split (find candidates, then read one) is the shape almost every harness follows. The model never touches the network in either case. It only ever emits a small structured request and waits for the harness to do the real work. A web search call is nothing more than a tool name and its arguments:

{
  "name": "web_search",
  "input": { "query": "current AAPL stock price" }
}

Everything else (running the query, opening the socket, parsing the response) happens on the harness side of that handoff, out of the model's sight.

The deep dive: three harnesses#

Below we take a look at how different harnesses provide the tools we've discussed above.

Anthropic's Claude API: the tools run on Anthropic's servers#

The Claude API exposes web_search and web_fetch as server tools.1 When enabled, Anthropic runs the tool for you during the request. You do not receive a tool call, execute it yourself, and send back a result. The API runs the search on its own infrastructure, drops the results into the conversation, and Claude keeps going.2

For web_search, the loop looks like this: Claude decides to search, the API runs the query, and Claude gets back a list of results. Each result carries a title, a url, a page_age, and an encrypted_content blob:1

{
  "type": "web_search_result",
  "url": "https://en.wikipedia.org/wiki/Claude_Shannon",
  "title": "Claude Shannon - Wikipedia",
  "encrypted_content": "EqgfCioIARgBIiQ3YTAwMjY1Mi1mZjM5...",
  "page_age": "April 30, 2025"
}

That encrypted blob is a detail worth pausing on: the raw search content is opaque to the caller, and it must be passed back verbatim on later turns so Anthropic can decrypt it and restore Claude's context. Citations are not optional here. Every web search response comes with cited sources built in, and the citation text does not even count against your token usage.1

web_fetch is where this harness meets your website directly. It does a plain retrieval of a URL and returns the full text of the page as a document block, or base64 for a PDF.2 Two things about that fetch are worth internalizing if you run a site. First, it explicitly does not execute JavaScript.2 The docs say so in as many words and point you at a separate browser-use tool for pages that need a real browser. A client-rendered page that paints its content with JavaScript returns almost nothing to this fetch. Second, there is a security rule that shapes agent behavior in a way you would not guess: Claude can only fetch a URL that has already appeared in the conversation, whether in a user message, a prior tool result, or a previous search.2 It cannot fetch a URL it invented in its own output. This is an anti-exfiltration measure, and it is why search and fetch are so often paired: search surfaces the URL into context, which then makes that URL fetchable.

The harness also gives the developer knobs the model never sees: max_uses caps how many searches or fetches a request may run, allowed_domains and blocked_domains restrict where it can go, and max_content_tokens truncates a large page before it floods the context window.12 And there is a real price attached: web search is billed at ten dollars per thousand searches on top of token costs.1 The harness, not the model, decides all of this.

OpenAI's Responses API: hosted search, and no fetch tool in sight#

OpenAI's Responses API also offers a hosted web_search tool, and at a glance it looks similar: you enable the tool, the model decides when to search, OpenAI runs the search server-side and returns results.3

Open AI takes a different approach to web fetch however.

There is no separate web-fetch tool exposed the way Anthropic exposes one.3 You cannot hand the model a bare "read this exact URL" primitive as a first-class, documented tool alongside search. Instead, OpenAI's reasoning models perform page reading inside the search action, through internal open_page and find_in_page steps that are part of the model's own search chain-of-thought rather than tools you wire up.3 So on this harness, "read that page" is not a separate lever you pull. It is folded into search and handled for you, out of sight.

The result surface has its own shape too. OpenAI distinguishes inline citations (the most relevant sources, annotated into the response text) from a sources field (the complete list of every URL the model consulted, which you request explicitly).3 Domain filtering exists but lives only in the Responses API, and it caps at up to 100 allowed or blocked domains.3 There is a fixed 128k search-context window that applies no matter how large the model's own context window is, and a search_context_size control (low, medium, high) to trade off how much result detail reaches the model.3 Location targeting rides along via a user_location object.

OpenClaw: bring your own search engine#

OpenClaw takes the opposite stance from the two hosted APIs. Instead of running the search itself, it treats the search engine as a swappable provider you choose.4

The web_search tool is a thin, normalizing layer over more than a dozen providers: Brave, DuckDuckGo, Exa, Perplexity, Tavily, SearXNG, Gemini, Grok, and others, some key-free and some paid.4 If you do not name one, OpenClaw auto-detects the first provider that has working credentials. Two OpenClaw users running the identical prompt against the identical model can get different results purely because one configured Brave and the other configured Perplexity. The harness variance we have been describing across companies happens inside a single harness but with OpenClaw, it is a configuration choice.

To keep the model from having to care which engine it got, OpenClaw normalizes every provider into one of a few closed result shapes.4 Structured providers return kind: "results", a plain list of title, URL, and snippet. AI-synthesizing providers (Perplexity, Gemini, Grok) return kind: "answer", a written answer with citations. Either way the payload is tagged untrusted: true and re-wrapped at the tool boundary, a deliberate trust marker so that provider text cannot masquerade as instructions to the agent. That is a security posture the hosted APIs handle differently, and it is visible right in the result contract.

OpenClaw's web_fetch is the piece that lands on your server, and it behaves much like Anthropic's in the ways that matter to a website owner. It does a plain HTTP GET, runs Readability to extract the main content, and returns markdown or text.5 It does not execute JavaScript, and the docs point you at the full browser tool for JS-heavy or login-walled pages, exactly the same fork in the road Anthropic draws.5 Where it differs is in the details the harness author gets to set: it sends a Chrome-like User-Agent by default, blocks private and internal hostnames to guard against server-side request forgery, caps the response size, and can fall back to a provider like Firecrawl when plain extraction fails on a stubborn page.5

Same two tool names as everyone else. A completely different machine behind them.

Why any of this matters to your website#

Step back and the three harnesses line up as three answers to the same question, "how should an agent touch the web?"

HarnessWhere search runsSeparate fetch toolCitationsWhat reaches your server
Anthropic Claude APIAnthropic's serversYes, gated to URLs already in contextAlways onFull-text fetch, no JavaScript
OpenAI Responses APIOpenAI's serversNo, page reading folded into searchInline plus a sources listHandled inside search, no exposed fetch
OpenClawYour chosen providerYes, runs locallyDepends on the providerLocal fetch, no JavaScript

For a website owner, the throughline is the one we keep coming back to. Whichever harness is asking, the request that finally reaches your server is a plain fetch that does not run your JavaScript or route JS-heavy pages to a separate, heavier browser tool that most agents will not reach for on a routine read. The specifics differ (the User-Agent, the domain rules, the size caps, the price), but the shape of what hits your site is remarkably consistent: a bare HTTP GET that reads your raw HTML and stops there.

The fetch that reads your site runs no JavaScript

This is the detail to carry away. A page that renders its content client-side, painting it in with JavaScript after load, hands the agent an empty shell. It does not matter which of these harnesses is asking: the routine read is a plain fetch of your raw HTML, and if the content is not in that HTML, the agent never sees it.

This is also why "did the agent find me?" has no single answer. It found you, or didn't, through a particular harness, with a particular search provider and a particular fetch implementation. A page that reads cleanly for one may return an empty shell to another. The variable you actually control is not which harness a user runs. It is whether your page is legible to the lowest common denominator all of them share: a JavaScript-free fetch of your server's raw response.

At Aandeg we are building Agent Assay, which requests your website the way an agent's harness does, with an agent-style fetch that does not execute JavaScript, and reports whether the raw response is something an agent could actually read. The harnesses differ in a hundred details. The fetch they share is the one your site has to survive. If you want to know when it launches, you can sign up on the Agent Assay section of our home page.

Footnotes#

  1. "Web fetch tool," Anthropic (Claude Platform docs). https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool . ↩ ↩2 ↩3 ↩4 ↩5

  2. "Web fetch," OpenClaw docs (tools/web-fetch.md, bundled with the OpenClaw distribution; mirrored at https://docs.openclaw.ai/tools/web-fetch ). ↩ ↩2 ↩3

← All research