Skip to main content
Formation exposes three independent capabilities: search, page reading, and browser interaction. Providers implement vendor-neutral contracts; the agent sees stable tool names. Search is implemented with Cloudflare Web Search, Brave and Perplexity Search, page reading with Cloudflare and Firecrawl, and interaction with Cloudflare and Browserbase. The integration catalog preserves provider choice, instance defaults with agent overrides, agent-owned browser authentication, and per-operation effect semantics.

Configuration and identity

Credentials are held by vendor accounts: one credential per vendor per owner (the install or an agent), shared by every capability that vendor implements. Vendor-level settings such as a Cloudflare account ID or a Browserbase project ID live on the account. A capability connection then chooses which vendor’s implementation it uses and carries only capability-level settings (browser hostname restrictions, a search gateway or engine) and limits. Connecting Cloudflare once therefore covers search, reading and the browser; switching a capability between vendors never moves or erases a credential, and removing an account leaves the connections that pointed at it unavailable rather than borrowing another vendor’s key. An install connection with no stored row is automatic: the capability uses the first vendor in catalog order (Cloudflare first) that the install has an account for. Storing mode: "disabled" is the opt-out; storing mode: "own" pins a provider. Agents automatically inherit the install’s effective connection unless explicitly turned off or customized with their own provider and their own vendor account. A missing agent row means inheritance, including the instance request limit; a stored disabled row remains an opt-out. Disabled capabilities receive no tool. An explicit agent override never falls back to an install account when its own account is missing or invalid. Configuration is read again before execution; rotation, removal and disabling invalidate previously offered tools, and browser sessions are swept when the account they were opened under changes. A hosting operator can supply a managed vendor account to the process with MESH_CLOUDFLARE_API_TOKEN and MESH_CLOUDFLARE_ACCOUNT_ID. It behaves as the install’s Cloudflare account whenever no stored one exists, so a freshly booted install has every web capability available to every agent with no dashboard setup. An install owner can connect their own Cloudflare account, which takes precedence while stored, and remove it to return to the managed one; the dashboard shows which is in use. Agents never use a managed account for a provider of their own. The managed token is read from the environment on each use and is never written to the database or returned by any route. Web tools use dynamic tool.Provider grants rather than static secret-kind Needs: an offer captures the agent and effective connection versions, and execution rechecks both under the same locks used by configuration writes. Keys are passed only to the selected adapter for that request, never injected into a workspace, prompt, or browser page. Queued stale offers are refused. In-flight HTTP calls poll the binding every second and cancel on change; a second check withholds results that race revocation. Disabling returns after the durable gate changes, not after a remote service confirms cancellation. Already-transmitted requests may still consume provider quota. This is an explicit query-provider exception to the generic drain-before-return lifecycle; it does not claim a remote action was undone or a token revoked at its issuer. Infrastructure credentials pay for search or browser execution. Website accounts, cookies, and authenticated profiles belong to an agent. Sharing a provider key does not share a website identity. Browser sessions are run resources; persistent profiles are separate, agent-owned resources with serialized writers.

Runtime contracts

