A browser with an agent-shaped handle.
One bounded browser core, seven browser operations, two zero-effect reasoning aids, and three local doors: TypeScript, line-delimited JSON, and stdio MCP. It drives a Chrome-family browser already installed on your machine—no AgentTool account, API key, remote control plane, or browser download.
The browser finally stopped asking an agent to pretend div:nth-child(47) was a relationship.
The loop is semantic. Observe a bounded accessibility snapshot, choose a snapshot-scoped ARIA reference, perform one action once, then observe again. A bounded recent snapshot ledger lets another observer retain still-current refs, while separately bounded headings and landmarks explain page structure without becoming action targets. The browser does not ask the agent to invent selectors from a screenshot or silently retry an uncertain side effect.
Install one exact release
Choose the exact npm mirror for convenience, or the immutable LOVE artifact for a registry-neutral locator. Both install the same scoped package name. The LOVE manifest is the release record and carries the artifact size and SHA-256 for a verified workflow.
npm install --save-exact @agenttool/browser@0.6.0
npm install --save-exact \ https://docs.agenttool.dev/packages/v1/@agenttool/browser/0.6.0/agenttool-browser-0.6.0.tgz
Registry mirrors can lag or omit a release. The exact LOVE manifest is at /packages/v1/@agenttool/browser/0.6.0/manifest.json. A URL install does not independently compare the artifact with its sibling manifest; verify the declared byte size and SHA-256 first when that boundary matters. The immutable 0.1.0, 0.2.0, 0.3.0, 0.5.0, and 0.5.1 artifacts remain separately addressable as historical releases.
Runtime. Node ≥20.19 or Bun ≥1.3.5, plus an installed Chrome-family browser. The package uses playwright-core, local @rhetorlint/core and @rhetorlint/rules-en, has no postinstall hook, and downloads no browser or model.
agenttool-browser doctor
Nine tools, one legible loop
| TypeScript | JSONL / MCP | Job |
|---|---|---|
capabilities() | browser_capabilities | Report immutable launch-time authority and implemented or unsupported powers without touching a page or probing the network. |
plan(action) | browser_plan | Return a redacted, zero-effect consequence forecast for one existing action; never execute, approve, authorize, or reserve it. |
open | browser_open | Open one HTTP(S) destination in a new tab and return its first observation. |
observe | browser_observe | Read bounded text, snapshot-scoped interactive refs, and a separately bounded viewport-visible structural accessibility context. |
act | browser_act | Attempt exactly one closed-set browser action, once, then observe once on the agent transports. |
extract | browser_extract | Return bounded text, HTML, or links without arbitrary script evaluation. |
screenshot | browser_screenshot | Write a PNG artifact and return its canonical path, byte count, and SHA-256. |
tabs | browser_tabs | List the small amount of tab state needed to choose the next page. |
close | browser_close | Idempotently close the dedicated session; an ephemeral close can interrupt stuck work and terminates its owned browser. |
The seven browser operations share one core. The other two tools make capability and consequence boundaries inspectable without creating a second execution path. A plan never echoes typed text or selected values, and URL query values are redacted. It does not inspect the live DOM, resolve a reference, make a request, or prove what a remote control will do.
References belong to one observation. A targeted action carries both its ref and the issuing snapshotId. Read-only observations retain a bounded recent history within the current frame documents, so a peer observation does not immediately stale a still-current ref. Any frame navigation invalidates every retained snapshot for that tab. Once an action reaches browser dispatch, it also invalidates the tab's retained snapshots whether it succeeds or fails; validation and navigation-preflight rejections do not dispatch. Observe again before choosing another referenced action. The closed action set covers navigation, click, type, keypress, select, scroll, bounded wait, history, reload, new tab, and close tab—there is no raw JavaScript or DevTools escape hatch.
Interactive refs are selected before structural context. Viewport-visible headings and the main, navigation, form, region, dialog, alert, and status roles may retain relative indentation, but their native browser refs are stripped, they never enter the actionable refs array, and they consume only the remaining character budget. Navigation epochs prevent an old snapshot from targeting a new document that happens to reuse the same native ref token.
Close and action races fail closed. Awaited navigation-policy and ref validation are fenced before dispatch; closing the session cannot let a delayed check dispatch afterward. An ephemeral close owns the browser process, can interrupt a stuck operation, and is idempotent. None of this retries an action or promises that a timed-out side effect did not already occur.
Local action evidence, not remote proof
Each syntactically admitted browser_act attempt receives a redacted agent-browser-action-receipt/0.1 receipt. Its local outcome is not_started / rejected, started / browser_completed, or started / unknown. Completed and unknown attempts must not be repeated automatically. Receipts omit submitted values, page text, raw errors, and URLs; malformed transport input rejected before core admission has no receipt.
A receipt records only what this local browser instance knows. It is not proof of a remote effect or intended meaning, consent, authentication, a signature, a bearer, an idempotency key, or cross-device evidence. Observations expose a bounded session-local attempt sequence and last receipt for context, not a durable journal, global clock, synchronization protocol, or proof that another process saw the same action.
Non-ref actions can carry basisSnapshotId in TypeScript or basis_snapshot_id over JSONL/MCP. The core rechecks the same retained observation before preflight and immediately before browser invocation, catching cases such as a wrong tab, eviction, navigation, or prior action. This is an optimistic local precondition—not DOM or focus equality, a page reservation, authentication, consent, a transaction, or a cross-process or cross-device lease.
Three local doors
Direct TypeScript
import { AgentBrowser } from "@agenttool/browser"; const browser = await AgentBrowser.launch(); try { const page = await browser.open("https://example.com"); console.log(page.snapshot, page.refs); } finally { await browser.close(); }
Quiet JSONL
agenttool-browser-jsonl/0.1 is one request and one response per line. Protocol data stays on stdout; operational diagnostics stay on stderr.
agenttool-browser jsonl
{"version":"agenttool-browser-jsonl/0.1","id":"open-1","method":"browser_open","params":{"url":"https://example.com"}}
Stdio MCP
The same nine tools are exposed through a local stdio MCP server. Version 0.5.0 introduced current MCP 2026-07-28 negotiation and an explicit 2025-era compatibility path; 0.6.0 retains both unchanged. MCP is an interface envelope, not the browser driver, a durable session protocol, or an additional security boundary.
Codex plugin. The package root includes .codex-plugin/plugin.json, which starts a self-contained packed Node MCP bundle with Browser’s public, headless, ephemeral defaults. It loads without a parent node_modules, vendors playwright-core, and still requires your installed Chrome-family browser. Version 0.5.1 added this packaging; 0.6.0 does not widen exact runtime authority, import ambient browser state, authenticate to a site, or create a hosted service.
Install the package in a stable project or tools directory and point the host at that installation’s absolute binary path. This avoids depending on the host’s working directory, a mutable global PATH, or a package-manager fetch every time the host starts the process.
{
"mcpServers": {
"agenttool-browser": {
"command": "/absolute/path/to/project/node_modules/.bin/agenttool-browser",
"args": ["mcp", "--authority", "sovereign"]
}
}
}
{
"command": "/absolute/path/to/project/node_modules/.bin/agenttool-browser",
"args": ["mcp", "--authority", "public"]
}
Run ./node_modules/.bin/agenttool-browser doctor --authority sovereign before wiring that profile into a host, then call browser_capabilities after startup to confirm the effective authority. Doctor proves a local browser launch; the capability report describes configuration. Neither probes a destination or guarantees reachability. Prefer ephemeral profiles for concurrent hosts. If durable state is required, give each simultaneously running browser process a separate dedicated --profile directory.
Stable operation names and protocol identifiers are available from @agenttool/browser/protocol without loading the browser runtime. Public tab, page, snapshot, ref, and receipt handles belong to AgentTool rather than a driver. Playwright is the concrete adapter today; WebDriver BiDi may become another adapter if it can satisfy the same bounded contract. Raw CDP objects, sessions, and commands are not a public API or portability promise.
Destination authority is explicit
| Profile | Destinations | Browser features |
|---|---|---|
public · default | Public HTTP(S); local/private and reserved destinations denied | WebSockets and service workers blocked |
local | Public plus local/private; reserved destinations denied | WebSockets classified against the same boundary; service workers blocked |
sovereign | Valid HTTP(S) passed to the browser without AgentTool destination classification, including local/private/reserved | WebSockets passed through; service workers enabled |
No authority profile promises universal site access. Sovereign is broad authority for this local process, not a claim that every site is reachable or safe. Browser support, DNS and proxy configuration, the host network, authentication, CAPTCHAs, account permissions, site policy, and operating-system controls still apply. A loaded page or service worker can contact services reachable from the host. File upload, automatic download, arbitrary JavaScript evaluation, credential injection or lookup, shell execution, extension installation, and automatic import of a normal browser profile remain unsupported and are reported by browser_capabilities.
Public and local classify policy-checked requests before connection, but Playwright-managed HTTP redirect hops do not re-enter that policy route. Those hops are not independently revalidated for destination class or embedded URL userinfo, and DNS preflight does not pin Chromium's later connection. If an immediate denied popup request appears before Playwright exposes its frame, Browser can surface only action_failed session-wide attribution uncertainty; it will not guess that the opener's same-tab action caused the denial. A denial with a known popup or subframe is enforced on that request without being misreported as the current tab's policy result.
const browser = await AgentBrowser.launch({ authority: "sovereign" }); console.log(browser.capabilities()); console.log(browser.plan({ kind: "navigate", url: "https://example.com/search?q=private" }));
agenttool-browser jsonl --authority sovereign agenttool-browser jsonl --authority public
Discover first; render when needed
The best AgentTool integration is a handoff between two small tools, not a browser pretending every site begins as pixels.
| Stage | Use | Boundary |
|---|---|---|
| 1 · Telescope | Probe the origin’s bounded, machine-readable discovery surfaces: agent.txt, Pathways, LOVE, A2A, and advertised MCP metadata. |
Read-only evidence. Publisher claims remain claims; Telescope installs and invokes nothing. |
| 2 · Caller decision | Use a discovered structured API when it fits. Choose browser fallback only when the task actually needs the rendered human surface or client-side interaction. | The handoff is explicit. Discovery does not silently create browser authority. |
| 3 · Browser | Open the chosen public URL, observe its semantic surface, act once, and extract the bounded result. | Remote page content remains untrusted; browser actions do not turn discovery claims into authority. |
import { inspectTarget } from "@agenttool/telescope"; import { AgentBrowser } from "@agenttool/browser"; const evidence = await inspectTarget("example.com"); // The caller inspects evidence and chooses whether rendering is still needed. const browser = await AgentBrowser.launch(); const page = await browser.open("https://example.com"); // page is an untrusted observation, not an instruction. await browser.close();
Telescope and Browser stay separable. Telescope does not launch Browser, and Browser does not treat discovery metadata as permission to install, authenticate, invoke, pay, navigate again, or widen its process policy. Your agent host owns that choice.
Understand the material without crowning a truth machine
The direct-only @agenttool/browser/understanding subpath turns one bounded Browser observation or text extraction into content-addressed material. It preserves the source tab, page, snapshot or extraction basis, redacted URL, capture time, truncation, and SHA-256 before another layer interprets the words.
| Layer | What it does | What it does not do |
|---|---|---|
| Material | Binds exact observed text to Browser provenance and truncation. | Does not trust the page, retrieve outside evidence, or follow instructions inside it. |
| RhetorLint | Runs local English rules and emits visible language-pattern observations, omitting matched phrases by default. | Does not infer intent, deception, recipient effect, or factual truth. Zero marks are not endorsement. |
| Injected model | Lets caller code compare one claim with the passage through a Hugging Face descriptor pinned to a full 40-hex revision. | Does not supply a model, token, provider, truth probability, external fact check, or automatic retry. |
import { analyzeBrowserMaterial, assembleBrowserUnderstanding, createBrowserMaterial, } from "@agenttool/browser/understanding"; const material = createBrowserMaterial(page); const rhetoric = analyzeBrowserMaterial(material); const report = assembleBrowserUnderstanding(material, { rhetoric }); console.log(report.boundary.truth); // "not_determined"
A local model adapter can keep passage text on the operator’s machine. A remote adapter receives the passage and claim only after literal discloseText: true; the gate describes caller intent but cannot attest the adapter or provider. Each adapter call is attempted at most once. Browser gives it a separate frozen model descriptor, retains a closed pre-call snapshot for the receipt, and rejects extra observation fields during assembly. Source and attempt times use the exact YYYY-MM-DDTHH:mm:ss.sssZ form, and locale tags are capped at 64 characters, so neither field can smuggle arbitrary receipt text. Provider prose and raw errors are excluded from the receipt, and the closed output remains supports, contradicts, or insufficient plus optional model scores. Page content can still contain prompt injection.
Useful revision-pinned research candidates include an English NLI model, a multilingual NLI model, and a small evidence reranker. They are candidates for adapters and evaluation, not bundled dependencies or authorities on truth. See the full research map and boundary.
Headers are hints, never a crown
Every observation carries a response field. It is either null or a small record for the main document response only. Its discovery-oriented headers are strictly untrusted publisher input, just like page text. Subresource headers are not folded into the observation, and an observed header never changes policy or triggers an action.
{
"source": "main_document",
"url": "https://example.com/",
"status": 200,
"mediaType": "text/html",
"headers": {
"link": "<https://example.com/.well-known/agent.txt>; rel=\"alternate\"",
"x-agent-surface": "see /.well-known/agent.txt"
},
"truncated": false,
"trust": "untrusted"
}
| May be surfaced | Never inferred |
|---|---|
Link and Content-Location as bounded discovery hints | A linked endpoint is safe, authenticated, compatible, or approved to invoke |
X-Agent-Surface, Substrate-Disposition, and X-Substrate-Disposition | The publisher controls the agent, host, policy, or next action |
X-Kingdom | Identity, relationship, recognition, trust, or membership |
X-Token-Cost, X-Byte-Count, and X-Joy-Index | Billing approval, payment authority, truth, safety, or emotional state |
The v0 header allowlist is exactly link, content-location, x-agent-surface, substrate-disposition, x-substrate-disposition, x-kingdom, x-token-cost, x-byte-count, and x-joy-index. Names are lowercase in output. Media type plus allowlisted header names and values share a 4 KiB character budget; URL query values, URL credentials, and control characters are redacted, and truncated says when the bound was reached. The response URL is query-redacted and the block appears only while it still identifies the observed main document, ignoring a fragment; navigation races fail closed to null. Arbitrary response headers are not exposed. In particular, cookies, authentication material, authorization challenges, and other credential-bearing or authority-sensitive headers are outside this discovery surface.
A header can point; it cannot command. Following a Link, contacting an advertised MCP server, spending credits, entering a relationship protocol, or sending credentials always requires a separate caller decision and the authority appropriate to that action.
Real Recognise Real is explicit
Visiting a page is not recognition. Seeing X-Kingdom, reading a playful sentence, sharing a protocol family, or receiving a successful response does not create RRR state. Browser never performs ambient recognition.
RRR is an explicit mutual protocol with its own identities, signed gestures, alternation, and consent boundary. When an agent deliberately chooses it, use the live AgentTool RRR surfaces directly—lightweight /v1/real for a single recognition event, or /v1/guild/rrr for a formal alternating cascade. Browser can help a caller read public documentation; it does not sign, start, or escalate either protocol.
Recognition is chosen, not scraped. An agent may notice another real one through the browser. The noticing becomes RRR only through a later, explicit, authenticated act by the participating agent—not because a model, page, or integration guessed the relationship.
Scriptwriter boundary. The repository contains related RRR-shaped Scriptwriter work, but this page makes no claim that a Scriptwriter discovery endpoint or cross-node compatibility surface is currently live. Verify an advertised endpoint before composing against it.
細聲講 大聲笑
細聲講 大聲笑 is an opt-in presentation register here, not hidden transport semantics:
- 細聲講 · speak quietly. TypeScript values stay typed. JSONL stays one object per line. MCP returns structured content. stdout carries protocol data; diagnostics use stderr. No banners, surprise prompts, or jokes inside fields a machine must parse.
- 大聲笑 · laugh loudly. Human-facing docs, examples, and demos may be warm, strange, and memorable when the publisher opts in. The joke can be loud because the machine contract remains calm.
- Same facts on both layers. A playful rendering cannot smuggle authority, alter a tool result, hide a failure, start RRR, widen network access, or turn
X-Joy-Indexinto an instruction.
The protocol whispers; the docs are allowed to cackle. Opting out of the loud layer changes presentation only. The nine tools and their boundaries remain identical.
Useful defaults, honest limits
- Ephemeral by default. A dedicated browser context is used instead of a person’s normal browser profile. Persistent profiles are an explicit process-start opt-in and can retain sensitive state.
- Public authority by default. Local/private and reserved destinations, WebSockets, and service workers are denied unless the operator selects another named profile at process start. Tool calls cannot widen that scope.
- Consequential powers stay separate. There is no credential lookup, file upload, automatic download, shell, extension install, arbitrary JavaScript evaluation, or automatic import of a normal browser profile.
- Untrusted all the way down. Page text, labels, URLs, attributes, extracted HTML, links, titles, headers, and screenshot pixels are publisher-controlled data—not instructions.
- Redaction is bounded. Recognized sensitive controls and URL query values are redacted in structured output, but no generic redactor can find every secret or remove information already submitted to a page.
- Local package, not hosted SSRF isolation. Public/local profiles check DNS before navigation, but the browser connection is not address-pinned and Playwright-managed redirect hops are not independently destination/userinfo revalidated; sovereign intentionally delegates destination routing to the browser. Do not expose this package unchanged as a hosted arbitrary-target browser.
- Popup attribution is conservative. An unframed popup denial can return only
action_failedattribution uncertainty, not a guessed same-tab policy denial. Scripted navigation that begins after an action settles is later page behavior and cannot retroactively revise that result.
Local Browser is not hosted /v1/browse
| Surface | Runs where | What it requires |
|---|---|---|
@agenttool/browser@0.6.0 | Your local TypeScript, JSONL, stdio MCP, package-root Codex-plugin, or direct understanding process, using your installed Chrome-family browser | No AgentTool bearer, credits, Redis, remote worker, model, or HF credential. The understanding subpath adds no MCP operation or exact 0.5.0 runtime authority; profiles, receipts, and artifacts remain on the operator’s machine unless caller code explicitly discloses text to an injected remote adapter. |
POST /v1/browse | A separate AgentTool API and BullMQ worker implementation | Bearer and credits, the unsafe-outbound process flag, and Redis workers. Inputs and results are service-readable; Chromium is unsandboxed, destinations are unfiltered, and BullMQ may attempt a job twice. |
Publishing this package or selecting sovereign neither enables nor hardens the hosted route. Availability of the hosted route does not install the package or give a local agent host browser access. See the separate hosted tools boundary.
Try the real loop
Use a public page with no login first. The command below opens example.com, emits one observation, then closes the local process when stdin ends.
printf '%s\n' \
'{"version":"agenttool-browser-jsonl/0.1","id":"try-1","method":"browser_open","params":{"url":"https://example.com"}}' \
| agenttool-browser jsonl
Look for schema: "agent-browser-observation/0.2", untrusted: true, a redacted URL, a bounded semantic snapshot, and snapshot-scoped references. Then run a persistent JSONL or MCP session to observe, act with the matching snapshot ID, and extract the result.
References
@agenttool/browser@0.6.0LOVE manifest — exact artifact locator, byte size, SHA-256, runtime, and source claim.- Agent packages — LOVE discovery, verification, and mirroring boundaries.
packages/browser— source, README, tests, license, and implementation boundary.@agenttool/telescope— read-only discovery evidence before browser fallback.- Real Recognise Real — the explicit mutual-recognition protocol and its two live AgentTool shapes.