← Blog

BLOG

Hermes Remote Browser Login: Connect Agents to Hosted Chromium

Hermes remote browser login explained: how to authenticate, wire CDP, and run Hermes agents against a hosted Chromium runtime instead of local Chrome.

September 30, 20269 min readRemote Browser

# Hermes Remote Browser Login: Connect Agents to Hosted Chromium

A Hermes remote browser login is the step where your Hermes agent stops driving a local Chrome instance and starts driving a hosted Chromium session over the Chrome DevTools Protocol (CDP). If you are searching for this, you probably already have Hermes installed, a browser skill configured, and a task that fails the moment it leaves your laptop. The login is not a username and password screen inside Hermes. It is the credential and endpoint handshake that lets Hermes attach to a browser running somewhere else.

This guide covers what that handshake actually is, how to wire it with Playwright's connectOverCDP, what breaks in practice, and when a hosted runtime is the right call versus keeping Chrome local.

What "Hermes remote browser login" actually means

Hermes is an agent framework that can call tools, including a browser tool. That browser tool needs a target. Locally, the target is a Chrome process Hermes launches itself. Remotely, the target is a WebSocket endpoint exposed by a browser runtime, plus an API key or session token that authorizes the connection.

So "login" here is shorthand for three distinct things:

  • Runtime authentication — your API key or session token proves you are allowed to create a browser session.
  • Session creation — the runtime spins up an isolated Chromium instance and returns a connection URL.
  • CDP attach — Hermes (usually through Playwright or Puppeteer) connects to that URL and takes control.

If any of those three fail, you get the classic symptoms: connect ECONNREFUSED, a WebSocket that opens then closes, or a browser that loads about:blank and never navigates. Most "Hermes remote browser not working" reports are one of these three, not a Hermes bug.

Why local Chrome stops working for Hermes agents

Local Chrome is fine for a demo. It breaks down for predictable reasons:

  • Session state leaks. Cookies, localStorage, and extensions from your personal browsing bleed into agent runs, which makes results non-reproducible.
  • Headless detection. A default local headless Chrome is easy to fingerprint. Sites that gate automation will block it.
  • No isolation. Two concurrent agent runs sharing one Chrome profile will fight over tabs, cookies, and focus.
  • No remote observability. When a run fails at step 40, you have no live view and no replay unless you built it yourself.
  • Machine coupling. The agent can only run where Chrome runs. That kills serverless and containerized deployment.

A hosted runtime addresses all five by moving Chromium off your machine and giving each session its own isolated context. For a deeper treatment of that architecture, see Remote Browser for AI agents.

The connection model: API key plus CDP endpoint

The flow looks like this:

  1. Your code authenticates to the runtime with an API key.
  2. The runtime returns a session object containing a CDP WebSocket URL.
  3. Hermes' browser layer connects to that URL.
  4. Hermes issues CDP commands (navigate, click, type, screenshot) through the connection.
  5. When the task ends, you close the session so it stops billing.

The important design point: the API key never goes into the browser. It authorizes session creation on your backend. The CDP URL is what the agent uses. Keep them separate, and never ship the API key to a client-side Hermes build.

Wiring Hermes to a hosted browser with Playwright

Most Hermes browser skills sit on top of Playwright. The connection call is chromium.connectOverCDP(). Here is a TypeScript example that creates a session, attaches, and runs a task:

import { chromium, Browser, BrowserContext } from "playwright";

const RUNTIME_API = "https://api.remote-browser.dev";
const API_KEY = process.env.REMOTE_BROWSER_API_KEY!;

interface SessionResponse {
  id: string;
  cdpUrl: string;
}