web_search returns ranked source URLs, titles, excerpts, and available dates. web_fetch returns extracted page content and the requested source URL. Both call declared hosted APIs; the control plane never fetches arbitrary website URLs. browser_navigate, browser_read, browser_act, and browser_screenshot operate on an isolated remote session. Browser actions cross the durable effect boundary and uncertain effects are never automatically replayed. The generic web capability is a Formation-owned tool surface. Vendor integrations are replaceable adapters with declarative configuration and a single catalog entry, following layers.md. This refines the older prohibition on first-party vendor wrappers: providers do not introduce vendor-specific tools or harness branches. Remote MCP remains the extension surface for additional third-party tools; web connections do not reuse MCP credentials or its server identity table. All six tools are registered and return tool.Observation through Definition.Execute, with schema, local URL-validation, provider-request, and artifact tests. Fixtures do not establish hosted redirect or subrequest isolation; browser destination guarantees differ by provider as described below. Search validates before admission, caps wire responses at 2 MiB and model observations at 32 KiB, and reports provider failure without exposing upstream response bodies. Database and cancellation failures remain execution failures. Budget and revoked-grant refusals are explanatory tool observations. Search creates no image or unbounded-content artifacts. Search only calls adapter-owned HTTPS API origins; redirects and ambient proxy configuration are refused. It never dereferences returned URLs. Page reading and browsing validate destinations separately. Hosted browser sessions accept HTTP and HTTPS destinations, including private hosts and custom ports reachable from the provider sandbox; Formation does not fetch those destinations. Hostname restrictions are optional: an empty list allows any website, and a configured list rejects destinations outside those hostnames. Non-HTTP schemes are invalid. Enforcement of redirects and page subrequests belongs at the remote execution boundary; DNS checks in the Formation control plane alone cannot pin a hosted provider’s DNS resolution. Adapters must state that boundary and reject requested policies they cannot enforce, rather than promise equivalent isolation from a URL-string check. All returned content is external evidence, not an instruction. Results retain their conversation provenance through memory and replies. Requests and results are bounded; provider failures produce actionable observations without disclosing tokens or response bodies. Per-run provider calls are metered durably before the request, alongside the harness’s existing budgets. No automatic provider fallback changes the payer or sends queries to an unselected service. Reservation identity is the persisted tool_calls.id. A unique reservation, the per-run counter, shared rate counter, and source evidence commit in one transaction. A repeated admission does not increment either meter and never resends the request. The run ledger remains the sole store of completed results and uncertain outcomes. Limits count admitted attempts, not invoice charges: once admission commits, a crash or cancellation conservatively consumes a unit even if transmission cannot be established. Before admission, cancellation or validation failure consumes none. Formation does not automatically retry or refund an uncertain attempt. This deliberately favors a hard spending bound over claiming exact correspondence with a provider’s billing ledger.

Delivery

  1. Search: shared configuration and credential resolution, Brave and Perplexity, dashboard and operator API, provenance, limits, and integration tests.
  2. Read: Firecrawl and Cloudflare behind web_fetch, URL validation, extraction limits, source metadata, and provider contract tests.
  3. Interact: Cloudflare and Browserbase sessions, browser tools and observations, agent-owned profiles, cleanup and recovery, protected artifacts, and tests.
Provider fixtures are the repeatable CI contract. Live vendor acceptance requires operator credentials and must be reported separately from fixture validation.

Configure web access

Open Settings → Web access and connect a vendor account. The Cloudflare card lists the token permissions each capability needs (Browser Run Edit for reading and the browser; Workers AI Read and AI Gateway Read for search) with links to create the token and find the account ID. Saving the account runs a verification of every capability the vendor implements and reports, per capability, whether it worked and, when it did not, the HTTP status, the vendor’s numeric error codes and request ID (never its message text), and the permissions that capability needs. A Cloudflare 403 is not always a missing grant: an account the feature is not enabled for answers the same way, which the error codes distinguish. Every capability left on automatic selection is then available to all agents. Open an agent’s Tools → Web access and choose Customize to configure that agent’s own provider and vendor account, Turn off to opt out, or Use instance settings to restore inheritance. New and existing untouched agents inherit live instance settings. No restart or environment variable is required. Cloudflare Web Search is served through AI Gateway. The connection can name a gateway (default default) and a search engine (ceramic, the default, exa or linkup); engine pricing and the AI Gateway credit requirement are Cloudflare’s. Brave and Perplexity remain available as search-only vendors. The connection meters requests per minute across its agents and requests per run. An inheriting agent can lower the instance’s per-run allowance. A rate or run limit produces an observation explaining exhaustion; requests are never retried automatically. Provider changes and key rotation invalidate old offers. Selecting a different provider does not send it the previous provider’s key: keys are stored separately by vendor and owner. Removing a vendor account deletes its key and settings atomically; capabilities that pointed at it stop being offered until another account is connected or the capability is moved. Search currently accepts query (up to 2,000 bytes) and limit (1–10, default 5). Provider-specific filters and generated research answers are outside this initial common contract. Excerpts are bounded and carry a truncated indicator.

Operator API

