Web Search Tool
โญ New in 1.3 โ web_search is a tool the agent can call mid-conversation to pull up a search on the device. Itโs a one-shot shortcut for โthe user asked me to look something up.โ
What it actually does (read this first)
Section titled โWhat it actually does (read this first)โweb_search opens a search results page in a browser on the connected phone. Thatโs it.
It does not:
- call a search API (no Serper, no Brave Search API, no server-side search)
- return search results or snippets to the model
- require an API key or any provider signup
It does: build a search-engine URL, then fire an Android VIEW intent at the best browser installed on the device so the results page appears on screen. The agent gets back a short status string (โOpened Chrome โ google search for: โฆโ), not the page contents.
Think of it as the fast path for โsearch for Xโ โ replacing the four-step dance of launch Chrome โ tap address bar โ type โ submit with a single tool call.
How it works
Section titled โHow it worksโAgent calls web_search(device, query, engine) โ โผopen_search() โ gitd/services/web_search.py:72 โ โโ 1. Build URL: _ENGINE_URLS[engine] + urlencode(query) โ โโ 2. List installed browsers: adb shell pm list packages โ โโ 3. Walk the browser priority chain, for each installed one: โ am start -a android.intent.action.VIEW -d <url> -p <package> โ (first one that handles the intent wins) โ โโ 4. Fallback: bare VIEW intent โ system default browser โ โโ 5. Last resort: open Play Store to install a browserBecause am start returns exit code 0 even when no activity handles the intent, _try_open() (web_search.py:57) inspects stdout for Error:, SecurityException, and no activities found to detect real failures.
Engines
Section titled โEnginesโThe engine argument selects a URL prefix (_ENGINE_URLS, web_search.py:39). Unknown values fall back to Google.
engine | Opens |
|---|---|
google (default) | google.com/search?q= |
ddg / duckduckgo | duckduckgo.com/?q= |
bing | bing.com/search?q= |
brave | search.brave.com/search?q= |
Browser fallback chain
Section titled โBrowser fallback chainโopen_search tries browsers in priority order (_BROWSER_CANDIDATES, web_search.py:27) and uses the first one thatโs installed:
Chrome โ Firefox โ Samsung Internet โ Edge โ Brave โ Opera โ Opera GX โ Vivaldi โ DuckDuckGo Browser โ system default โ (Play Store, if nothing else)
So on a stock device youโll land in Chrome; on a stripped vendor build with no browser, Ghost opens the Play Store search for โbrowserโ rather than failing silently.
The tool the model sees
Section titled โThe tool the model seesโweb_search is a static entry in the agent tool list (gitd/services/agent_tools.py:249), in Anthropic tool-use format and auto-converted for other providers:
{ "name": "web_search", "description": "Open a web search in the best available browser. Use when the user asks to search/look up somethingโฆ", "input_schema": { "type": "object", "properties": { "device": { "type": "string" }, "query": { "type": "string" }, "engine": { "type": "string", "description": "google, ddg, bing, brave. Default google." } }, "required": ["device", "query"] }}The same tool is exposed to the MCP Server (gitd/mcp_server.py:403), so a claude-code / Cursor client driving Ghost gets it too. Both paths call the one open_search implementation.
When it fires
Section titled โWhen it firesโAlways available โ no flag, no config, no gate. web_search is a permanent member of the tool list every provider receives, so the model can call it any time. Itโs steered purely by the description: โUse when the user asks to search/look up something.โ Say โlook up the weather in Berlinโ in Agent Chat and the model will typically fire web_search on the active device.
What comes back
Section titled โWhat comes backโThe return value is a status string, one of:
| Situation | Returned string |
|---|---|
| Success (named browser) | Opened Chrome โ google search for: <query> |
| Success (system default) | Opened default browser โ google search for: <query> |
| Empty query | web_search error: empty query |
| No browser handled it | web_search failed: no browser handled the VIEW intent. Opened Play Store to install one. (Tried: โฆ) |
When to use / when NOT to use
Section titled โWhen to use / when NOT to useโUse it when:
- You want the agent to surface a search on the phone for a human to look at
- Youโre building a flow where the phone screen is the output surface (kiosk, demo, assisted browsing)
Donโt reach for it when:
- You want the agent to read and reason over search results โ it canโt; results never come back to the model. For research-style reasoning, use a cloud provider whose model has its own web tool (see LLM Providers).
- You need headless search with no visible browser โ this tool is fundamentally โput a results page on the screenโ.
Gotchas
Section titled โGotchasโ- Results are display-only. The model receives a status string, not page content. This is the single most important thing to understand about the tool.
- No cost, no rate limit at Ghostโs layer โ thereโs no search API to bill or throttle.
- Short ADB timeouts:
pm list packagesis 10 s, eacham startis 8 s (web_search.py). ddgandduckduckgoboth work, though onlyddgis advertised in the schema.
Related
Section titled โRelatedโ- MCP Server โ exposes
web_search(and every other tool) to external MCP clients - ADB Device Control โ the intent/
am startlayer this tool builds on - LLM Providers โ for models that reason over web results, pick a provider with its own web tool