novo agents

Managed browser

Turn on capabilities: { browser: true } and the agent gets a real cloud browser it can drive: navigate, snapshot the page, click, type, and screenshot. Novo provisions and operates the browser; you host nothing. It is a managed capability, so the agent loop and your provider keys stay inside Novo and only browser actuation reaches the managed runtime.

const agent = await novo.agents.create({
  context: 'You research products and fill out web forms.',
  band: 'core',
  capabilities: { browser: true },
});

The browser is deterministic: the agent's own model reads an accessibility snapshot of the page and acts on specific elements by reference; there is no second AI driver. Advanced options are explicit:

capabilities: {
  browser: {
    authenticated: { profileKey: 'user:user_123' },
    files: true,
    tabs: true,
  },
}

profileKey is your opaque stable key for a persistent browser profile. Novo stores only a keyed hash of it and leases one live session per (organization, profileKey).

How it works

Perception is the accessibility snapshot. browser_snapshot returns a compact tree of the page in which every interactive element has a [ref=…] handle (iframes and open shadow DOM included). The agent reads that tree and acts by passing a ref as the target of an action tool. Mutating actions return a fresh snapshot, so the agent always has current refs. Refs are valid only until the page changes; after a navigation the agent re-snapshots.

Tools

| Tool | What it does | |---|---| | browser_snapshot | Capture the page's accessibility tree, with a [ref=…] per interactive element. Pass a target to snapshot just one region. Returns untrusted page content. | | browser_navigate / browser_navigate_back | Go to a URL / back. Returns a fresh snapshot. browser_navigate accepts authenticated or isolated mode; authenticated mode requires a configured profile. | | browser_click | Click an element by target (ref or selector). | | browser_type | Type into an editable element; optionally submit. | | browser_fill_form | Fill several fields in one call. | | browser_select_option / browser_check / browser_uncheck / browser_hover / browser_drag | Select, toggle, hover, and drag by ref. | | browser_press_key | Press a key or chord (e.g. Enter, Control+A). | | browser_wait_for | Wait for text to appear/disappear, or a fixed time. | | browser_handle_dialog | Accept or dismiss the next native alert/confirm/prompt (arm it before the triggering action; that action's result reports what was done to the dialog). | | browser_evaluate | Run a JavaScript function in the page and return its result. Returns untrusted page-derived data. | | browser_console_messages | Read console messages and uncaught page errors logged since this turn started. Useful for debugging. Returns untrusted page output. | | browser_network_requests | List network requests the page made since this turn started (method, URL, status). Useful for debugging API calls and failures. Returns untrusted page output. | | browser_take_screenshot | Capture the current view. Returns a model-visible image and a retrievable artifact. | | browser_resize | Resize the viewport. | | browser_file_upload / browser_download_files | Move files between the workspace and the page. Present only with browser.files: true and workspace binary I/O. | | browser_tabs | Multi-tab control (list / new / select / close). Present only with browser.tabs: true. | | browser_mouse_click_xy / browser_mouse_move_xy / browser_mouse_drag_xy | Pixel-coordinate fallback for canvas/WebGL surfaces the accessibility tree can't express. Novo exposes this automatically when the managed browser is enabled; the agent still prefers ref-based actions when refs exist. |

Action tools accept a target that is either a snapshot ref (e.g. e5) or a plain CSS/role/text selector.

Sessions and lifecycle

The browser session is thread-scoped: the agent keeps the same browser across the thread's runs, so page state carries between messages. Without authenticated.profileKey, each session starts clean. With authenticated.profileKey, Novo attaches a persistent provider context so cookies and saved logins carry across sessions and threads. The profile is bound to one live session at a time: profile data is persisted when the session holding it closes, so when a NEW thread needs the profile while a previous thread's session is still open but idle, Novo closes the idle holder first (persisting its cookies) and the new session inherits them. Open tabs and page state never carry across sessions; only the profile's cookies and storage do.

browser_navigate accepts sessionMode: 'authenticated' | 'isolated'. Omit it to continue the thread's live mode or start authenticated when a profile is configured, isolated otherwise. Explicitly switching modes closes the prior thread session and loses its tabs/page state. Isolated mode starts clean and never reads or writes the persistent profile. Authenticated mode without permission returns browser_authenticated_unavailable. If another active thread holds the profile, Novo returns non-retryable browser_context_busy with isolatedAvailable: true; retry in isolated mode only when the task does not require saved logins. Novo never silently downgrades an authenticated request.

Novo provisions the session lazily on the first browser tool call and keeps it alive across run pauses (including tool-approval and user-input waits). It is reaped after about 15 minutes of inactivity, or at a roughly 1-hour hard ceiling. If a session is reaped during a long pause, the next browser call transparently starts a fresh one.

Strict data handling

Baseline managed browser sessions are available with dataHandling: 'strict' when strict browser mode is enabled for the deployment. Strict browser sessions disable browser-session logging and recording where supported; model inference still runs inside Novo, not inside the browser runtime.

Strict browser does not promise regional processing or data residency. A strict run will replace an existing standard browser session rather than reusing it. browser.authenticated and browser.files are not exposed in strict yet because persistent browser profiles, uploads, and downloads have separate storage controls outside provider session ZDR.

Untrusted page content

Everything the browser reads from a page (the snapshot, a browser_evaluate result, page copy) is untrusted input. Novo wraps it so the model treats it as data, not instructions, but a page can still try to social-engineer the agent. For flows that act on what a page says (logging in, paying, submitting forms), gate the action behind a tool approval hook so a human confirms consequential actions.

Files and screenshots

browser_take_screenshot returns the image inline for the model to inspect and also stores the full-resolution PNG as a managed artifact. Public output uses a compact ref: { artifactId } by default, or { artifactId, url } when top-level artifacts config projects a durable handler URL or explicitly includes temporary Novo signed URLs. Screenshot exports are off by default; set artifacts.exports.include.browserScreenshots: true only when your product wants screenshots promoted to durable storage. The inline copy is downscaled to fit model image limits; the artifact keeps full resolution.

Browser downloads are stored internally as managed artifacts. They are exported to your result handler only when artifacts.exports.include.browserDownloads: true. Download bytes are never returned inline to the model.

Live view and session events

To embed or observe the live session, watch the run stream for the public session id, then fetch the live view from your server:

  • A data-novo-browser-session stream chunk carries { runId, threadId, agentId, sessionId, status } and arrives before that step's data-novo-step-terminal. A isNovoBrowserSession(event) guard is exported from the SDK.
  • await novo.browserSessions.liveView(sessionId) calls GET /v1/browser-sessions/:sessionId/live-view and returns a short-lived live-view URL.

liveViewUrl is a control-capable bearer URL: anyone with it can drive the session. It is returned only by the authenticated no-store live-view API and never in streams, durable rows, or model-visible tool output. Do not log or persist it. Lifecycle stream chunks never contain page URLs. A reap-driven close may surface on the next session read rather than as a live chunk, so don't depend on a guaranteed closed chunk.

Billing

Session-minutes accrue while the browser is open, including across pauses, and are billed per use, debited from the same wallet with the standard platform margin, and independent of run completion. They are not bounded by a run's budgetCents / maxDurationMs; the idle timeout is the bound. The model tokens the agent spends reading snapshots and choosing actions bill as part of the run's normal inference; there is no separate browser-driver charge.

See pricing for rates. Browser usage appears in /v1/usage, /v1/usage/events, and the Billing console.

If you enable browser on a workspace that isn't provisioned for the managed browser, agent creation returns a 400 invalid_request_error with code managed_browser_unconfigured. See errors.