The agent never sees the DOM
Kamapathy hands an agent a bounded text view of a page, 4,000 characters and 40 controls by default, and a fixed set of routes. That view is a product decision with a budget.
When an agent asks Kamapathy to look at a page, it does not get the DOM. Kamapathy is the desktop browser I built for a person to share with their agents, and the agent's whole view of a page is a bounded text snapshot: URL, title, visible text, then controls with short refs such as @e5, capped by default at 4,000 characters and 40 controls. The agent acts through a fixed set of routes against those refs, and nothing it writes runs inside the page. Building it taught me that what an agent sees is a product decision with a budget, and the odd calls are where the decision shows: text under aria-hidden is kept, controls under it are dropped, and a password field reports only that it holds a value.
What the agent gets
The repository keeps the real captures from its benchmark run, so I can show one. This is the form builder page of the local fixture site, from tests/fixtures/benchmarks/kamapathy-form.txt, all 535 characters of it:
Create a form
http://127.0.0.1:3000/forms/new
Kamapathy form lab
Overview
Create a form
Interaction lab
Reference page
Create a form
Form title
Description
Template
Create form
Local test fixture. Submissions remain on this machine and are discarded when the server closes.
Controls:
@e1 a "Overview"
@e2 a "Create a form"
@e3 a "Interaction lab"
@e4 a "Reference page"
@e5 input "Form title"
@e6 textarea "Description"
@e7 select "Template"
"feedback": "Product feedback"
"research": "Research request"
@e8 button "Create form"
Everything the agent needs is there: the labels, the select's exact option values and a ref for each control. The agent pipes the title into fill TAB_ID @e5 --stdin, pipes research into fill TAB_ID @e7 --stdin, and sends click TAB_ID @e8, or all three as one batch. What is missing is deliberate: no tree, no attribute dump (a control keeps type, role, state and, in JSON, a CSS selector), no scripts, no styles, no bounding boxes. It reads through open shadow roots and same-origin frames, and lists a cross-origin frame as a control that refuses every action, so the agent knows there is content it cannot read.
The budget
In electron/automation.ts a compact snapshot allows 4,000 characters of text and 40 controls, and a full one 24,000 and 100, which are also the caps. In electron/automation-dom.ts a label is cut at 80 characters in compact mode and 200 in full, a select lists at most 20 options, and a whole snapshot at most 100 options across its selects. Whatever falls outside sets a truncated flag, and omittedControls counts the controls left out, so the agent can scope the next snapshot to a CSS selector.
When a page holds more controls than the budget, which survive is a second decision, made in code: controls inside open dialogs, popups, listboxes and menus first, then controls on screen inside fixed or sticky bars, then the rest of the screen, then tall pinned columns such as a navigation sidebar, then everything else. That rule is why a snapshot of a modal shows the modal and not forty navigation links.
The budget is what the agent pays for, so I measured it. The repository keeps four full-mode captures of the fixture site, which docs/benchmark-results.json dates to 11 September 2026 on a pre-release 0.1.0 build, and scripts/benchmark.mjs counts them through js-tiktoken 1.0.21 with o200k_base, an encoding from tiktoken, "a fast BPE tokeniser for use with OpenAI's models". That JSON came from earlier drafts whose hashes match none of the committed files, so on 12 October 2026 I re-ran the count on the committed files. The form builder costs 149 tokens, the created form 248, the receipt 156 and a reference page with 18 notes 1,056: 1,609 for all four. The skill's instructions cost more: SKILL.md is 1,493 tokens, references/actions.md 2,617 and references/connection.md 842, 4,952 for the three files the script counts, and references/scripting.md, which SKILL.md sends the agent to, adds 2,247 the script never counts. The instructions for looking at a page cost at least three times what four pages cost.
None of the pages reached full mode's 24,000-character or 100-control limits, so compact mode would cost the same or less. As docs/benchmarks.md says, the numbers exclude the model's reasoning, its commands, tool schemas, screenshots, caching and latency: they measure the observation, not the task.
The odd calls
Three edge cases look wrong until you know why.
The first is aria-hidden. The attribute, as MDN puts it, "hides content from assistive technology but doesn't visually hide anything", and component libraries put visible field labels and questions inside it. So the page text includes rendered text under aria-hidden; a snapshot that followed the accessibility tree would drop the question above a group of radio buttons. Controls under aria-hidden stay excluded, because an open modal hides the page behind it that way, and an agent offered forty links from behind a dialog will click one.
The second is secrets. A field reports only that its value is hidden when it is a password input, when its name or id contains a token such as pwd, otp, cvc or pin, when its autocomplete is current-password, new-password, one-time-code or any cc- value, or when a stylesheet masks it with -webkit-text-security. Once judged secret a field stays secret, so a "show password" toggle does not leak it on the next snapshot. The security notes state the limit: a site can echo a value in ordinary text, and the snapshot does not pretend to redact that.
The third is what the snapshot is not. Playwright's aria snapshots, in its documentation, "provide a YAML representation of the accessibility tree of a page". Kamapathy's docs say its snapshots are "bounded DOM summaries, not accessibility tree exports". The snapshot is a cheap, truncated model for a language model, which would read an icon font's arrow_drop_down as a word, so those are dropped, and which has to be told a select option's exact value, so that is printed. The differences are decisions, not gaps.
Nothing the model wrote runs in the page
The other half of the boundary is what goes back. Every observation runs the same fixed function through Electron's executeJavaScriptInIsolatedWorld, which the Electron docs describe as working "like executeJavaScript but evaluates scripts in an isolated context". There is exactly one such call in the main process, in electron/automation.ts, and it runs a function defined in the source, with the operation name and the agent's arguments passed as JSON-encoded values: data in that call, never code. The docs list what is absent: no arbitrary JavaScript, no Chrome DevTools Protocol, no Node.js, no shell, no filesystem. The skill tells the model the same thing: one documented route per call, and "nothing runs inside the page".
The transport is as narrow as the view. The server listens on a Unix socket in a per-profile directory created with mode 0o700, the socket file set to 0o600 once open; on Windows it is a named pipe called kamapathy- followed by 16 random bytes, new on every start. There is no token: the operating system account is the trust boundary, and nothing running as another account can open the socket.
A view is a decision
The DOM is not the page as a person sees it, and the accessibility tree is not the page as a model needs it. Either one, handed over whole, is a bigger prompt and a larger surface that still leaves the model to find the Save button among three hundred nodes. Deciding that the agent gets 4,000 characters, 40 controls, 80-character labels, 20 options per select and nothing executable is deciding what the agent is for: filling the form in front of it, in a page the person can watch and take over. The cost is real and the docs list it: closed shadow roots, PDF documents, canvas editors and JavaScript dialogs are out of reach, and the agent hands those to the person.
Kamapathy is at version 0.2.0 on Electron 44 and React 19, MIT licensed, an early preview with unsigned installers (read on 12 October 2026). The code, fixtures and benchmark script are in the repository on GitHub. If you build a tool for agents, write down the budget first. The model will see whatever you give it, and the moment you can quote the snapshot in full, as I did above, you have decided what it is allowed to know.
Get new posts by email
Occasional essays on engineering, AI, and building for the people technology leaves behind.
Subscribe with RSS