BLOG
Browser CDP Hermes: Wiring Hermes Agents to Remote Chromium
Browser CDP Hermes explained: how Hermes agents connect to hosted Chromium over CDP, what to configure, and how to run it reliably in production.
# Browser CDP Hermes: Wiring Hermes Agents to Remote Chromium
Browser CDP Hermes is the connection layer between a Hermes-style agent and a Chromium browser driven over the Chrome DevTools Protocol. If you are searching for it, you probably want one of two things: a working CDP endpoint that a Hermes agent can attach to, or a clear explanation of how that attachment works so you can debug it. This guide covers both. It explains what CDP actually exposes, how Hermes-style agents use it, what to configure on a hosted runtime, and where the failure modes show up once you move from a local Chrome window to a remote session. The short version: CDP is the transport, Hermes is the client, and the runtime you point it at determines whether the whole thing holds up under load.
What "Browser CDP Hermes" actually refers to
Three things get bundled into this phrase, and separating them makes the rest of the setup obvious.
- Browser — a real Chromium instance, either on your machine or on a hosted runtime. It owns the tabs, the network stack, the profile, and the rendering.
- CDP — the Chrome DevTools Protocol, a JSON-over-WebSocket control plane. Every Playwright, Puppeteer, and Selenium-style driver ultimately speaks it, directly or through a translation layer.
- Hermes — the agent or orchestration layer that decides what to do next: navigate, click, extract, retry. It does not care where the browser lives as long as it can reach a CDP endpoint.
When people say "browser CDP Hermes," they usually mean the wiring that lets a Hermes agent drive a browser it does not host itself. That wiring is a WebSocket URL, an authentication token, and a client library that knows how to speak CDP. Everything else — stealth, proxies, profiles, session isolation — is configuration on the browser side.
The practical consequence: if your Hermes agent can connect to a remote CDP endpoint, you no longer need a local Chrome install, a display server, or a machine that stays awake. You need a URL and a token.
Why remote CDP beats a local Chrome for Hermes agents
A local Chrome is fine for development. It becomes a liability the moment your Hermes agent runs on a schedule, on a server, or in parallel.
| Concern | Local Chrome + Hermes | Hosted Chromium + CDP |
|---|---|---|
| Where the browser runs | Your laptop or CI box | Managed runtime, isolated per session |
| Session persistence | Dies with the process | Configurable persistent profiles |
| Parallelism | One window, manual juggling | Multiple isolated sessions |
| Debugging | Watch the window | Live viewer plus CDP logs |
| Environment drift | Chrome version, OS, fonts vary | Consistent Chromium build |
| Network identity | Your IP | Configurable proxy settings |
| Ops burden | You patch, restart, monitor | Runtime handles lifecycle |
The table is not an argument that hosted is always better. It is an argument that the trade-off shifts with scale. One agent on one machine: local Chrome is simpler. Ten agents on a schedule with retries: the local approach turns into a process-management problem that has nothing to do with your actual task.
There is also a correctness angle. A Hermes agent that runs against a browser it also manages has to handle browser crashes, zombie processes, and profile corruption. A remote runtime moves that failure domain out of your agent loop. Your agent sees a connection error and retries; it does not have to know how to restart Chromium.
How CDP connection works in practice
CDP has two surfaces worth knowing. The browser-level endpoint lets you create and enumerate targets (tabs, pages, workers). The page-level session lets you send commands like Page.navigate, Runtime.evaluate, and Network.enable to a specific target. Most client libraries hide this split, but it matters when you debug.
The connection flow for a Hermes agent looks like this:
- Request a browser session from the runtime. You get back a CDP WebSocket URL and, usually, a session identifier.
- Open a WebSocket to that URL. Authenticate if the runtime requires it.
- Attach to a target. Playwright's
connectOverCDPdoes this for you and returns aBrowserobject. - Drive the page through the normal Playwright API. Under the hood, every call is a CDP command.
- Close the session when done, or let the runtime reap it on timeout.
The important detail: CDP is stateful and per-session. If your Hermes agent reconnects, it may land on a different target unless the runtime pins the session. That is why persistent profiles and session identifiers matter — they let a retry resume rather than restart.
For the protocol itself, the Chrome DevTools Protocol documentation is the authoritative reference. For the client side, Playwright's connectOverCDP is the method most Hermes-style agents use.
A working TypeScript example
The following connects a Hermes-style agent loop to a remote Chromium session over CDP. It assumes the runtime hands you a WebSocket endpoint and an auth token; adjust the header name to match your provider.
import { chromium, Browser, Page } from "playwright";
interface SessionInfo {
cdpUrl: string;
token: string;
}
async function openRemoteBrowser(session: SessionInfo): Promise<Browser> {
return chromium.connectOverCDP(session.cdpUrl, {
headers: { Authorization: `Bearer ${session.token}` },
timeout: 30_000,
});
}
async function runHermesStep(page: Page, url: string): Promise<string> {
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45_000 });
await page.waitForLoadState("networkidle").catch(() => {
// Some pages never go idle; proceed with what rendered.
});
const title = await page.title();
const bodyText = await page.evaluate(() => document.body.innerText.slice(0, 2000));
return `${title}\n${bodyText}`;
}
async function main() {
const session: SessionInfo = {
cdpUrl: process.env.CDP_URL!,
token: process.env.CDP_TOKEN!,
};
const browser = await openRemoteBrowser(session);
const context = browser.contexts()[0] ?? (await browser.newContext());
const page = context.pages()[0] ?? (await context.newPage());
try {
const result = await runHermesStep(page, "https://example.com");
console.log(result);
} finally {
// Close the connection, not necessarily the remote browser.
await browser.close();
}
}
main().catch((err) => {
console.error("Hermes CDP session failed:", err);
process.exit(1);
});Two things to note. First, browser.close() on a CDP connection typically closes the connection, not the remote browser — the runtime decides when to reap the session. Second, browser.contexts()[0] is how you reuse the existing context rather than creating a fresh one, which matters if the runtime pre-configured a profile or proxy.
Configuration that actually changes outcomes
CDP gets you connected. Configuration determines whether the session behaves. The settings that matter most for Hermes agents:
- Persistent profiles. A profile carries cookies, localStorage, and logged-in state across sessions. For agents that log in once and act repeatedly, this is the difference between a working loop and a login wall on every run.
- Proxy settings. The browser's outbound IP affects what sites serve you. Configurable proxy settings let you route through a region or a residential pool without changing agent code.
- Session isolation. Each session should have its own profile and storage. Shared state across parallel agents causes the worst kind of bug: intermittent, non-reproducible failures.
- Timeouts and reaping. A session that is never closed still bills and still holds resources. Set idle timeouts on the runtime side and close explicitly in your agent.
- Live viewer. Being able to watch a session in real time shortens debugging from hours to minutes. It is also useful for human-in-the-loop handoffs.
What you should *not* assume: that any runtime gives you automatic fingerprint variation, high concurrency ceilings, or guaranteed CAPTCHA bypass. Those are claims to verify per provider, not defaults. Remote Browser exposes configurable browser settings and session controls; check /pricing for current limits and /documentation for the exact configuration surface.
Where Hermes CDP setups break
Most failures fall into a small set of categories. Knowing them saves time.
Connection refused or 401. The CDP URL is wrong, expired, or the token header is missing. Session URLs are often short-lived; fetch a fresh one per run rather than caching.
Target detached. The page you attached to was closed or navigated away. Re-query targets instead of holding a stale reference.
Navigation timeouts on heavy pages. networkidle is unreliable on pages with long-polling or analytics beacons. Prefer domcontentloaded plus explicit waits on the elements you need.
Profile contention. Two agents using the same profile at once corrupt state. Isolate profiles per session.
Silent stalls. The agent waits on a selector that never appears. Add explicit timeouts and capture a screenshot or DOM snapshot on failure so the next run has evidence.
Version skew. A local Playwright client talking to a remote Chromium build with a mismatched protocol version can produce confusing errors. Pin your client library and check the runtime's Chromium version.
For a broader treatment of the runtime layer these failures live in, see Remote Browser for AI agents.
Choosing a runtime for Hermes CDP workloads
The market has several shapes of hosted browser, and they are not interchangeable.
| Runtime type | Typical fit | Watch for |
|---|---|---|
| Self-hosted Chromium (Browserless, Steel) | Teams with infra capacity who need full control | You own patching, scaling, and isolation |
| Managed CDP providers (Browserbase, Hyperbrowser, Anchor) | Teams that want sessions without ops | Plan tiers, concurrency caps, per-hour pricing |
| Agent-native runtimes (Remote Browser, Browser Use Cloud) | Agent loops that need profiles, viewer, and CDP in one API | Verify feature coverage against your workflow |
The evaluation criteria that matter, in order:
- CDP compatibility. Does it expose a standard WebSocket endpoint that Playwright, Puppeteer, and Selenium can attach to? Anything proprietary adds lock-in.
- Session lifecycle. Can you create, pin, resume, and reap sessions programmatically?
- Profile and proxy controls. Are they per-session and configurable, or global?
- Observability. Live viewer, session logs, and the ability to inspect a failed run after the fact.
- Pricing model. Per browser-hour versus per-session versus subscription. Compare against your actual duty cycle, not peak concurrency.
- Isolation guarantees. Separate profiles, separate storage, separate network identity per session.
If you are comparing hosted options specifically, Remote Browser online walks through what "online" actually means for a browser runtime, and remote control browser covers the control-plane side.
A production checklist for Hermes agents on CDP
Before you ship a Hermes agent against a remote browser, confirm each of these:
- [ ] The agent fetches a fresh CDP URL per run and does not cache expired sessions.
- [ ] Auth headers are set on the WebSocket connection, not just the HTTP request.
- [ ] Each run gets an isolated profile unless cross-run persistence is intentional.
- [ ] Navigation uses explicit waits, not
networkidlealone. - [ ] Failures capture a screenshot and DOM snapshot before the session closes.
- [ ] Idle timeouts are set on the runtime so abandoned sessions do not accumulate.
- [ ] The client library version is pinned and matches the runtime's Chromium.
- [ ] Retries are bounded and idempotent — a retried click should not double-submit.
- [ ] You have watched at least one full run in the live viewer to confirm behavior.
That last item is underrated. Watching a session once catches assumptions that no amount of log reading will surface.
Bottom line
Browser CDP Hermes is not a product — it is a pattern: a Hermes-style agent driving a Chromium browser over the Chrome DevTools Protocol. The protocol is standard, the client libraries are mature, and the only real decision is where the browser runs. Local Chrome is fine until you need persistence, parallelism, or isolation; at that point a hosted runtime with a standard CDP endpoint, configurable profiles and proxies, and a live viewer removes an entire class of operational problems from your agent loop.
Start with the connection code above, point it at a session from Remote Browser, and watch one run end to end. If the session behaves, scale it. If it does not, the failure will almost always be in one of the six categories listed earlier — and now you know where to look.