← Blog

BLOG

Hermes Agent Browser Install: Setup and CDP Wiring

Hermes agent browser install explained: what the Hermes browser extension does, how to wire it to Playwright or Puppeteer over CDP, and when to use a hosted runtime.

September 17, 202611 min readRemote Browser

# Hermes Agent Browser Install: Setup and CDP Wiring

A Hermes agent browser install is not a single package you add to node_modules. Hermes is an agent framework that drives a browser through a tool layer, and the "install" step is really about giving that tool layer a browser it can reach. You have two paths: attach Hermes to a Chrome instance running on your machine, or point it at a hosted Chromium session that exposes a CDP endpoint. This guide covers both, with the Playwright and Puppeteer wiring you need, the failure modes that show up in production, and the criteria for choosing between them.

If you already know you want a remote runtime and just need the connection string, skip to Connect Hermes to a Hosted Browser. If you are still deciding, the comparison table below is the short version.

What "Hermes Agent Browser Install" Actually Means

Hermes is a tool-calling agent. It does not ship its own rendering engine. When a Hermes task needs to click, type, scroll, or read a page, it calls a browser tool, and that tool needs a live browser process with a debugging port open. So the install question decomposes into three parts:

  1. The agent side. Hermes itself, plus whatever model provider and tool definitions you have configured.
  2. The browser side. A Chromium binary that is running, reachable, and not already claimed by another process.
  3. The transport. The protocol that connects them. In practice this is almost always the Chrome DevTools Protocol (CDP), usually through Playwright's connectOverCDP or Puppeteer's connect.

Most "install" tutorials collapse these into one step and hand you a shell script. That works until you need a second concurrent agent, a persistent login, or a browser that survives your laptop sleeping. Then the transport layer becomes the thing you actually care about.

The Hermes browser extension is not the browser

There is a common confusion worth clearing up early. A Hermes browser extension, where one exists, is a control surface. It lets you trigger agent runs from a browser tab or inspect what the agent is doing. It is not the execution environment. The agent still needs a browser it can drive programmatically, and extensions generally cannot expose a CDP endpoint that Playwright can attach to.

If your goal is "run Hermes against a real browser," you want a CDP endpoint, not an extension. If your goal is "watch Hermes work from my own Chrome window," the extension is a convenience layer on top of that same endpoint.

Prerequisites Before You Install Anything

Pin these down first. Skipping them is the source of most setup failures.

  • Node.js 18 or newer. Playwright and Puppeteer both require it. Check with node -v.
  • A Chromium build you control. Either a local Chrome/Chromium install or a hosted session. Version drift between the agent's expectations and the browser's actual version causes subtle selector and API failures.
  • A CDP endpoint. Locally this is http://127.0.0.1:9222. Remotely it is a wss:// URL your provider gives you.
  • Network egress rules. If you are behind a corporate proxy, the agent host must be able to reach the CDP endpoint. This is the single most common cause of "connection refused" in managed environments.
  • A decision on profile persistence. Do you need the agent to stay logged in across runs? If yes, you need a persistent profile, and that changes which runtime you should pick.

Option A: Local Chrome with a Debugging Port

The fastest path. Start Chrome with remote debugging enabled, then attach.

# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 \
  --user-data-dir=/tmp/hermes-profile

# Linux
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/hermes-profile

Verify the endpoint is live before touching agent code:

curl -s http://127.0.0.1:9222/json/version | head -20

You should see a JSON blob with Browser, Protocol-Version, and webSocketDebuggerUrl. If you get a connection error, Chrome either did not start with the flag or another process already holds port 9222.

Wiring Hermes to local Chrome with Playwright

import { chromium, Browser, BrowserContext, Page } from 'playwright';

async function attachLocalChrome(): Promise<{ browser: Browser; context: BrowserContext; page: Page }> {
  // connectOverCDP attaches to an already-running browser.
  // It does NOT launch one, and it does NOT apply launchOptions.
  const browser = await chromium.connectOverCDP('http://127.0.0.1:9222');

  // A CDP-attached browser usually has one default context already.
  const context = browser.contexts()[0] ?? (await browser.newContext());
  const page = context.pages()[0] ?? (await context.newPage());

  return { browser, context, page };
}

