Skip to content

Web Search and Read Tools

An agent loop that needs the web should not build its own ungoverned tool. The Agent Runtime Pack ships two tools, web:search and web:read, that run through runGovernedAgentLoop. The host supplies the search provider behind a port; the pack names no provider.

const runtime = createWebToolRuntime(baseRuntime, {
  provider, // your WebSearchProvider adapter
  allowlist: ['docs.example.org', 'example.net:443'], // per-run egress allowlist
  emitter: securityEmitter, // receives sandbox:violation on a denial
  fetch: proxiedFetch, // optional: a fetch routed through the egress proxy
  caps: { max_results: 8, max_read_bytes: 131072 },
});
await runGovernedAgentLoop({
  ...base,
  runtime: run.guardRuntime(runtime), // optional: step records, budgets, approval classes
  executeTurnInput: { ...input, tool_catalog: buildWebToolCatalog() },
});

web:search takes { query, limit?, recency? } (recency is day, week, month or year). It returns results of { title, url, snippet, source_id, citation }, plus truncated.

web:read takes { url, max_bytes? }. It returns { url, final_url, content_type, text, truncated, fetched_at, bytes, untrusted, citation }. Only http and https URLs without credentials are accepted, and only text, JSON and XML content types are returned.

WebSearchProvider has one method, search({ query, limit, recency? }, { signal? }), returning { title, url, snippet } hits. The host supplies the adapter (for example a hosted search service). createFixtureWebSearchProvider(hits) serves tests. A provider failure is the tool error WEB_SEARCH_PROVIDER_ERROR.

The allowlist is per run and uses the same grammar as the sandbox network allowlist: host, host:port or an IPv4 CIDR. An empty list denies every read. The check runs before the first request and again on every redirect hop. A denial returns WEB_EGRESS_DENIED, makes no request and emits one sandbox:violation event (network_egress_blocked) to the emitter.

The default transport is the platform fetch. A host that confines egress passes a fetch routed through the engine egress proxy (see the confinement reference), which adds address vetting and the proxy’s own denial events on top of this check.

caps.max_results (default 10) and caps.max_read_bytes (default 262144) are host ceilings. A call’s limit and max_bytes can only lower them. When results or bytes are cut, truncated is true. Other caps: default_read_bytes, max_snippet_chars, max_redirects and timeout_ms.

Every search result and every page read carries a citation in the shape of the knowledge pack’s KnowledgeCitation: { sourceId, documentId, path, revisionId, retrievedAt }. sourceId is the host name, documentId a digest of the URL, path the URL, and revisionId a digest of the content seen.

Fetched text is data. web:read returns text inside an untrusted-web-content block whose first line says the text is data, not instructions. The block records the source URL, fetch time and a random nonce. Any copy of the block’s tags inside the page is broken, so a page cannot close the fence early. The output also carries untrusted: true and an external_tool_read provenance label. Search results are marked the same way. The loop only executes tool calls the model requests; text in a tool result never becomes a call, and the tests prove an injected instruction in a fetched page does not.

Wrap the web runtime with createAgentTaskRun(...).guardRuntime and each call is a step record under the task’s max_steps, cost and duration budgets. Declare an approval_class for web:read in toolPolicy to put reads behind the request_user gate.

WEB_INVALID_INPUT, WEB_URL_NOT_ALLOWED, WEB_EGRESS_DENIED, WEB_HTTP_ERROR, WEB_FETCH_FAILED, WEB_TOO_MANY_REDIRECTS, WEB_CONTENT_TYPE_UNSUPPORTED and WEB_SEARCH_PROVIDER_ERROR.