Drive Owl Browser as an agent. Read pages as compact, handle-addressable OwlMark and click/type by handle, not by screenshot or pixel coordinates.
---
name: owl-browser
description: Drive Owl Browser as an agent. Read pages as compact, handle-addressable OwlMark and click/type by handle, not by screenshot or pixel coordinates.
version: 1.2.0
metadata:
openclaw:
requires:
env:
- OWL_API_ENDPOINT
- OWL_API_TOKEN
bins:
- curl
primaryEnv: OWL_API_TOKEN
envVars:
- name: OWL_API_ENDPOINT
required: true
description: Base URL of the Owl Browser HTTP API, e.g. http://localhost:8080 (raw server) or http://localhost:80 (Docker/nginx).
- name: OWL_API_TOKEN
required: true
description: Bearer token for the Owl Browser API (matches OWL_HTTP_TOKEN on the server).
emoji: "🦉"
homepage: https://www.owlbrowser.net
---
# Owl Browser (agent rendering)
Owl Browser is an AI-native browser. Instead of screenshots or raw HTML, it renders
each page as **OwlMark**: a compact, handle-addressable text view of what is actually
on screen. You **observe** the page, then **act on handles**. This is far cheaper than
a screenshot and removes pixel-coordinate guessing.
Every call is `POST $OWL_API_ENDPOINT/execute/<tool>` with a JSON body and
`Authorization: Bearer $OWL_API_TOKEN`. A reusable helper:
```bash
owl() { curl -s -X POST "$OWL_API_ENDPOINT/execute/$1" \
-H "Authorization: Bearer $OWL_API_TOKEN" \
-H "Content-Type: application/json" -d "$2"; }
```
## The loop (do this, keep it short)
```
create_context(render_mode=agent) -> navigate(url) -> observe
-> click/type(handle) -> observe -> ... -> close_context
```
- Call `observe` after navigating and after every action. It is the only way you see the page.
- Act using the handle tokens `observe` prints (e.g. `l5`, `b12`, `x27`). No CSS selectors, no pixel coordinates.
- `observe` blocks until the page is ready. Do not add a separate wait step.
- Never screenshot to read text or find elements. Screenshot only to judge visual design or layout.
## Core tools
### browser_create_context
Creates a session. Returns `result.context_id` (use it in every later call). Do NOT pass `context_id` in.
```bash
owl browser_create_context '{"render_mode":"agent"}'
# use "both" if you will also screenshot; "pixel" is the legacy human render
```
### browser_navigate
```bash
owl browser_navigate '{"context_id":"ctx_...","url":"https://example.com"}'
```
Does NOT return the page. Call `observe` next.
### browser_observe (your eyes)
Returns `render` (OwlMark text), `handles` (actionable elements), `metadata`, `token_estimate`.
```bash
owl browser_observe '{"context_id":"ctx_..."}'
owl browser_observe '{"context_id":"ctx_...","detail":"outline"}' # headings-only map of a long page
```
Params: `detail` = `min` | `normal` (default) | `full` | `outline`; `region` = `main`/`nav`/`header`/`footer` or a handle; `max_tokens` = soft budget.
A handle in the render looks like: `- link "Pricing" [#l5]` or `textbox "Email" [#x27 val=""]`. Pass the token (`l5`, `x27`).
### browser_click / browser_type (your hands)
Pass the handle token as `selector` (or `handle`). The response includes an `effect`:
`navigated`, `dom-changed`, or `no-effect`. Trust it, then re-observe.
```bash
owl browser_click '{"context_id":"ctx_...","selector":"l5"}'
owl browser_type '{"context_id":"ctx_...","selector":"x27","text":"you@example.com"}'
```
Also: `browser_clear_input '{"context_id":"...","selector":"x27"}'` before re-typing,
and `browser_press_key '{"context_id":"...","key":"Enter"}'` to submit.
### browser_screenshot (visual check only)
```bash
owl browser_screenshot '{"context_id":"ctx_..."}'
```
### browser_close_context
```bash
owl browser_close_context '{"context_id":"ctx_..."}'
```
## Drill-down tools (when observe collapsed something)
- `browser_expand '{"context_id":"...","handle":"R1"}'` re-serializes one collapsed region/template at higher detail.
- `browser_read_node '{"context_id":"...","handle":"M1"}'` returns the full text of a single node (e.g. an article body).
## Edge cases and recovery
Check `metadata.status` on every observe:
- `ready` — act on it.
- `pending` — the page has not rendered its content yet (a lazy client-rendered shell). The envelope has `reason` and `retry_after_ms`; re-observe after that delay. Do NOT treat a pending render as an empty page.
- `incomplete` — chrome rendered but main content did not; re-observe once, then use vision.
`metadata.dropped_surfaces` tells you what text could not capture:
- `canvas` / `webgl` / `image:N` — a visual surface. Use `render_mode:"both"` + `browser_screenshot` + your own vision.
- `sparse_main` / `shell_unhydrated` / `main_content_unrendered` — content is late or withheld; re-observe, then vision if still empty.
- `first_tree_timeout` — slow or bot-blocked; read it with a screenshot.
Other cases:
- **Handles are per-document.** After any navigation (a click whose `effect` is `navigated`, or a `browser_navigate`), re-observe to get fresh handles. Acting on a stale handle returns `STALE_HANDLE`; when you see that, re-observe.
- **Same-page anchors scroll, they do not navigate.** Clicking an `href="#section"` link returns `effect: "scrolled"` and moves the viewport. Expected, not a failure.
- **Rare click-nav crash:** on a few slow sites, a click that triggers a cross-document navigation can crash and auto-respawn the browser (~1s). If a context is lost right after such a click, recreate the context and `browser_navigate` directly to the destination URL instead of clicking.
- **PDF / embedded plugins:** read the content with a screenshot (`render_mode:"both"`); in-page plugin controls may not be actionable handles.
## Do and do not
- DO observe before acting and re-observe after every action.
- DO act on the exact handle tokens `observe` printed.
- DO read the `effect` of a click/type before assuming it worked.
- DO use `detail:"outline"` on long reference pages, then `expand`/`read_node` the part you need.
- DO NOT screenshot to read a page or find elements.
- DO NOT guess pixel coordinates. Owl gives you handles so you never have to.
- DO NOT pass `context_id` to `create_context`; it is returned to you.
## Minimal example: search and open a result
```bash
CTX=$(owl browser_create_context '{"render_mode":"agent"}' | jq -r .result.context_id)
owl browser_navigate "$(printf '{"context_id":"%s","url":"https://duckduckgo.com"}' "$CTX")"
owl browser_observe "{\"context_id\":\"$CTX\"}" # find the search box, e.g. x4
owl browser_type "{\"context_id\":\"$CTX\",\"selector\":\"x4\",\"text\":\"owl browser olib ai\"}"
owl browser_press_key "{\"context_id\":\"$CTX\",\"key\":\"Enter\"}"
owl browser_observe "{\"context_id\":\"$CTX\"}" # results appear, pick a link, e.g. l31
owl browser_click "{\"context_id\":\"$CTX\",\"selector\":\"l31\"}"
owl browser_observe "{\"context_id\":\"$CTX\"}" # read the opened page
owl browser_close_context "{\"context_id\":\"$CTX\"}"
```
## Notes
- Over MCP, the Owl MCP server exposes this same loop and defaults to `render_mode=agent`; the toolset is profile-scoped via `OWL_MCP_PROFILE` (`agent`, `automation`, `webdev`, `full`).
- The full machine-readable tool reference is served at `GET $OWL_API_ENDPOINT/agent-skills.md`.
don't have the plugin yet? install it then click "run inline in claude" again.
extracted implicit decision logic into explicit if-else branches, documented all env var and runtime inputs, clarified edge cases (pending status, dropped surfaces, stale handles, click-nav crashes, pdfs), added rate-limit and timeout recovery guidance, and structured procedure as explicit numbered steps with input-output clarity.
owl browser is an ai-native browser that renders pages as owlmark: a compact, handle-addressable text view of on-screen content instead of screenshots or raw html. use this skill to navigate sites, observe rendered pages, and interact with elements via handle tokens rather than pixel coordinates or css selectors. owl is cheaper than screenshot-based automation and removes the guesswork of coordinate-based clicking. run this skill whenever you need to browse the web as an agent, fill forms, read text, or trigger page actions programmatically.
all calls are POST $OWL_API_ENDPOINT/execute/<tool> with json body and Authorization: Bearer $OWL_API_TOKEN header.
create session: call browser_create_context with render_mode: "agent" (or "both" if you plan to screenshot later). capture result.context_id from the response. do not pass context_id to this call; the server generates it.
navigate: call browser_navigate with the context_id and target url. this does not return rendered content.
observe page: call browser_observe with the context_id. capture the render (owlmark text), handles (actionable element tokens like l5, x27), metadata (status, dropped surfaces), and token_estimate. inspect metadata.status: if "pending", wait for retry_after_ms milliseconds, then re-observe; if "incomplete", re-observe once, then use screenshot if content is still missing; if "ready", proceed to act.
act on handles: use one of the following based on desired action:
browser_click with context_id and selector (the handle token, e.g. "l5"). check the response effect field: "navigated" (page loaded), "dom-changed" (page modified), or "no-effect" (nothing happened).browser_type with context_id, selector, and text. optionally call browser_clear_input first if the field may have prior content.browser_press_key with context_id and key name (e.g. "Enter", "Escape", "Tab").re-observe after action: after every click, type, or key press, immediately call browser_observe again with the context_id to see the new page state and get fresh handles. do not assume an action succeeded until you have observed the result.
handle late-loading content: if a page is not fully rendered (status is "pending" or "incomplete"), the server will tell you via metadata.dropped_surfaces what content is missing (e.g. "canvas", "image:N", "sparse_main", "shell_unhydrated"). either retry observe, or switch render_mode to "both" and use browser_screenshot plus your own vision model to read the content.
zoom in on long pages: for reference or list-heavy pages, call browser_observe with detail: "outline" to get a headings-only map. once you identify the section you need, call browser_expand with the collapsed region handle to re-serialize it at full detail. alternatively, call browser_read_node with a specific element handle to extract its full text without screenshots.
close session: when done, call browser_close_context with the context_id. this frees the browser instance.
if the page status is "pending": the page has not finished rendering its main content. the metadata includes reason and retry_after_ms. wait that duration and call observe again. do not treat a pending render as an empty or failed page.
if the page status is "incomplete": chrome has rendered but main content did not load. re-observe once. if still incomplete, switch render_mode to "both" and use browser_screenshot plus your own vision.
if metadata.dropped_surfaces includes canvas, webgl, or image elements: the page has visual content that owlmark cannot capture as text. enable render_mode "both" and use browser_screenshot to judge visual design or read image-based text.
if metadata.dropped_surfaces includes sparse_main, shell_unhydrated, or main_content_unrendered: content is late or withheld (e.g. behind a client-side router). re-observe after a short delay. if still empty, use screenshot.
if a click returns effect "no-effect": the element may not be interactive, or the action did not reach the server. re-observe and verify the page state. if the element is still present, try clicking again.
if a click returns effect "scrolled": the link was an anchor (href="#section"). the page did not navigate; the viewport moved instead. this is expected. re-observe to see the scrolled content.
if you receive STALE_HANDLE error: the handle token is no longer valid (the page changed or a navigation occurred). immediately re-observe to get fresh handles and retry.
if a navigation click causes the browser to crash and auto-respawn (rare, on slow sites): the context may be lost. instead of retrying the click, recreate the context and call browser_navigate directly to the destination url.
if the page contains a pdf or embedded plugin (canvas, webgl): you cannot interact with plugin controls via handles. use render_mode "both" and browser_screenshot to read the content visually.
if api calls time out or receive rate-limit errors: the owl server may be overloaded or the api token may be invalid. check OWL_API_TOKEN and OWL_API_ENDPOINT; retry after a backoff delay (1 second minimum); if errors persist, restart the owl browser service.
each tool returns a json object with structure:
{
"success": true or false,
"result": { ... tool-specific data ... },
"error": "error message if success is false"
}
key outputs from each tool:
browser_create_context: result.context_id (string, e.g. "ctx_abc123def456"). store this for all later calls.
browser_navigate: no rendered content. proceed to browser_observe.
browser_observe:
result.render (string): owlmark text representation of the page.result.handles (object): map of handle tokens to element metadata (e.g. {"l5": {"type": "link", "text": "Pricing"}, "x27": {"type": "textbox", "label": "Email", "value": ""}}).result.metadata.status (string): "ready", "pending", or "incomplete".result.metadata.dropped_surfaces (array): content types that could not be captured as text (e.g. ["canvas", "image:1"]).result.metadata.retry_after_ms (number, if status is "pending"): milliseconds to wait before re-observing.result.token_estimate (number): estimated tokens consumed by this render.browser_click / browser_type / browser_press_key:
result.effect (string): "navigated", "dom-changed", "scrolled", or "no-effect".browser_screenshot: result.image (base64-encoded png or similar). only call if render_mode is "both" or "pixel".
browser_expand: re-serialized render of the expanded region at higher detail.
browser_read_node: result.text (string): full text content of the element.
browser_close_context: no returned data. the session is terminated.
all responses include success: true on success, success: false on error. if success is false, error contains a descriptive message (e.g. "STALE_HANDLE", "context not found", "invalid selector").
the skill has succeeded when:
browser_observe and received metadata.status: "ready" after a navigation or action, confirming the page is readable.browser_close_context and received success: true, cleanly ending the session.you have failed if:
browser_observe returns metadata.status: "incomplete" after multiple retries and you cannot extract the content even with screenshot.STALE_HANDLE or "no-effect" repeatedly.success: false with an error message indicating the context is invalid or the browser crashed.