(async () => {
  const { page, browser } = await attachLocalChrome();
  await page.goto('https://example.com');
  console.log(await page.title());
  // Do NOT call browser.close() here unless you want to kill the user's Chrome.
  await browser.close();
})();

Two things trip people up here. First, connectOverCDP ignores launchOptions entirely, because nothing is being launched. If you need --disable-blink-features=AutomationControlled or a specific user agent, you must pass those flags when you start Chrome, not in the Playwright call. Second, browser.close() on a CDP-attached browser closes the browser, not just your connection. In a long-running agent you usually want to leave it open and drop the reference.

Wiring Hermes to local Chrome with Puppeteer

import puppeteer from 'puppeteer';

const browser = await puppeteer.connect({
  browserURL: 'http://127.0.0.1:9222',
  defaultViewport: null,
});
const page = await browser.newPage();
await page.goto('https://example.com');
// browser.disconnect() detaches without killing Chrome.
browser.disconnect();

Note the difference: Puppeteer gives you disconnect() for a clean detach. Playwright does not have an exact equivalent, which is why the Playwright pattern above avoids close().

Where local setup breaks down

  • One browser, one agent. A single Chrome instance with one debugging port is a shared resource. Two Hermes runs will fight over tabs.
  • State is fragile. Close the terminal, the browser dies. Sleep the laptop, the session drops.
  • No isolation. Cookies, localStorage, and service workers from one task leak into the next.
  • Scaling is manual. Ten concurrent agents means ten Chrome processes, ten ports, and ten profiles you have to manage.

For development and single-task debugging, local is fine. For anything that runs on a schedule or serves multiple users, it is a liability.

Option B: Hosted Chromium via CDP

A hosted runtime gives you a Chromium session that already has a CDP endpoint open. You do not install a browser, you do not manage ports, and you do not worry about whether the machine running the agent is the same machine running the browser. This is the model Remote Browser uses, and it is the same model behind most production browser-agent stacks.

The install step shrinks to: get a connection URL, pass it to connectOverCDP.

import { chromium } from 'playwright';

const CDP_URL = process.env.REMOTE_BROWSER_CDP_URL!; // wss://... from your session

const browser = await chromium.connectOverCDP(CDP_URL);
const context = browser.contexts()[0];
const page = await context.newPage();

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());

await browser.close(); // safe here: the session is yours, not a shared desktop Chrome

That is the whole integration. Everything else, session lifecycle, profile persistence, proxy configuration, live viewing, is handled on the runtime side.

What you get that local Chrome does not

  • Session isolation. Each agent run gets its own browser context. No cookie bleed between tasks.
  • Persistent profiles. Log in once, reuse the authenticated state across runs. Useful for agents that operate behind a login.
  • Live viewer. You can watch the session in a browser tab while the agent runs, which cuts debugging time dramatically compared to reading logs.
  • Configurable browser settings. User agent, viewport, locale, timezone, and proxy settings are set at session creation rather than patched in after launch.
  • Usage controls. Sessions are metered, so you can see what a workload costs. Current rates are on /pricing.

Local vs Hosted: Decision Table

CriterionLocal Chrome + debug portHosted Chromium via CDP
Setup timeMinutesMinutes, but no binary management
Concurrent agentsManual, one port eachSession-per-agent, isolated
Profile persistenceManual --user-data-dir jugglingBuilt into session config
Survives laptop sleepNoYes
Live debuggingChrome window on your desktopWeb viewer, shareable
Proxy / geo controlManual flagsConfigured per session
Cost modelYour hardwareMetered per browser-hour, see /pricing
Best forLocal dev, single-task debuggingProduction agents, scheduled runs, multi-tenant

