What happened
The WebSearch tool is advertised to the model in the tool list every turn even when it cannot possibly run. Availability is only checked inside the tool's impl/search() — i.e. at call time — so the model spends a turn (and tokens) calling a tool that is guaranteed to fail:
- Desktop path:
apps/desktop/src/main/tool-assembly.ts builds buildWebSearchAgentTool() unconditionally into builtinTools; the settings/credential/privacy checks in apps/desktop/src/main/web-search/agent-tool.ts all live in impl, returning web_search_error { reason: not_configured | incognito_active }. - Host path:
packages/runtime-host/src/server/web-search-tool.ts has the same shape — resolveWebSearchExecution() is only consulted inside search().
The Settings · Web search toggle (webSearch.enabled, which defaults to false), the Tavily credential status, and privacy/incognito mode have zero effect on the model-visible surface. This contradicts the tool's own fail-closed documentation ("Fail-closed paths" in agent-tool.ts) and its description, which actively invites calls ("Use ONLY when the user asks for current external information").
How to reproduce
- Leave web search disabled (the default), or unset the Tavily key, or enable privacy mode.
- Start a session with a real backend.
- Ask something that could benefit from current external information.
- Expected: the model never sees
WebSearch in its tool list (or the surface marks it unavailable), so it never attempts it. - Actual:
WebSearch is in the tool list every turn; the model calls it and gets a web_search_error back, wasting a turn. In deep-research sessions WebSearch is also in DEEP_RESEARCH_ALLOWED_TOOL_NAMES (desktop-backend-tool-surface.ts), so a disabled web search silently cripples the DR toolset too.
Suggested direction
The availability logic already exists and returns rich states: resolveWebSearchExecution() in packages/storage/src/runtime-policy/coordinator.ts:535 yields privacy_mode | disabled | credential_not_configured | ready. It is simply only consulted at call time. Options:
- Filter
WebSearch out of the effective tool surface (tool-assembly.ts / desktop-backend-tool-surface.ts, and the host's execution-model-composition.ts) based on the same check, recomputed per turn so mid-session toggle changes take effect. - Alternatively, keep the tool but attach availability metadata to the surface so the model knows not to call it — though hiding is closer to the existing fail-closed posture and to how capability-gated tools are handled.
Also worth deciding: Settings · Web search wording should state when the tool is hidden from the model vs. merely failing at call time.
Environment
- Surface: Desktop (Runtime Host shares the same pattern)
- Commit: current
main
Logs, screenshots, or additional context
apps/desktop/src/main/tool-assembly.ts — webSearchTool added to toolsAfterSkill / builtinTools unconditionallyapps/desktop/src/main/web-search/agent-tool.ts — fail-closed checks only inside implpackages/runtime-host/src/server/web-search-tool.ts — resolveWebSearchExecution() only inside search()packages/storage/src/runtime-policy/coordinator.ts — resolveWebSearchExecution(), the existing availability seam
What happened
The
WebSearchtool is advertised to the model in the tool list every turn even when it cannot possibly run. Availability is only checked inside the tool'simpl/search()— i.e. at call time — so the model spends a turn (and tokens) calling a tool that is guaranteed to fail:apps/desktop/src/main/tool-assembly.tsbuildsbuildWebSearchAgentTool()unconditionally intobuiltinTools; the settings/credential/privacy checks inapps/desktop/src/main/web-search/agent-tool.tsall live inimpl, returningweb_search_error { reason: not_configured | incognito_active }.packages/runtime-host/src/server/web-search-tool.tshas the same shape —resolveWebSearchExecution()is only consulted insidesearch().The Settings · Web search toggle (
webSearch.enabled, which defaults tofalse), the Tavily credential status, and privacy/incognito mode have zero effect on the model-visible surface. This contradicts the tool's own fail-closed documentation ("Fail-closed paths" inagent-tool.ts) and its description, which actively invites calls ("Use ONLY when the user asks for current external information").How to reproduce
WebSearchin its tool list (or the surface marks it unavailable), so it never attempts it.WebSearchis in the tool list every turn; the model calls it and gets aweb_search_errorback, wasting a turn. In deep-research sessionsWebSearchis also inDEEP_RESEARCH_ALLOWED_TOOL_NAMES(desktop-backend-tool-surface.ts), so a disabled web search silently cripples the DR toolset too.Suggested direction
The availability logic already exists and returns rich states:
resolveWebSearchExecution()inpackages/storage/src/runtime-policy/coordinator.ts:535yieldsprivacy_mode | disabled | credential_not_configured | ready. It is simply only consulted at call time. Options:WebSearchout of the effective tool surface (tool-assembly.ts/desktop-backend-tool-surface.ts, and the host'sexecution-model-composition.ts) based on the same check, recomputed per turn so mid-session toggle changes take effect.Also worth deciding: Settings · Web search wording should state when the tool is hidden from the model vs. merely failing at call time.
Environment
mainLogs, screenshots, or additional context
apps/desktop/src/main/tool-assembly.ts—webSearchTooladded totoolsAfterSkill/builtinToolsunconditionallyapps/desktop/src/main/web-search/agent-tool.ts— fail-closed checks only insideimplpackages/runtime-host/src/server/web-search-tool.ts—resolveWebSearchExecution()only insidesearch()packages/storage/src/runtime-policy/coordinator.ts—resolveWebSearchExecution(), the existing availability seam