Skip to main content

ZeroDOM

Your agent doesn't need the DOM. It needs to know what it can click.

ZeroDOM turns a bloated HTML page into a token-optimized interaction graph — the buttons, inputs and links an agent can actually act on, and nothing else.

An agent driving a browser gets one of two action spaces today, and both are bad. Pixels are slow, expensive, and produce coordinates that go stale the moment the page scrolls. The accessibility tree is cheaper but enormous, and it has no stable handles: 102 of Hacker News' 220 actionable nodes share a (role, name) pair with another node, so there is no way to say which story to upvote.

ZeroDOM is a third option — a flat list of what the page can do, where every entry has a stable id, and the addressing information that makes it clickable never enters the context window. About 10 tokens per action, on every page tested.

  • No LLM in the loop. lxml in, graph out, 10–30ms, identical output every run.
  • Selectors never enter the context window. The model sees [03]; the CSS path stays in selector_map() on your side.
  • Nothing leaves your machine. No telemetry, no API keys, no storage — the only network traffic is the page you pointed it at.
  • The selectors actually resolve. Verified in a real browser: 231/231 on Hacker News, where rows carry no id or class and numeric ids need escaping.

Install

pip install zerodom
# or: uvx zerodom — the CLI runs straight off PyPI

One extra step only if you use the browser-backed features (from_page, --render, --screenshot, --html):

playwright install chromium

Quickstart

from zerodom import ZeroDOM

graph = ZeroDOM.from_page(page)      # any Playwright page, sync or async
print(graph.to_compact_text())       # what you send the model
selectors = graph.selector_map()     # {"node_01": "#email-input", ...} — stays your side

Real output:

PAGE: Orbit — Fleet Console | file:///…/demo/demo-page.html
[01] a 'Fleet'
[05] input 'Search'
[07] input* 'Assigned driver' ph='Search by name'
[15] button 'Dispatch'
[17] button! 'Recall (in transit)'

* marks required, ! marks disabled. The model answers click 15. You resolve node_15 against selector_map() and click it.

Cost per action

The fair comparison isn't raw HTML — nobody sends a model raw HTML. It's Playwright's page.aria_snapshot(mode="ai"), which is what Playwright MCP puts in a model's context.

page ZeroDOM ARIA
airbnb.com 9.9 23.3
github.com/…/issues 12.1 70.2
en.wikipedia.org article 11.6 41.0
news.ycombinator.com 10.2 47.0
developer.mozilla.org 9.9 46.8

tokens per actionable node — measured 2026-08-03

~10 tokens per action, flat across every page, against 23–70 and wildly variable. Mean 71.0% fewer tokens than the snapshot a model actually gets, and 93.1% against raw HTML.

MCP server

{
  "mcpServers": {
    "zerodom": {
      "command": "uvx",
      "args": ["--from", "zerodom", "zerodom-mcp"]
    }
  }
}

Goes in claude_desktop_config.json for Claude Desktop, or .cursor/mcp.json for Cursor.

tool what it does
zerodom_parse_url(url, verbose=False) navigate, return the compact graph
zerodom_read_page(verbose=False) re-read the live DOM without navigating
zerodom_find(query) return only the nodes matching a phrase
zerodom_click_node(node_id) click, then return what changed
zerodom_fill_node(node_id, text) type, then return what changed

An agent loop shouldn't re-read the page it already has. zerodom_find answers "where's the dispatch button?" in one line, and actions return a diff — + appeared, - gone, ~ value changed — instead of re-listing every node.

CLI

zerodom https://news.ycombinator.com          # compact graph + token savings
zerodom https://example.com --find checkout   # only the nodes that match
zerodom https://example.com --render          # headless Chromium, for JS-heavy pages
zerodom https://example.com --json            # full JSON graph, selectors included
zerodom https://example.com --html out.html   # graph beside an annotated screenshot

Limitations

  • Closed shadow roots are unreachable — no browser API exposes them. Open roots work.
  • Cross-origin and same-origin iframes are not traversed yet; only the top frame is parsed.
  • Canvas-rendered UIs have no DOM to read. Use a vision model there.
  • Labels come from the page, so a hostile page can write anything into one. Treat graph text as untrusted input — see SECURITY.md.
  • ZeroDOM operates on a Page you already control. It does not bypass bot detection and makes no attempt to.

Links

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zerodom-0.0.1.tar.gz (159.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zerodom-0.0.1-py3-none-any.whl (27.8 kB view details)

Uploaded Python 3

File details

Details for the file zerodom-0.0.1.tar.gz.

File metadata

  • Download URL: zerodom-0.0.1.tar.gz
  • Upload date:
  • Size: 159.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for zerodom-0.0.1.tar.gz
Algorithm Hash digest
SHA256 a84de1ddd90ccb692e6dacff57376b93771cbb212b43bef5a85ad492e6474586
MD5 f59afe5eb888f3107d6727d17da70713
BLAKE2b-256 02aa4870e27bc374c2aea8fae42c95289ec929ec12d1dbdd9ff3af3badcb3bdb

See more details on using hashes here.

Provenance

The following attestation bundles were made for zerodom-0.0.1.tar.gz:

Publisher: release.yml on DevHusnainAi/zerodom

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zerodom-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: zerodom-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 27.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for zerodom-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 39647af8622fa932bf8bc4c15c3209b002435d1dbd5559a395fe25d17cb8e9ac
MD5 92adb0e5ba204ad8e38e461c85911787
BLAKE2b-256 8f8392025b2b473397636d848986a96e4923b82e3c01e7be6b5ffcde8f7bf94a

See more details on using hashes here.

Provenance

The following attestation bundles were made for zerodom-0.0.1-py3-none-any.whl:

Publisher: release.yml on DevHusnainAi/zerodom

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page