Agent-first lightweight browser automation — direct CDP, no Playwright, more accurate and more reliable.
Overview · Highlights · Core Technology · Features · Quick Start · Contents
English · 中文
📌 Overview
Octop Browser is a lightweight browser-use style tool that makes an agent's browser usage more accurate and reliable. Unlike typical stacks that route every action through Playwright, it launches a real Chromium and talks to the Chrome DevTools Protocol (CDP) directly — removing the intermediate layer so element targeting is driven by stable, live DOM refs and rarely misses.
Under the hood it starts a Chromium process with a --remote-debugging-port and speaks CDP itself. Because element references come straight from the live page, the agent acts on the real nodes, and those refs stay valid through reflows and re-renders — the result is higher action accuracy and lower token usage, with a persistent profile so logins don't expire mid-task.
Octop Browser's design goal: give an agent a browser it can act on with confidence — accurate clicks, low token cost, and persistent logins — through a small, well-shaped set of tools and a matching CLI.
Note: the install-browser command may bootstrap Playwright once purely to download a Chromium binary. Playwright is not a runtime dependency — the agent always drives the browser over CDP.
✨ Highlights
| Feature | Description | |
|---|---|---|
| ⚡ | Direct CDP | Connects straight to Chrome via CDP — no Playwright / intermediate layer |
| 🎯 | Ref-based targeting | Stable element refs survive layout reflows, so clicks land where intended |
| 🪶 | Lightweight DOM | Token-efficient multi-level DOM keeps prompts small |
| 🔐 | Auth persistence | Profile-based logins persist across sessions — no repeated sign-in |
| 🛠️ | Agent tools | One stateless browser_tool with ~20 actions |
| 💻 | CLI | Every action available as a first-class command |
| 🎬 | Record & replay | Capture a workflow, then replay it as skill-guided agentic execution |
| 🤖 | MCP server | Expose the browser to any MCP-capable agent |
🧠 Core Technology
| Layer | Technology |
|---|---|
| Language | Python 3.11+ |
| Transport | CDP over websockets (hand-rolled async client) |
| Launcher | subprocess Chromium with --remote-debugging-port |
| DOM | Multi-level builder + stable ref system |
| Tools | Stateless browser_tool action set |
| Recording | Injected JS recorder + semantic collapse + skill generator |
| Interfaces | CLI + MCP server |
| Build / quality | hatchling · ruff · mypy · pytest |
🤔 Features
Browser tools
browser_tool(action=...) exposes the following actions:
| Action | Description | Action | Description |
|---|---|---|---|
navigate |
Navigate the active tab | select |
Pick a <select> option |
dom_tree |
Dump the multi-level DOM | scroll |
Scroll the viewport |
screenshot |
Capture a screenshot | hover |
Hover an element |
click |
Click by ref | eval_js |
Run JavaScript |
type |
Type text by ref | go_back / go_forward |
History nav |
fill |
Fill a field by ref | reload |
Reload the page |
press |
Press a key | new_tab / close_tab |
Tab control |
wait |
Wait for a condition | switch_tab / list_tabs |
Tab management |
close_session |
Close the CDP connection (pass kill=True to also stop Chrome) |
CDP session
BrowserSession.create(profile=...)opens a persistent Chromium session.- Stateless helper:
browser_tool(action="navigate", url=..., profile="work"). - Set
BROWSER_USE_IDLE_TIMEOUT_MINUTESto stop a local Chrome profile after that many minutes without real CDP activity (0, the default, disables it). await session.close(kill=True)orbrowser_tool(action="close_session", profile="work", kill=True)stops the local Chrome process immediately. Profile data and login state stay on disk.
CLI
Every tool is also a CLI command:
octop-browser install-browser # fetch a Chromium binary (one-time)
octop-browser navigate "https://example.com" --profile work
octop-browser dom-tree --profile work
octop-browser click --ref inp_1 --profile work
octop-browser type "octop" --ref inp_1 --profile work
octop-browser screenshot --profile work
octop-browser close-session --kill --profile work # also stop Chrome
# session: open / close-session / new-tab / switch-tab / close-tab / list-tabs
Record & replay
Octop Browser can record a real browsing session and replay it:
octop-browser record daemon-start— launch the long-lived recording daemon.octop-browser record start— begin capturing the active tab.- Browse normally. A small injected script captures clicks, typed text, navigations, and submits, with privacy redaction of sensitive fields.
octop-browser record stop— stop capturing.octop-browser record generate-steps <id>— inspect the semantic steps;record generate-skill <id>emits a draft Skill (draft.skill.md).octop-browser replay run <id>— re-execute as skill-guided agentic execution (the model replays intent, not brittle coordinates/refs).
Use record list / record show <id> / record status / record doctor to manage recordings.
MCP server
octop-browser ships an MCP server, so any MCP-capable agent can drive the browser through the same tool set.
python -m octop_browser.mcp_server # or: make mcp
It speaks MCP over stdio, which is the transport most agent clients expect.
Over MCP each action is exposed as its own tool named after it — browser_navigate,
browser_click, browser_dom_tree, browser_close_session, browser_record_start,
install_browser, … — rather than as a single dispatching tool. Arguments match the
Python API, so browser_close_session also takes kill to terminate Chrome.
Agent skills
The skills/ directory ships ready-made Skill files for agents that support
skill loading — skills/octop-browser/SKILL.md (English) and
skills/octop-browser-zh/SKILL.md (Chinese). They encode the safe usage
pattern: run dom-tree before every click, pass refs instead of
coordinates, and reuse named profiles for login state. record generate-skill
emits a draft Skill in the same format.
🚀 Quick Start
Prerequisites
- Python 3.11+ — verified on 3.11 and 3.12. Higher-level applications built on top of it (e.g. Octop) may require a newer Python; follow their own docs.
- A Chromium / Chrome binary (auto-downloaded by
install-browser)
1. Install
pip install octop-browser
octop-browser install-browser # fetch a Chromium binary once
2. Use as a library
from octop_browser import BrowserSession
async with await BrowserSession.create(profile="default") as session:
await session.navigate("https://example.com")
dom = await session.dom_tree()
await session.click(ref="btn_1")
Or statelessly:
from octop_browser import browser_tool
await browser_tool(action="navigate", url="https://example.com", profile="work")
3. Use as a CLI
octop-browser navigate "https://example.com" --profile work
octop-browser dom-tree --profile work
4. Record a workflow
octop-browser record daemon-start
octop-browser record start
# ... interact with the page ...
octop-browser record stop
octop-browser record generate-skill <recording_id> # emit a draft Skill
octop-browser replay run <recording_id> # replay it
📑 Contents
- Overview
- Highlights
- Core Technology
- Features
- Quick Start
- Reference
- Project Info
📖 CLI reference
| Command | Description |
|---|---|
install-browser |
Download a Chromium binary (one-time bootstrap) |
navigate |
Navigate the active tab to a URL |
open |
Start a session and open one or more URLs as separate tabs |
dom-tree |
Print the multi-level DOM |
screenshot |
Capture a screenshot |
click / type / fill / press |
Interact by ref |
wait / select / scroll / hover |
Page control |
eval-js |
Run JavaScript |
go-back / go-forward / reload |
History / reload |
new-tab / switch-tab / close-tab / list-tabs |
Tab management |
close-session |
Detach from a profile's CDP session. Chrome keeps running so a later command re-attaches; add --kill to terminate it and free its port |
record ... |
doctor, daemon-start, daemon-stop, status, start, stop, list, show, generate-steps, generate-skill |
replay run <id> |
Replay a recorded workflow |
Record & replay options
| Option | Values | Purpose |
|---|---|---|
record start --privacy |
none · mask-sensitive (default) · mask-all |
Redact typed values before they are written to disk. mask-sensitive masks fields that look sensitive (password, token, secret, otp, jwt, … matched against the field's type / name / id / placeholder / label); mask-all masks every typed value |
replay run --input key=value |
repeatable | Supply a value for a {{placeholder}} step at replay time |
Redacted values are stored as the placeholder {{sensitive_value}}, so a recording that
captured a login can still be replayed safely — keep the secret in the environment and
pass it in at replay time:
octop-browser replay run <recording_id> --input sensitive_value="$MY_PASSWORD"
DOM levels
dom-tree (and the dom_tree action) takes --level to trade detail for tokens:
| Level | Cost | Contents |
|---|---|---|
minimal |
~50 tokens | Title / URL only |
interactive (default) |
~200–500 | Clickable / typeable elements with refs |
full |
~1k–3k | Full readable page |
structured |
varies | JSON, for programmatic use |
octop-browser dom-tree --level minimal # cheap page check
octop-browser dom-tree --level structured # JSON for scripts
🐍 Python API
Exports
Everything below is importable from the package root: from octop_browser import ...
| Symbol | Kind | Purpose |
|---|---|---|
BrowserSession |
class | Persistent CDP session — await BrowserSession.create(profile="work") |
browser_tool |
async fn | Stateless one-shot action — await browser_tool(action="click", ref="btn_1") |
ToolResult |
model | Return value of every session method and browser_tool() |
ActionMetrics |
model | Per-action timing / token counters |
TabInfo |
model | One tab: tab_id · url · title · active |
OctopSettings |
model | Full settings object — pass via BrowserSession.create(settings=...) |
settings |
instance | Process-wide default, built from the environment at import time |
BrowserMode |
type | Literal["auto", "headed", "headless"] |
chromium_executable |
fn | Path of the managed Chromium binary, or None if absent |
ensure_chromium |
fn | Blocking install — returns True once a binary is ready |
install_chromium_stream |
async gen | Same install as an event stream, for progress UIs |
InstallEvent |
TypedDict | One install event: a log line, or terminal done / success / error |
Tool results
Every session method and browser_tool() call returns a ToolResult:
| Field | Type | Notes |
|---|---|---|
success |
bool |
False means the action failed — inspect error |
content |
str | dict |
Main payload: DOM text, screenshot path, JS return value, … |
error |
str | None |
Failure reason; None on success |
metrics |
ActionMetrics |
action · duration_ms · dom_nodes_scanned · estimated_tokens · screenshot_size_kb |
metadata |
dict | None |
Optional side-channel — e.g. screenshot reports page url / title here |
res = await session.dom_tree(level="interactive")
if res.success:
print(res.content, res.metrics.estimated_tokens)
session.metrics_summary() aggregates running totals for the whole session.
Hooks
BrowserSession mixes in HooksMixin, so callbacks can observe every action:
@session.on("after_action")
async def log_metrics(metrics): # metrics: ActionMetrics
print(metrics.action, metrics.duration_ms)
| Event | Payload | Fired |
|---|---|---|
before_action |
dict — {"action": str, "params": dict} |
Before each action is dispatched |
after_action |
ActionMetrics |
After an action succeeds |
action_error |
ToolResult |
After an action returns success=False |
page_navigated |
— | Reserved: accepted by on(), not emitted yet |
Callbacks may be sync or async, and an exception inside a hook is logged without breaking the action.
⚙️ Configuration
Every setting is read from the environment at import time. You can also build a
OctopSettings object explicitly and hand it to BrowserSession.create():
from octop_browser import BrowserSession, OctopSettings
settings = OctopSettings(cdp_port_start=9300, cdp_timeout=60.0)
session = await BrowserSession.create(profile="work", settings=settings)
| Variable | Default | Purpose |
|---|---|---|
BROWSER_USE_MODE |
auto |
Launch mode: auto / headed / headless (auto picks headed on macOS/Windows or when DISPLAY is set) |
BROWSER_USE_CDP_WS_URL |
(unset) | Connect straight to an existing Chrome DevTools WebSocket URL — use this for remote / Docker Chrome and skip the launcher |
BROWSER_USE_CHROME_BIN |
auto-detect | Absolute path to the Chrome / Chromium binary |
BROWSER_USE_PROFILES_DIR |
~/.octop-browser/profiles |
Base directory for Chrome user-data-dirs |
BROWSER_USE_SCREENSHOTS_DIR |
~/.octop-browser/screenshots |
Where the screenshot action writes PNGs |
BROWSER_USE_CDP_HOST |
localhost |
Host serving Chrome's CDP endpoint |
BROWSER_USE_CDP_PORT_START |
9222 |
First CDP debug port assigned to profiles |
BROWSER_USE_CDP_TIMEOUT |
30.0 |
Seconds to wait for a CDP command response |
BROWSER_USE_VIEWPORT_WIDTH |
1440 |
Viewport width in CSS pixels |
BROWSER_USE_VIEWPORT_HEIGHT |
900 |
Viewport height in CSS pixels |
BROWSER_USE_IDLE_TIMEOUT_MINUTES |
0 (off) |
Stop a locally managed Chrome after N minutes without real CDP activity |
BROWSER_USE_LAUNCH_RETRIES |
20 |
How many times to poll Chrome after launch |
BROWSER_USE_LAUNCH_DELAY |
0.25 |
Seconds between launch polls |
BROWSER_USE_CDP_MAX_MESSAGE_SIZE |
33554432 (32 MiB) |
Max CDP WebSocket frame size; 0 disables the cap (raise it for very large screenshots) |
BROWSER_USE_NO_SANDBOX |
auto-detect | Force (1) or forbid (0) --no-sandbox. When unset, it is added only if the kernel denies unprivileged user namespaces (typical in containers) |
BROWSER_USE_DISABLE_GPU |
auto (on in headless) | Force (1) / forbid (0) --disable-gpu. Defaults to on in headless mode, off in headed mode |
BROWSER_USE_DISABLE_DEV_SHM_USAGE |
auto (on in Linux) | Force (1) / forbid (0) --disable-dev-shm-usage. Defaults to on on Linux, where Docker's 64 MiB /dev/shm is too small for Chrome |
🛠️ Development
Prerequisites: Python 3.11+, uv
make install # uv sync --extra dev (falls back to pip install -e ".[dev]")
make all # format + lint + typecheck + test
🤝 Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Run
make allbefore submitting - Open a Pull Request against
main
See CONTRIBUTING.md for branching, PR, and release details (release/* → main auto-publishes to PyPI).
Found a vulnerability? Please follow the private disclosure process in SECURITY.md instead of opening a public issue. Release notes for every version live in CHANGELOG.md.
🔗 Related projects
| Project | Description |
|---|---|
| harness-agent | Agent runtime that drives the browser tools |
| harness-memory | Memory system for browser-backed agents |
| harness-gateway | Multi-platform IM channel bridge |
| Octop | The self-hosted assistant that composes the Harness stack |
📄 License
This project is licensed under the MIT License.
Metadata
Release files for octop-browser 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| octop_browser-1.0.0.tar.gz | 455.7 kB | Without provenance | Details for octop_browser-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| octop_browser-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for octop_browser-1.0.0-py3-none-any.whl |
Total release size: 563.8 kB
Release files / octop_browser-1.0.0.tar.gz
| Download URL | octop_browser-1.0.0.tar.gz |
|---|---|
| Size | 455.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a7e12bcf7ca1541d6c81e05d88ecb6a3aaf988ee648fe05922d06f94de18210c
|
|
BLAKE2b-256 checksum How to use checksums |
9ca6a89e5d3c6b27bb5dc310290f52fb85a3d1f59279b7854ebd5c9973855651
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / octop_browser-1.0.0-py3-none-any.whl
| Download URL | octop_browser-1.0.0-py3-none-any.whl |
|---|---|
| Size | 108.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9856d0a3449ff8fb63dc86acb6cf51e492e3d70d77f7f9a35c1802c99566a174
|
|
BLAKE2b-256 checksum How to use checksums |
b02a16ab16287ea1e3f2357c12ccfe1c90dd0ec7af70d71605c54933c5e7db86
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for octop-browser 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.