GET returns capabilities and vendors. Each capability carries its provider descriptors (name, label, vendor, permissions, capability-level fields), the stored settings, effective_provider, credential_source (install or agent), account_source (owner, environment or empty), account_version, credential_present and available. Each vendor carries its descriptor (label, credential_label, setup_instructions, setup_links, vendor-level fields), the owner’s stored config and version, credential_source, configured, environment_available (a managed account exists for the install whether or not it is in use) and the capabilities the vendor implements with the permissions each needs. Secrets and secret fragments are never returned. Every response is Cache-Control: no-store. Every write answers with the same listing. PUT …/vendors/{vendor} stores or rotates an account:
version is the account’s version from GET (zero for a new account); a stale version returns 409. Omit api_key to keep the stored credential; creating an account without one returns 412. Blank keys are rejected. DELETE takes {"version": n} and removes the account and its credential; for the install this is also how an operator returns to a managed account. PUT …/{capability} takes a full settings replacement and the connection version from GET (zero for a new configuration):
An install uses mode: "auto" (an empty provider and config) to return to automatic selection; an agent uses mode: "inherit" the same way. mode: "disabled" removes the capability from future offers. mode: "own" requires that the owner has an account with the provider’s vendor and otherwise returns 412; it never stores a half-configured connection that would silently fall back. POST …/vendors/{vendor}/test verifies the owner’s saved account against every capability the vendor implements and returns, per capability, the provider, the permissions it needs and a probe result. POST …/{capability}/test verifies the saved effective connection of one capability. Both accept only an empty body or {}; the credential, configuration and target are always the saved ones. The API settings write is audited by the existing operator middleware. Runtime observations identify the capability, provider, and credential owner type; queries and result excerpts inherit the originating conversation’s evidence scope. Encrypted credentials continue to use Formation’s existing master-key rotation and backup procedures. Migration 0066 adds the connections and meters and extends external evidence without changing MCP connection ownership.

Public page reading

web_fetch is registered by the read capability with firecrawl and cloudflare providers. Configure it in Settings → Web access and agents inherit it automatically. Agent overrides are available in Tools settings. Cloudflare requires an account ID and a token with Browser Rendering Edit permission; Firecrawl requires a Firecrawl API key. The same operator API, version checks, durable attempt limits, and conversation evidence used by search apply independently to read connections. Arguments are a public url and optional max_bytes (1–20,000; default 12,000). The tool returns requested_url, optional title, Markdown content, retrieved_at, and an explicit truncated flag. Neither adapter claims a verified final redirect URL. Provider responses are limited to 2 MiB; encoded observations to 30 KiB, including JSON escaping. Empty results, unsuccessful responses, and reported HTTP error pages fail without exposing vendor bodies. Cloudflare can still return a site’s block page as ordinary extracted content; callers must assess the content, not infer success from a nonempty result alone. The only network connections made by Formation are to the two declared HTTPS API origins. Model arguments cannot supply headers, cookies, HTML, actions, custom API endpoints, or proxy settings. Firecrawl cache reuse and storage are disabled for these requests; the provider’s separate retention policy still applies. TLS verification is explicitly enabled. Input validation rejects credentials, custom ports, non-HTTP schemes, local hostnames, ambiguous numeric hosts, and private/reserved IP literals. These checks do not pin the hosted browser’s DNS resolution or constrain its redirects/subrequests. The hosted provider is the network isolation boundary; this capability is for public content and supplies no Formation network access or website credentials. It does not offer an operator domain-egress restriction because these quick-action APIs cannot provide equivalent enforcement across both adapters. Deployments requiring such a restriction must leave this capability disabled until a provider with that enforceable contract is added. Provider contracts: Firecrawl scrape and Cloudflare Markdown.

Browser interaction

