← Blog

BLOG

Web Agents: What They Are and the Runtime They Need

Web agents drive real browsers to complete tasks. Learn how they work, how they differ from Playwright scripts, and what a production runtime requires.

9 min readRemote Browser
Ivory computer towers connected across terracotta platforms in a quiet desert landscape
The infrastructure behind your browserREMOTE BROWSER / ILLUSTRATION

A web agent is a program that takes a goal in natural language — "find the invoice for March and download the PDF" — and figures out the clicks, typing, and navigation needed to complete it in a real browser. Unlike a scripted test, it decides its own steps at runtime. Unlike a plain API client, it works through the same interface a person does: pages, forms, and buttons.

That definition sounds simple, but it hides the hard part. The model is only half of a web agent. The other half is the browser it drives, and that half is where most production failures originate. This guide covers how web agents actually work, where they diverge from tools like Playwright, and what to evaluate in the runtime underneath them. If you want the runtime overview first, see Remote Browser for AI agents.

What a web agent actually does

Strip away the branding and a web agent is a loop:

  1. Observe. Read the current page state — DOM, accessibility tree, screenshot, or some mix.
  2. Reason. Given the goal and the observation, choose the next action.
  3. Act. Click, type, scroll, navigate, or extract.
  4. Verify. Check whether the action moved the task forward.
  5. Repeat until the goal is met or a stopping condition fires.

The observation step is what separates web agents from classic automation. A Playwright test knows the selector #submit-order exists because a developer wrote it. A web agent has to discover that the button labeled "Place order" is the right target, often on a page it has never seen.

That discovery loop is expensive. Every step involves a model call, and every model call needs fresh page state. A task that takes a human thirty seconds can take a web agent twenty to fifty steps. Multiply that by concurrency and you get the central infrastructure question: how do you run many real browsers, reliably, for long enough to finish real tasks?

Web agents vs. Playwright: not either/or

A common framing pits "browser use" against Playwright, as if you pick one. In practice they solve different problems and often run together.

DimensionPlaywright scriptWeb agent
Who decides the stepsDeveloper, at authoring timeModel, at runtime
Selector strategyExplicit, hand-writtenInferred from page state
Handles UI changesBreaks until updatedOften adapts
DeterminismHighLow to moderate
Cost per runNear zero beyond computeModel tokens per step
Best forRegression tests, known flowsOpen-ended tasks, unknown sites
Failure modeClear assertion errorSilent wrong action

The practical pattern is hybrid. Use deterministic Playwright code for the parts you control — login, navigation to a known page, form fills with stable IDs — and hand control to an agent for the ambiguous middle. Many teams expose Playwright actions as tools the agent can call, which keeps the cheap, reliable steps cheap and reliable.

If you are comparing the two directly, Agent Browser vs Browser Use covers where the runtimes diverge. The short version: Playwright is a library, and a web agent needs a *place to run* that library at scale.

Why the browser runtime decides success

A web agent's task success rate is a product of model quality *and* environment quality. A strong model in a flaky browser still fails. The environment contributes in several concrete ways:

  • Session stability. Long agent loops run for minutes. If the browser process is recycled, memory-limited, or killed mid-task, the agent loses its page state and has to restart.
  • Page fidelity. Agents that observe the DOM need the real DOM, not a stripped-down renderer. Anti-bot systems, SPAs, and shadow DOM all behave differently under headless approximations.
  • Network identity. Many sites behave differently based on IP reputation and geography. Configurable proxy settings matter for tasks that touch geo-restricted or rate-limited sites.
  • State persistence. Login sessions, cookies, and local storage need to survive across steps and sometimes across runs. Persistent profiles solve this; ephemeral sessions do not.
  • Observability. When an agent fails, you need to see what it saw. A live viewer and session recordings turn a black-box failure into a debuggable one.

None of these are model problems. They are runtime problems, and they are why teams that prototype locally hit a wall in production.

The local-browser ceiling

Running a web agent against a local Chrome install works for a demo. It stops working when you need any of the following:

  • Concurrency. Ten parallel agents need ten isolated browser contexts, each with its own profile and network identity. A laptop handles a handful before memory pressure degrades everything.
  • Uptime. Agents that run on schedules or in response to events need a browser that is always available, not one tied to a developer's machine.
  • Isolation. Two agents sharing a profile will corrupt each other's cookies and sessions. Isolation has to be structural, not a convention.
  • Reproducibility. Debugging a failure means replaying the exact session. That requires recordings and persistent state, which local runs rarely capture.

The usual next step is self-hosted Playwright infrastructure — containers, a queue, a session manager. That works, but it is a second product to build and operate. The alternative is a hosted runtime that exposes browsers over CDP. Remote browser online walks through that model; the key point is that your agent code stays the same and only the connection target changes.

Connecting a web agent to a hosted browser

