Overview: empirically tests web fetch behavior across agentic platforms to contribute to the Agent-Friendly Documentation Spec - specifically the Known Platform Limits; full methodology and findings in the docs
Origin: programmatic extension of Dachary Carey's Agent Web Fetch Spelunking
Goal: measure what happens between "agent fetches URL" and "user sees output" - retrieval mechanism behavior, content transformation, architectural constraints. Observes the URL-to-response pipeline through layers platforms don't disclose.
| Platform | Tool | Scripts, Results |
|---|---|---|
| Anthropic Claude API | web fetch | claude-api/ |
| Anysphere Cursor IDE | @Web context attachment |
cursor-web-fetch/ |
| Cognition Windsurf Cascade IDE |
read_url_content,@web directive |
windsurf-cascade-web-search/ |
| Google Gemini API | URL context | gemini-url-context/ |
| Microsoft GitHub Copilot Extension |
fetch_webpage |
copilot-web-content-retrieval/ |
| OpenAI APIs Chat Completions, Responses |
web search | open-ai-web-search/ |
Each platform has two tracks:
- Interpreted - ask the agent to reflect on what it retrieved; captures self-perception and estimation variance
- Raw - Python extracts measurements directly from the response object; produces citable, reproducible numbers
Cursor, Copilot and Cascade testing use a hybrid approach: raw track saves verbatim output to disk for programmatic analysis; interpreted track asks the agent to self-report metrics from fetched content. Visit the Cursor Friction Note and Copilot Friction Note for methodology challenges unique to IDE-based testing.
Cascade adds a third track: explicit - identical to interpreted, but prefixed with
@web; designed to test whether it changes retrieval ceiling, tool chain, or chunking behavior; Cascade Friction: Explicit analysis.
- Python 3.8+
- API keys for the applicable platforms
- Copilot extension-chat, Cursor and/or Windsurf-Cascade IDEs if applicable
# Clone the repo
git clone https://github.com/rhyannonjoy/agent-ecosystem-testing.git
cd agent-ecosystem-testing
# Create and activate a virtual environment
python3 -m venv venv
# On Windows: venv\Scripts\activate
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Set API keys
export ANTHROPIC_API_KEY="key-here"
export GOOGLE_GEMINI_API_KEY="key-here"
export OPEN_AI_API_KEY="key-here"# Cascade-interpreted, no `@web`
python windsurf-cascade-web-search/web_search_testing_framework.py --test {test ID} --track interpreted
# Explicit, `@web` prefixed
python windsurf-cascade-web-search/web_search_testing_framework.py --test {test ID} --track explicit
# Raw
python windsurf-cascade-web-search/web_search_testing_framework.py --test {test ID} --track raw
# ChatGPT-interpreted
python open-ai-web-search/web_search_test.py
# Raw
python open-ai-web-search/web_search_test_raw.py
# Claude-interpreted
python claude-api/web_fetch_test.py
# Raw
python claude-api/web_fetch_test_raw.py
# Copilot-interpreted
python copilot-web-content-retrieval/web_content_retrieval_testing_framework.py --test {test ID} --track interpreted
# Raw
python copilot-web-content-retrieval/web_content_retrieval_testing_framework.py --test {test ID} --track raw
# Cursor-interpreted
python cursor-web-fetch/web_fetch_testing_framework.py --test {test ID} --track interpreted
# Raw
python cursor-web-fetch/web_fetch_testing_framework.py --test {test ID} --track raw
# Gemini-interpreted
python gemini-url-context/url_context_test.py
# Raw
python gemini-url-context/url_context_test_raw.pyFree Tier Limits: Claude API not available on the free-tier, the API is pay-as-you-go; 5 RPM and 20 RPD limits for
gemini-2.5-flash- running both tracks in the same day exhaust the daily quota; plan runs across days or use a paid tier; Cascade, Copilot, and Cursor tests available on Pro Plan; OpenAI requires a minimum credit top-up (~$5) before any API call succeeds regardless of agent -insufficient_quotais an account-level block, not a rate limit; setRATE_LIMIT_SLEEP_SECONDS = 0in the scripts if you're on a paid tier
Like Copilot and Cursor, Cascade testing uses manual chat sessions in the Windsurf IDE.
The framework generates prompts, but execution requires copy-paste into the Cascade chat
panel. Cascade is the first platform in this collection with a user-invocable web directive,
@web, making it the primary variable under test alongside the standard truncation questions.
All 11 URLs run across three tracks; interpreted and explicit track results compared
directly to isolate the @web effect.
| Category | Question | Source |
|---|---|---|
| Baseline | What does Cascade retrieve by default? Does @web change the ceiling/output size on the same URL? |
MongoDB docs ~20KB–256KB; HTML, Markdown URL variants |
| Structured Content | How does Cascade handle tables, code blocks, nested headings, and JavaScript-rendered pages? | Wikipedia, Anthropic API docs, Markdown Guide, Gemini docs |
| Offset/ Pagination |
Does view_content_chunk auto-paginate after truncation, or only when prompted? |
256KB MongoDB tutorial; fragment navigation via URL # identifier |
| Edge Cases |
How does Cascade handle redirect chains, SPAs, raw Markdown files, and JSON endpoints? | 5-hop redirect chain, Gemini landing page, GitHub raw .md |
Cascade self-reported three tools:
read_url_contentfor URL fetch,view_content_chunkfor paginating documents viaDocumentId, andsearch_webfor query-based lookup.read_url_contentrequires approval before execution, a behavior with no Copilot or Cursor equivalent.
Both tracks test the same four URLs. The Claude-interpreted track asks the agent to
describe what it received; the raw track extracts character counts, CSS indicators,
and truncation signals programmatically from the web_fetch_tool_result block.
| Category | Question | Source |
|---|---|---|
| Short HTML, no token limit | Does the API tool have the same CSS-eating-content problem Dachary found in Claude Code? What's the effective default token limit? | Short docs page with substantial inline CSS before content begins |
| Same page, Markdown version | Does the API tool request or prefer Markdown? How much smaller is the token footprint? | .md URL variant of first test, compares input_tokens directly |
| Long page, no token limit | At what point does content get truncated? Does the API tool behave differently than Claude Code, which truncated to ~100KB? | Long tabbed tutorial page, no max_content_tokens set |
| Long page, explicit token limit | Does max_content_tokens=5000 work as documented? Is truncation clean or mid-sentence? |
Same page as Test 3, with max_content_tokens=5000 |
Unlike API-based platforms, Copilot testing uses manual chat sessions in the VS Code IDE.
The framework generates prompts, but execution requires copy-paste into the Copilot chat
window. Both tracks test 11 distinct URLs across baseline, structured content, offset,
and edge case categories. The primary finding is that Copilot autonomously selects between
two retrieval mechanisms -fetch_webpage and curl - with no prompt control, and the
mechanism selected determines output format more than any other variable.
| Category | Question | Source |
|---|---|---|
| Baseline | What does Copilot retrieve by default? How does output vary across agent routing and HTML vs Markdown URLs? | MongoDB docs pages at 20KB–256KB; HTML and Markdown URL variants |
| Structured Content | How does Copilot handle tables, code blocks, nested headings, and JavaScript-rendered pages? | Wikipedia, Anthropic API docs, Markdown Guide, Google Gemini docs |
| Offset/Pagination | Does Copilot auto-chunk after apparent truncation? Can it paginate a large document? | 256KB MongoDB tutorial; fragment navigation |
| Edge Cases | How does Copilot handle redirect chains, SPAs, raw Markdown files, and JSON endpoints? | 5-level redirect chain, Gemini landing page, GitHub raw .md, httpbin.org |
Copilot has no publicly documented web fetch mechanism.
fetch_webpagesurfaced through tool logs. Whencurlsubstitution occurs, it happens without disclosure in the chat UI. Read the Friction Note for analysis.
Unlike testing platforms with API web fetch tools, Cursor testing uses manual chat sessions with the Cursor IDE. The framework generates prompts, but execution is intentionally not automated and requires copy-paste into the Cursor IDE. Interpreted track asks Cursor to self-report; raw track saves outputs to disk for measurement. Both tracks test 13 distinct URLs. Tests focus on truncation behavior, backend routing variance, content conversion patterns, and reproducibility.
| Category | Question | Source |
|---|---|---|
| Baseline | What's the reproducibility baseline? Do same URLs always return identical content? | Small docs pages (4-20KB), tested multiple times to measure variance |
| Structure-Aware | Does Cursor truncate intelligently at content boundaries, or cut mid-content? | Wikipedia pages, Markdown guides, docs with complex structure |
| Output Pattern | What triggers Markdown conversion vs raw HTML? | Large tutorials, JavaScript-heavy SPAs, timeout scenarios |
| Edge Cases | How does Cursor handle redirects, JSON endpoints, raw Markdown files, and version drift? | 5-level redirect chains, API endpoints, GitHub raw .md URLs |
Both tracks test the same eight URLs and content types. The Gemini-interpreted track
asks the agent to characterize each retrieval; the raw track reads url_retrieval_status
enums and tool_use_prompt_token_count directly from the response object.
| Category | Question | Source |
|---|---|---|
| Single HTML page | Baseline: what does a single successful fetch look like? What's the tool_use_prompt_token_count? |
Gemini docs |
| Single PDF | PDF is a documented supported type. Does it actually work? | W3C WCAG 2.1 |
| 5 URLs | Multi-URL baseline. Does url_context_metadata preserve request order? |
Gemini API docs |
| 20 URLs, at limit |
Does the tool handle the maximum documented URL count cleanly? | Gemini API docs |
| 21 URLs, over limit |
Is the limit a hard API error or silent truncation/dropping? | Gemini API docs |
| "Unsupported" | YouTube documented as unsupported. Does the documented limitation match actual behavior? | YouTube music video |
| "Unsupported" | Google Workspace documented as unsupported. Does it fail at the API layer or the retrieval layer? | Google Docs edit URL |
| JSON API endpoint | JSON is a documented supported type. Does it work for unauthenticated API endpoints? | GitHub API |
Two tracks test the same queries through different API surfaces. The ChatGPT-interpreted track
uses the Chat Completions API with gpt-4o-mini-search-preview in which search is always implicit.
The raw track uses the Responses API with gpt-4o + web_search_preview in which tool invocation
is conditional, explicitly observable.
| Category | Question | Track |
|---|---|---|
| Live Data | Does the tool always invoke for live data? Is citation count a reliable proxy for search depth? | Both |
| Recent Event | How consistent are citation counts and source quality for a recent but stable fact? | Both |
| Static Fact | Does the agent skip the tool for a static fact? Is that behavior consistent across runs? | Both |
| Trivial Math | Is tool invocation skipped for a query that needs no retrieval whatsoever? | Raw |
| Open-ended Research |
Do internal search queries reflect the current date? Does search_queries_issued show stale date strings? |
Both |
search_context_size |
What's the latency and source count at low? |
Both |
search_context_size |
Does high produce more sources or lower latency than low? Is the tradeoff consistent across runs? |
Both |
| Domain Filter Allow List |
Does allowed_domains actually constrain sources returned? Does it work on web_search vs web_search_preview? |
Raw |
| Domain Filter Block List |
Does blocked_domains work? What parameter name does the API accept? |
Raw |
| Ambiguous Query |
How does the agent resolve "Python release" - programming language, animal, or both? Is disambiguation stable? | Both |
If you run these tests and get results, please open an issue or PR with your findings.
Especially useful: Run the same tests from different network locations or at different times to check for caching behavior vs live-fetch variation; run PDF tests against different source URLs to distinguish server-blocking from tool limitations; run Cursor tests with explicit agent selection to measure agent-specific variance; test Cursor's MCP server integration vs native web fetch behavior