Browser interaction combines element references with native visual and keyboard controls. Automated tests cover provider protocols, input handling, and database-backed ownership/lifecycle; live website validation is separate. Application compatibility, including Google Docs editing, must be established with the intended provider and workflow. Operator login/MFA handoff is not part of these controls. The browser connection offers browser_navigate, browser_read, browser_act, and browser_screenshot through Cloudflare Browser Run and Browserbase. Both adapters use hosted browsers controlled through CDP. Configure an account ID (Cloudflare) or project ID (Browserbase) and the provider credential. For Cloudflare, create a custom API token with Account → Browser Run → Edit (called Browser Rendering in some Cloudflare documentation), including the configured account. Specific account and All accounts both work when they include that account. In the Cloudflare dashboard, select that account, open Search (Cmd/Ctrl + K), and choose Copy account ID. See the Cloudflare setup guide and account ID instructions. Browserbase credentials and the project ID come from its project settings. Cloudflare acquires a browser directly over the documented /browser-run/devtools/browser WebSocket endpoint, with Bearer authentication and a five-minute idle allowance. Optional domain guardrails use the documented base64url cf-brapi-guardrails acquisition header. Formation validates the returned session identity and constructs its fixed Cloudflare reconnect URL, rather than following an arbitrary provider-supplied URL. It reads the session ID from cf-browser-session-id, or Cloudflare.getSessionId when that header is absent, following Cloudflare’s Agents SDK and CDP implementation. Subsequent commands reconnect to the same retained session. Saved /browser-rendering/devtools/browser WebSocket endpoints remain supported. Cleanup uses the documented HTTP DELETE endpoint without a CDP reconnect that would extend idle lifetime; a closing response does not yet prove termination. If a browser call fails, its error identifies the stage (for example, session creation, browser connection, or navigation). HTTP 401 and 403 remain distinct: 401 means browser access was rejected; it does not establish that the API token is invalid or expired. HTTP 403 suggests checking token permissions and account access. Neither status alone proves the credential’s exact defect. HTTP 429 indicates provider rate or capacity limits. Failed HTTP and WebSocket handshakes retain up to eight numeric Cloudflare error codes (1–999999) from a bounded 16 KiB JSON envelope and a format-validated CF-Ray request ID. Free-form provider messages, response bodies, credentials, and raw connection errors are never exposed. Failure observations carry is_error: true and structured browser_failure (stage, phase, reason, outcome, recovery, retry guidance, and HTTP status or provider codes/request ID when known). Web capability provenance preserves this metadata; commitment evidence rejects error observations. A completed tool handler still has execution status succeeded, but the message trace labels the observation operation failed. This separates execution from operation outcome without changing replay or mutation-completion semantics. The classifications distinguish three cases:
  • A known pre-dispatch refusal, such as a stale reference, changed viewport, obscured target, or read-only field, reports not_dispatched. Observe again before choosing a fresh action; the refused input was not sent.
  • When every input command was acknowledged but reading or saving its resulting observation fails, the outcome is input_dispatched. This proves browser input delivery, not that the website saved an edit. Inspect with browser_read or browser_screenshot before deliberately continuing. The session remains available for that inspection and subsequent actions.
  • A failure during input delivery remains uncertain. Read-only inspection remains available, but cannot itself prove whether the original effect happened. The original operation must be reconciled before further mutations; Formation does not silently release that reservation or replay the input.
