My product's homepage carries 105,866 bytes of HTML in its <body> once the browser has built it.
An AI agent got 81 lines of it. Each line is a role, a name in quotes, and a short ref like e29 to act on. No colors, no layout, no hero animation.
That is what agent-browser hands a model instead of a screenshot, and it changed what I think "making a site work for agents" means. It mostly means the accessibility work we were already supposed to be doing.
A browser driven one shell command at a time
agent-browser is a Rust CLI from Vercel Labs, Apache-2.0. It was #6 on GitHub trending on November 10 with 775 stars that day, and the GitHub API showed 49,056 stars when I checked that morning. Version 0.42.0 shipped the day before.
The model works in a loop: open a page, take a snapshot, act on a ref, snapshot again. A background daemon starts on the first command and keeps Chrome open over the DevTools Protocol, so every step is one short call:
npm install -g agent-browser agent-browser open example.com agent-browser snapshot -i # - link "Learn more" [ref=e1] agent-browser click @e1 The snapshot is Chrome's accessibility tree, trimmed. The -i flag keeps the interactive elements plus named headings and landmarks; the full tree adds the text. The 0.42.0 changelog says the bundled skill now tells agents to prefer snapshots and take a screenshot only when asked, or when the DOM can't represent what's on screen. So the default view of your site is built from the tree Chrome builds for screen readers.
What my two sites look like to it
I ran the binary from the 0.42.0 release (its SHA-256 matched the release digest) in a throwaway folder, pointed at my installed Chrome instead of letting it download Chrome for Testing. Then I opened my own sites.
| Body HTML | Full snapshot | snapshot -i | |
|---|---|---|---|
| somiibo.com | 105,866 bytes | 10,431 bytes | 3,425 bytes, 81 refs |
| ianwiedenman.com | 35,557 bytes | 1,861 bytes | 900 bytes, 28 lines |
That is roughly a thirtieth of the markup for the interactive view, which is the real pitch for an agent: fewer tokens per step, and a handle it can act on without guessing a CSS selector.
Both sites passed the test I cared about. Every button and link had a name. The nav reads button "Platforms", link "Pricing", button "Download". The platform cards read as one link each, with the card's whole text as the name. The 13 project logos on my personal site came through as link "Somiibo", link "Slapform" and so on, because each <img> carries alt text.
Two small blemishes showed up. The whitespace beside an inline SVG icon survives into the name (link " Download free", button "Language "). And the agent sees a heading written as "My projects" as "MY PROJECTS", because CSS uppercases it.
I clicked Pricing by its ref. The click returned in 0.03 seconds and get url said /pricing. The snapshot I took right after said (no interactive elements): the new page hadn't finished loading. After wait --load networkidle it had 109 refs. The README says to snapshot again after navigation, and the 0.42.0 skill tells agents to wait and retry when an expected element is missing. Now I know why.
Then I wrote the page I was afraid of
Two clean pages tell you little about the failures. I wrote a nine-line checkout page with mistakes that are common on real sites, and took the same snapshot:
- heading "Checkout" [level=1, ref=e111] - button "Add to cart" [ref=e112] - button [ref=e113] - generic "Pay now" [ref=e114] clickable [onclick] - link [ref=e115] - textbox "Email" [ref=e116] - textbox "Email" [ref=e117] Line by line:
- An icon button with an
aria-labelreads "Add to cart". The same button without one readsbutton, and nothing else. A model can click it. It can't know what it is without a screenshot. - A cart icon linked as
<a><img></a>with no alt text readslink. Same problem. - A
<div onclick>that says "Pay now" got a ref anyway. agent-browser runs its own scan for things that look clickable (pointer cursor,onclick,tabindex, editable) and adds them asclickable. That's a patch over the page's semantics. - A
<span>with onlycursor: pointer, labeled "Apply coupon", didn't make it. The text shows up in the full tree, but it has no ref. - A
<details>toggle, "Shipping options", got no ref either. That one is a known bug, issue #2294:<summary>shows as a disclosure triangle with no handle. - An input with only a placeholder and one with a real label read the same,
textbox "Email".
The audit and the agent disagree
agent-browser bundles axe-core, so I ran agent-browser a11y on the same page. It found 7 violations, and the two critical ones were exactly the unnamed button and the image link. Good overlap.
The differences are the interesting part. Axe raised nothing about the <div onclick>, which a keyboard user can't reach, while the agent got a ref for it through the cursor scan. Axe raised nothing about the <summary>, a native HTML control, while the agent had no ref for it. A clean audit doesn't mean a clean agent view, and the reverse holds too.
Here's my claim. The accessible name is the interface. A button's name is what a screen reader says, what Playwright's getByRole matches, and now what a model reads before it decides where to click. agent-browser's heuristics will paper over some gaps, but each patch has an edge: issue #2246 points out that its clickable scan doesn't look inside shadow roots, so a <div onclick> in a web component gets nothing. Semantic HTML has no such edge. If you expect agents to book, buy or file a ticket on your site, the cheapest step isn't an "agent API". It's a name on every control.
Rough edges I hit
-
opentimed out after 25 seconds on both of my sites while waiting for the load event. The page was usable anyway and the snapshots worked. I'd guess a slow third-party script, but I didn't trace it. - My first command failed: the session socket path would have been 174 bytes, past the 103-byte limit the error named, because my scratch folder sits deep in
/private/tmp. The error said so and namedAGENT_BROWSER_SOCKET_DIRas the fix, which worked. - The npm package unpacks to 133 MB by npm's own count. My install crawled, so I stopped it once the macOS binary had landed and checked that file against the release digest.
- Refs are numbered per session and never reused, so mine started at
e83on the second site. That's by design (the docs say ids are never recycled within a session), but it reads oddly in a transcript. - Open issues and PRs stand at 558. One filed on November 10, #2540, reports the daemon hanging on every command under 0.42.0 on Linux. I didn't hit it on macOS.
Try it on your own page
The no-install route is npx agent-browser open <your-url>, then snapshot -i. agent-browser install fetches Chrome for Testing, or point AGENT_BROWSER_EXECUTABLE_PATH at a Chrome you already have, as I did. Read the output as if it were all you knew about your site. Every bare button or link is a control a model has to guess at.
When you ran it, what was the first control on your page that came back with no name?