Skip to content

Browser Session Contract

A governed agent can drive one browser session across many action batches through a published contract in the Agent Runtime Pack. The pack ships schemas and pure policy functions, and no browser. The host owns the browser binary and the egress wall (a hosted service, a third-party builder or a self-hosted runner implements BrowserExecutor), so the same agent loop runs on any host.

BrowserActionSchema (zod) validates navigate, click, type, submit, screenshot, download and page_text. Every action carries the session_id it runs in. Each action returns a BrowserActionResult with a posture: ok, handoff_required, egress_denied or failed.

A session id is owned by one task. createBrowserSessionRegistry() keeps one browser context across batches until idle_timeout_s of inactivity or the task end (endTask). A closed id is refused (SESSION_EXPIRED, SESSION_ENDED) and never reopened, and earlier actions are never replayed into a new context. Another task’s id is refused (SESSION_NOT_OWNED).

A task passes allowed_origins, and narrowAllowedOrigins() proves they are a subset of the binding’s allowedOrigins. It is deny by default: an empty list, an invalid entry and an origin the binding does not list are refused. classifyBrowserRequest() returns allowed or egress_denied for each navigation, redirect and subresource. runBrowserBatch() applies it to the executor’s reported requests and ends the batch on a denial. The pack reports what happened; blocking the traffic is the host’s egress wall.

Evidence is an asset_ref plus a SHA-256 content_hash, never image bytes. Fields matching the configured redaction_rules must be masked, and the executor reports a proof (the rules it applied and the count of matches left unmasked). proveScreenshotRedaction() fails closed: no rules, no proof, a rule not applied or any unmasked match means no evidence is kept (REDACTION_NOT_PROVEN). A step receipt records the length of typed text, never the text.

page_text returns a node tree capped by page_text.max_nodes, max_depth and max_text_chars, with truncated set when a budget was reached. The host classifier reports a page class; when it is one of handoff_classes the batch ends with posture handoff_required and the remaining actions are skipped. The classes are configuration, not part of the pack.

const report = await runBrowserExecutorConformance({
  executor, // your BrowserExecutor
  config: parseBrowserSessionConfig(workspaceConfig.agent_runtime.browser_session),
  binding_allowed_origins,
  fixture, // a site you control, with a credential-shaped field
  readAsset: (ref) => store.read(ref),
});

It checks one context across three batches, the masked value absent from the evidence asset and the receipts, unproven masking failing closed, off-origin and redirect denial, page_text budgets and handoff. Configuration is described in Configuration.