No classification automatically retries an edit or treats stable page content as proof that a website committed it. Read-only observation failures dispatch no input and may be retried. They do not turn an active session uncertain or clear uncertainty from an earlier edit. Editors that keep changing after the bounded wait return the latest complete observation with page_changing: true; controls are revalidated before input. Browsing is open by default: a blank hostname list omits provider domain controls. An operator may optionally supply comma-separated hostnames, including the login, redirect, API, and asset hosts a workflow needs. Saved lists remain in effect until explicitly cleared. This is not the control-plane URL reader: destinations are opened by the remote cloud browser, so Formation does not impose the reader’s public-network or default-port policy on them. Provider reachability still applies. When supplied, the list is sent to the provider’s domain controls; arbitrary provider endpoints, JavaScript, HTTP headers, filesystem uploads, and downloads are not exposed as agent arguments. These controls have different scopes. Cloudflare guardrails restrict HTTP(S) requests, including redirects and page dependencies, to the configured hostnames. Browserbase allowed domains restrict top-frame HTTP(S) navigation and also permit subdomains; they do not restrict iframe loads, scripts, images, or XHR. Formation separately validates HTTP(S) addresses and, when a list is configured, refuses observations outside its exact hostname list, but those checks cannot prevent requests already made by the hosted page. Browserbase therefore does not provide request-wide domain isolation. Deployments requiring that policy must use an appropriate provider rather than treating these adapters as equivalent egress boundaries. Neither local fixtures nor URL validation prove remote DNS pinning or private-address filtering. Instance setup offers Save and enable, Disable connection (retains the credential), and a confirmed Disconnect action (deletes the saved credential and disables the connection). Agents must still choose to use the connection. Browser connections initially show Configured. Test saved browser connection acquires a fresh blank browser, reconnects, verifies Browser.getVersion, and closes the session. It uses the saved effective provider and credential, including instance infrastructure inheritance; unsaved form values are never sent. It does not load website profiles, create a run, or navigate to a website. Success shows Verified for the current view; it is not proof of a website login or completed Google Docs edit. Saving or changing the effective connection clears that result. On failure, the result includes only a fixed stage, HTTP status, numeric provider codes and validated request ID. For Cloudflare HTTP 401, Formation also checks both user-token and account-token verification APIs; it reports an active token only after a successful verification and never infers an expired token from a failed verification request. POST /api/settings/web/browser/test (instance owner) and POST /api/agents/{slug}/web/browser/test (authorized agent access) accept an empty body or {}. Other input is rejected. Search and reading tests send one small fixed request the same way. The provider call has a 45-second timeout and independent bounded cleanup, and configuration and account versions are checked again before a result is returned. Website identities remain agent-owned throughout. A browser session belongs to one run and uses that run’s deadline, not a separate five-minute lifetime. Browser operations use Formation’s normal run allowance; the legacy web-connection request/rate fields do not cap browser work and are not shown in browser setup. Search and reader limits are unchanged. Durable browser usage and source evidence are still recorded. Cloudflare’s five-minute keep_alive setting is an idle timeout between CDP connections, not a total-session limit; Cloudflare also imposes its own quotas. Browserbase’s session timeout follows the remaining run duration, within its provider-supported range of 60 seconds to six hours. Formation cleans up at run end. One live session may write an agent’s profile at a time. Durable operation leases serialize access across replicas; a crashed operation becomes uncertain. Read and screenshot calls are replayable observations, while navigation and actions cross the run ledger’s existing external-effect boundary before execution. The runtime never repeats a dispatched action automatically. After an uncertain action, inspect the page with browser_read or browser_screenshot; the recovery classification above determines whether further actions are available. A lost session requires a new run, rather than an invisible session replacement.

Element and visual actions

browser_read returns bounded text, visible controls, and fresh ref values. References live in isolated JavaScript worlds and reject changed, detached, or obscured targets; typing also refuses read-only regions. The reader traverses open shadow roots and available child-frame contexts. Frame observations identify unavailable or restricted contexts instead of inventing controls. Frame and element counts are bounded, and truncation is explicit. Closed shadow-root internals are not exposed as DOM references. Use a ref with click, type, press, or fill_secret. type selects and replaces the entire value of an input, textarea, or editable text region; it is not append-only typing. press focuses the referenced control and sends a native key or chord. Password fields support ordinary clicking and typing. References are replaced by a new read and expire after an action or navigation. For a canvas editor or another surface without a useful DOM reference, call browser_screenshot. Its protected image accompanies observation_id, viewport_width, viewport_height, and expires_in_seconds. Supply that ID instead of ref to use native input: Coordinates and wheel distances use CSS pixels in the returned viewport, with coordinates measured from its top-left corner. Pointer origins and drag destinations must be inside that viewport. Visual insert_text is limited to 32 characters per action; DOM type retains its 2,000-byte limit. Key chords support Control/Ctrl, Alt/Option, Shift, and Meta/Command/Cmd, plus letters, digits, function keys F1–F24, and editing/navigation keys such as Backspace, Delete, Home, End, PageUp, PageDown, Escape, Tab, Enter, and the arrow keys. Modifiers are released within the action. No agent-provided JavaScript is evaluated. For example, after observing an editor, click its visible editing surface:
Each successful visual action returns the resulting page, a fresh screenshot, and a new observation_id. Use the new ID for the next step:
Then use that action’s new ID with {"action":"insert_text","observation_id":"<new observation ID>","text":"banana"}. insert_text uses the selection/caret already present; use a selection chord such as Control+a deliberately when replacing content is intended. It sends native key events for supported characters, with Chromium text insertion for other Unicode characters. Send chunks of at most 32 characters and inspect the returned screenshot before continuing. Newlines and tabs act as their keys. A visual observation expires after 60 seconds, an action, navigation, or a viewport/document/frame change; taking another screenshot replaces its ID. Visual input is fenced to the owning agent, run, target, document, and observed viewport. When hostname restrictions are configured, they also apply to the receiving frame. Native pointer and focused-keyboard input can reach rendered frame or shadow content without exposing it as a DOM reference; unavailable frame validation is refused rather than bypassing the restriction. fill_secret requires a fresh DOM reference to an input or textarea and an agent-vault entry name (Agent vault). Formation opens the value for the session’s agent, inserts it over CDP, and discards it, so the model names the secret and never sees it. The action refuses text, key, and visual coordinates/observation IDs. Contenteditable regions and canvas surfaces do not accept secret-reference filling. Post-action observations wait at least 750 ms and require a complete document with 500 ms of stable visible content, within an eight-second bound. Stability is not confirmation that a submission or document edit was saved; verify the website result and, when relevant, persistence after a reload.