async function createSession(): Promise<SessionResponse> {
  const res = await fetch(`${RUNTIME_API}/sessions`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${API_KEY}`,
    },
    body: JSON.stringify({
      // Keep sessions short-lived for agent tasks.
      timeoutSeconds: 300,
      // Persistent profiles let a logged-in agent resume later.
      profile: "hermes-agent-default",
    }),
  });

  if (!res.ok) {
    throw new Error(`Session create failed: ${res.status} ${await res.text()}`);
  }
  return (await res.json()) as SessionResponse;
}

async function runHermesTask(task: (ctx: BrowserContext) => Promise<void>) {
  const session = await createSession();
  let browser: Browser | undefined;

  try {
    browser = await chromium.connectOverCDP(session.cdpUrl);
    // connectOverCDP reuses the default context of the remote browser.
    const context = browser.contexts()[0] ?? (await browser.newContext());
    await task(context);
  } finally {
    // Closing the CDP connection does not always end the remote session.
    await browser?.close();
    await fetch(`${RUNTIME_API}/sessions/${session.id}`, {
      method: "DELETE",
      headers: { Authorization: `Bearer ${API_KEY}` },
    });
  }
}

runHermesTask(async (ctx) => {
  const page = await ctx.newPage();
  await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
  console.log(await page.title());
});

Two details that trip people up:

  • connectOverCDP returns a Browser whose contexts already exist. Calling newContext() on a CDP-connected browser behaves differently than on a locally launched one. Reuse browser.contexts()[0] unless you have a reason not to.
  • Closing the Playwright browser object does not guarantee the remote session is terminated. Always call the runtime's session-delete endpoint, or you will keep paying for an idle browser.

For the underlying protocol details, the Chrome DevTools Protocol documentation is the authoritative reference for the commands Playwright sends under the hood.

Local Chrome vs hosted Chromium for Hermes

CriterionLocal ChromeHosted Chromium runtime
SetupInstall Chrome, manage versionsCreate session via API
IsolationShared profile, leaks stateOne session per task
ConcurrencyLimited by your machineBounded by your plan, not hardware
Headless detectionDefault fingerprint, easily flaggedConfigurable browser settings and proxy routing
ObservabilityManual screenshotsLive viewer plus session logs
DeploymentTied to the machine running ChromeRuns from any backend or worker
Cost modelYour hardware and timeMetered per browser-hour
Persistent loginLocal profile filesManaged persistent profiles

The trade-off is real: hosted runtimes cost money per session and add a network hop. Local Chrome is free and lower latency. The decision usually comes down to whether you need isolation, scale, or reproducibility. If you need any of the three, local Chrome becomes technical debt fast.

Handling login flows inside the agent

There is a second meaning of "login" worth separating out: the agent logging into a target website. This is where persistent profiles matter.

A persistent profile stores cookies and storage state on the runtime side, keyed to a profile name. Your Hermes agent authenticates once — either by driving the login form or by loading a saved storage state — and subsequent sessions reuse it. That avoids re-running a login flow on every task, which is both slow and a common source of CAPTCHA triggers.

Practical rules:

  • Do not hardcode credentials in the agent prompt. Pass them as environment variables or a secrets store the runtime can read.
  • Prefer storage state over form replay when the site supports it. Fewer steps means fewer failure points.
  • Rotate profiles per tenant if you run multi-tenant agents. Sharing a profile across customers is a data-isolation bug.
  • Expect session expiry. Build a re-auth branch into the agent rather than assuming the profile stays valid forever.

If your login flow keeps failing, the failure is usually upstream of Hermes — see Hermes remote browser not working for the diagnostic checklist.

Production criteria before you commit

Before you standardize on any hosted runtime for Hermes, check these:

  • CDP compatibility. Confirm it exposes a standard CDP WebSocket, not a proprietary protocol. Playwright and Puppeteer should attach without a shim.
  • Session lifecycle controls. You need explicit create and delete, plus a timeout so orphaned sessions self-terminate.
  • Profile persistence. Verify storage state survives across sessions and is scoped per profile.
  • Proxy and network controls. Check whether you can route traffic through specific regions and whether proxy configuration is available.
  • Live debugging. A viewer that lets you watch a running session is worth more than any log file when an agent stalls.
  • Usage visibility. You should be able to see browser-hours consumed per session. Current rates and limits live on the pricing page.
  • Isolation guarantees. Each session should be its own browser context, not a shared tab pool.

If a runtime cannot answer these, it is a demo tool, not infrastructure.

Where Hermes fits in a larger agent stack

Hermes is one framework among several that need a browser runtime. The same CDP endpoint works for browser-use, Puppeteer scripts, Selenium, and custom agents. That is the argument for treating the browser as a separate runtime layer rather than a library you import — see Remote web browser for how that separation plays out across frameworks.

The practical benefit: when you swap Hermes for another framework, or run both side by side, the browser layer does not change. Your session creation code, profile names, and proxy config stay put. Only the agent logic moves.

Common failure modes and fixes

WebSocket connects then immediately closes. Usually an auth failure on the session token, or the session timed out before the agent attached. Check that you are passing the CDP URL, not the REST API base URL.

Agent sees `about:blank` and stalls. The agent is navigating in a new context that the remote browser did not expect. Reuse browser.contexts()[0].

Login works locally, fails remotely. The remote browser has a different IP and a clean profile. Sites that trusted your local session will challenge a fresh remote one. Use a persistent profile and a proxy in the right region.

Sessions pile up and bill. You closed the Playwright browser but never deleted the session. Add the delete call to a finally block, as in the example above.

Concurrency errors under load. You hit your plan's session cap. Check the current limits on pricing rather than assuming there is no ceiling on concurrent sessions.

Summary

A Hermes remote browser login is a three-part handshake: authenticate to the runtime, create a session, attach over CDP. Get those right and Hermes stops caring whether Chromium runs on your laptop or in a data center. Get them wrong and you get connection errors that look like Hermes bugs but are really lifecycle bugs.

Start by moving one non-trivial Hermes task to a hosted session. Watch it in the live viewer, confirm the session deletes cleanly, and check your browser-hour usage. If the task succeeds and the session terminates, you have a working remote login. If it does not, the failure is almost always in the session lifecycle, not the agent. For the full API surface, see the documentation.