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. Storingmode: "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
- Search: shared configuration and credential resolution, Brave and Perplexity, dashboard and operator API, provenance, limits, and integration tests.
- Read: Firecrawl and Cloudflare behind
web_fetch, URL validation, extraction limits, source metadata, and provider contract tests. - Interact: Cloudflare and Browserbase sessions, browser tools and observations, agent-owned profiles, cleanup and recovery, protected artifacts, and tests.
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 (defaultdefault) 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):
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. Thebrowser 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 withbrowser_readorbrowser_screenshotbefore 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.
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:
observation_id. Use the new ID for the next step:
{"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 throughbrowser_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:
{"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.