Website identities and screenshots

Website profiles contain cookies and local storage in Playwright storage-state shape. They are encrypted in Formation’s existing secret store, so the existing master-key rotation process covers them. Profiles are agent-owned and remain separate from provider credentials. IndexedDB, session storage, browser extensions, downloads, and complete browser-user-data directories are not persisted. Cookies can expire or be invalidated by the website. Credentials typed through browser_act are ordinary tool arguments recorded in the run ledger; use fill_secret or authenticated profile import when avoiding password text in tool history is required. Visual controls do not add operator MFA handoff. In an agent’s Tools page, Website logins imports authenticated storage state, clears saved login state, and stops an active browser. Profiles cannot be replaced while a browser is using them. Profile APIs never return stored cookie values. The endpoints are GET/PUT /api/agents/{slug}/browser-profile and POST /api/agents/{slug}/browser-profile/stop, behind agent-access checks and the existing mutation authentication/audit middleware. PUT accepts version and state; changing stale state fails rather than overwriting it. Screenshots are JPEGs bounded to 1280×720 and 2 MiB, encrypted as agent-owned artifacts, with a 24-hour read lifetime. The tool observation retains an artifact reference. The background sweep deletes expired encrypted image bytes while retaining artifact metadata for audit. Vision-capable models receive the newest screenshot as image bytes, never a signed remote browser URL. Operators can retrieve an artifact through GET /api/agents/{slug}/browser-artifacts/{artifact} with agent authorization. A background sweep closes sessions after a run ends, credentials are disabled or changed, or the session expires. A busy operation retains its lease until it finishes or the lease expires. A remote close failure retains the profile writer and retries cleanup, preventing another run from concurrently using its login. Provider timeouts bound orphaned sessions after a process crash. No API token, CDP connection URL, or cookie value is returned in browser observations.

Opt-in Google Docs acceptance

TestLiveGoogleDocVisualEditing exercises the real Formation tool pipeline against Cloudflare, with an isolated local scratch database and a disposable Google Doc that permits anonymous editing. It appends a unique banana mesh-live-<UUID> marker, verifies rendered document content, reloads through browser_navigate, and requires that marker to persist. It closes the remote browser and drops the scratch database afterwards. This tests tool execution, not a Daedalus model conversation or every Google Docs workflow. Canvas-only builds are verified from the actual screenshot using local tesseract OCR; install it on the test machine. No document API, export endpoint, pending input value, or remote OCR service is used for persistence verification. Compile the test binary from the repository root:
Run only this test, supplying provider credentials directly on standard input from your secret manager. Its stdin JSON shape is {"provider":"cloudflare","account_id":"<account ID>","token":"<token>"}; credentials do not belong in shell arguments, environment variables, or a checked-in fixture. Configure the database URL for a local test role allowed to create databases:
MESH_LIVE_ARTIFACT_DIR is optional. When set, the test exports restricted-permission JPEG evidence before editing, after editing, and after reload; failures can export a verification screenshot. The credential is encrypted only in the scratch database, and the test does not update a production connection or import a website identity. Report live results separately from ordinary CI, where the test skips.