The honest read: local wins on latency and zero cost for a single developer poking at one task. Hosted wins the moment you have more than one agent, a login to maintain, or a schedule to keep.

Production Criteria for a Hermes Browser Runtime

If you are choosing a runtime rather than just wiring one up, these are the questions that actually predict whether it holds up.

Does it expose raw CDP? Some platforms give you a proprietary API and nothing else. That locks you out of Playwright, Puppeteer, and any tooling built on CDP. You want a wss:// endpoint you can hand to connectOverCDP directly. The Chrome DevTools Protocol documentation is the reference for what that endpoint can do.

How are sessions isolated? Separate browser contexts at minimum, separate browser processes ideally. Shared contexts mean one agent's cookies can affect another's results.

Can profiles persist? For agents that log in, this is non-negotiable. Re-authenticating on every run is slow and often triggers rate limits or bot detection.

Is there a live view? Logs tell you what the agent thought happened. A viewer tells you what actually happened. When a selector fails intermittently, the viewer is the difference between a five-minute fix and an afternoon of guessing.

What is the failure mode? Does a dropped connection kill the session, or can you reconnect? Does the session survive a deploy of your agent code? These matter more than any benchmark number.

How is usage metered? Per browser-hour is the common model. Understand whether idle time counts, and check /pricing for the specifics rather than assuming.

Common Install Failures and Fixes

`connectOverCDP` throws "Protocol error" or hangs. Usually a version mismatch between the Playwright client and the remote Chromium. Playwright's CDP support targets Chromium specifically; connecting to Firefox or WebKit over CDP is not supported. Check the Playwright CDP docs for the current constraints.

`launchOptions` seem to be ignored. They are. connectOverCDP does not launch anything. Move those flags to the browser start command or to session creation on the hosted side.

Agent works locally, fails in CI. Almost always network. The CI runner cannot reach 127.0.0.1:9222 because there is no Chrome there. Either run Chrome in the CI job or switch to a hosted endpoint.

Sessions die mid-task. Check whether the agent code is calling browser.close() on a shared instance, or whether an idle timeout is reaping the session. Hosted runtimes usually have an idle policy; local Chrome does not.

Login state disappears between runs. You are using an ephemeral context. Switch to a persistent profile, or persist storage state explicitly with context.storageState({ path }).

When to Move Off Local

The migration trigger is usually one of these:

  • You need more than two concurrent agents.
  • A task requires an authenticated session that must survive restarts.
  • You are running on a schedule and cannot guarantee a desktop is awake.
  • You need to debug a failure that only reproduces in a clean environment.
  • You want to hand a session URL to a teammate so they can watch.

At that point the local install is not buying you anything except latency, and you are paying for it in operational overhead. The remote browser for AI agents overview covers the runtime model in more depth, and remote browser online walks through the no-local-install path if you want to try it before committing.

A Minimal End-to-End Hermes Setup

Putting it together, a working Hermes browser tool looks like this:

  1. Create a hosted session and capture the CDP URL.
  2. Store the URL in an environment variable, not in code.
  3. In your Hermes browser tool, call chromium.connectOverCDP(process.env.REMOTE_BROWSER_CDP_URL).
  4. Reuse the existing context rather than creating a new one, so the session's profile and settings apply.
  5. Close the browser at the end of the task so the session is released and metering stops.
  6. Log the session ID alongside your task ID so you can correlate failures with sessions later.

That is the entire install. There is no binary to download, no port to open, no --user-data-dir to clean up. The complexity that used to live in your setup script moves to the runtime, where it is someone else's problem.

If you want to see the connection model in action before writing code, the remote web browser walkthrough shows a session being created and driven from a browser tab. And if you are wiring this into a larger control loop, remote control browser covers the patterns for keeping an agent and a human in the same session.

The short version: Hermes needs a browser with a CDP endpoint. Local Chrome gives you one for free and costs you isolation, persistence, and scale. A hosted runtime gives you all three and costs you a metered session. Pick based on how many agents you plan to run and whether they need to stay logged in.