The connection layer for almost every web agent stack is the Chrome DevTools Protocol. Playwright, Puppeteer, Selenium, and most agent frameworks can attach to a remote Chromium instance over CDP using a WebSocket endpoint. The Chrome DevTools Protocol documentation describes the underlying domains; Playwright's `connectOverCDP` is the practical entry point for most teams.

Here is a minimal TypeScript example that connects to a hosted session, runs an agent-style observe-act loop, and cleans up:

import { chromium, type Page } from "playwright";

type Session = { cdpUrl: string; id: string };

// Your runtime client returns a CDP WebSocket URL for a new session.
async function createSession(): Promise<Session> {
  const res = await fetch(`${process.env.RUNTIME_API}/sessions`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.RUNTIME_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ profile: "checkout-agent", region: "us-east" }),
  });
  if (!res.ok) throw new Error(`session create failed: ${res.status}`);
  const data = await res.json();
  return { cdpUrl: data.cdpUrl, id: data.id };
}

async function observe(page: Page): Promise<string> {
  // Accessibility tree is usually a better agent input than raw HTML.
  const snapshot = await page.accessibility.snapshot();
  return JSON.stringify(snapshot);
}

async function act(page: Page, action: { type: string; target?: string; text?: string }) {
  switch (action.type) {
    case "click":
      await page.getByRole("button", { name: action.target }).click();
      break;
    case "type":
      await page.getByRole("textbox").fill(action.text ?? "");
      break;
    case "goto":
      await page.goto(action.target!, { waitUntil: "domcontentloaded" });
      break;
    default:
      throw new Error(`unknown action: ${action.type}`);
  }
}

async function runAgent(goal: string) {
  const session = await createSession();
  const browser = await chromium.connectOverCDP(session.cdpUrl);
  const context = browser.contexts()[0] ?? (await browser.newContext());
  const page = context.pages()[0] ?? (await context.newPage());

  try {
    await page.goto("https://example.com/orders");
    for (let step = 0; step < 25; step++) {
      const state = await observe(page);
      const action = await decideNextAction(goal, state); // your model call
      if (action.type === "done") return action;
      await act(page, action);
    }
    throw new Error("step budget exhausted");
  } finally {
    await browser.close(); // detaches; runtime reclaims the session
  }
}

Two details matter more than they look. First, browser.close() on a CDP connection detaches your client — it does not necessarily terminate the remote browser, so session lifecycle is the runtime's job. Second, the step budget is not optional. Agents loop, and an unbounded loop is a billing incident waiting to happen. Usage controls on the runtime side and a hard cap on the agent side are both worth having.

What to evaluate in a web agent runtime

When you compare runtimes — hosted providers, self-hosted Playwright, or a managed CDP endpoint — score them against the workload, not the marketing.

Session lifecycle. Can you create, inspect, and terminate sessions via API? Are sessions isolated by default? What happens to a session when your worker crashes — does it linger and bill, or get reclaimed?

Connection protocol. CDP over WebSocket is the baseline. Confirm that Playwright, Puppeteer, and Selenium all connect without patched clients, and that the endpoint survives reconnects.

Profiles and state. Persistent profiles let agents keep logins across runs. Check how profiles are scoped, whether they can be shared safely, and how to reset them. The documentation covers the profile model in detail.

Network controls. Configurable proxy and region settings matter for geo-specific tasks. Be precise about what you need: a proxy is not the same as browser fingerprint management, and vendors vary in what they actually expose.

Observability. A live viewer for active sessions and recordings for finished ones are the difference between a five-minute debug and a day of guessing.

Cost model. Metering is usually per browser-hour. Understand what counts as billable time — session creation, idle time, teardown — and check pricing for current rates rather than assuming a number from a blog post.

Concurrency limits. Every runtime has them. Know your ceiling before you design a fan-out architecture around it.

Where web agents fit in a real stack

Web agents are not a replacement for APIs. If a site offers a documented endpoint, call it — it is faster, cheaper, and more reliable than driving a browser. Web agents earn their place when:

  • The target site has no API, or the API is gated behind a partner program.
  • The task spans multiple sites and requires judgment about which source to trust.
  • The workflow changes often enough that maintaining selectors is more expensive than letting a model adapt.
  • A human would otherwise do the task by hand in a browser, and the volume justifies automation.

They are a poor fit for high-frequency, latency-sensitive operations, for anything where a wrong click has irreversible consequences without a confirmation step, and for tasks where a deterministic script already works. The honest framing is that web agents trade determinism for flexibility, and you should only pay that price when flexibility is what you need.

The bottom line

Web agents are a model plus a browser, and the browser is the part teams under-invest in. Prototypes run fine on a local Chrome. Production needs isolated sessions, persistent profiles, CDP access that works with your existing Playwright or Puppeteer code, live observability, and a cost model you can forecast. Get those right and the model has a fair chance to succeed. Get them wrong and no amount of prompt engineering will save the task success rate.

Start by connecting one agent to a hosted session over CDP, run it against a real target, and watch the session live. The failure modes you see in the first hour will tell you more about your runtime requirements than any comparison table. For the connection model in depth, read remote control browser next.