Overview
Orbis is a headless browser engine for AI agents and automation. It fetches pages, builds a standards-based DOM and runs the page’s JavaScript in a V8 sandbox — but it never lays out or paints anything. Instead of pixels it produces three things an agent can use directly:
- An action map — every visible link, button and form field, numbered
@1,@2,@3, with its role, label and resolved CSS selector. - Markdown — the readable content of the page, without scripts and styles.
- Structured data — OpenGraph, Twitter card, canonical URL, meta tags and JSON-LD.
You reach the engine four ways: the Chrome DevTools Protocol (Playwright, Puppeteer), W3C WebDriver (Selenium), HTTP endpoints, or the Model Context Protocol (Claude Code, Cursor, Windsurf). The npm package installs three equivalent commands: memora-orbis, orbis and the deprecated memora-browser.
Install
Orbis runs on Node.js and is tested on Node 20 and 22. Run it without installing, or add it to a project:
# run directly
npx memora-orbis --port 9222
# or add to a project
npm install memora-orbisThe Python client talks to a running Orbis server over HTTP:
pip install memora-orbisnpx memora-orbis reports that the package can’t be found, contact enterprise@memora.company for early access.Quickstart
1. Start the server
npx memora-orbis --port 9222╔════════════════════════════════════════════════════════════════╗
║ 🪐 ORBIS BY MEMORA ║
║ An Agentic Browser • Built for Autonomous AI ║
║ Powered by Memora Browser Engine ║
╚════════════════════════════════════════════════════════════════╝
▶ CDP WebSocket: ws://127.0.0.1:9222/devtools/browser/memora-root
▶ Discovery Endpoint: http://127.0.0.1:9222/json/version
▶ Agent Snapshot: http://127.0.0.1:9222/orbis/snapshot (or /memora/snapshot)
▶ Stealth Mode: 🛡️ ACTIVE (Anti-Detect On)
▶ License / Tier: ⚡ COMMUNITY EVALUATION (Free For Devs)
▶ Status: READY FOR PLAYWRIGHT / PUPPETEER / AGENTS
Stealth is on by default; there is no --stealth flag, only --no-stealth. Check the server with curl http://127.0.0.1:9222/json/version — it reports a Chrome 130 compatible browser and product: "Orbis/1.0.0".
2. Distill one page
npx memora-orbis --eval https://app.example.com/login🔍 [Orbis] Fetching & Distilling: https://app.example.com/login ...
✨ Successfully distilled in 35.6ms!
📌 Title: Enterprise Cloud - Sign In to Workspace
🎯 Interactive Elements: 15
🧠 Estimated Prompt Tokens: 630
=== INTERACTIVE ACTION MAP (15 elements) ===
[@3] [select] "Language Selector" type="select" (selector: #locale-picker)
[@4] [input] "alex@memora.company" type="email" placeholder="alex@memora.company" (selector: #username)
[@9] [button] "Sign In with Credentials" type="submit" (selector: #btn-submit)
…
3. Dump a page in the format you need
npx memora-orbis fetch https://example.com --dump markdown
npx memora-orbis fetch https://example.com --dump action_map
npx memora-orbis fetch https://example.com --dump structured_dataPlaywright & Puppeteer
Start Orbis (CDP is the default protocol), then connect instead of launching a browser. The rest of your script is unchanged.
import { chromium } from 'playwright';
// Before: const browser = await chromium.launch();
const browser = await chromium.connectOverCDP('http://127.0.0.1:9222');
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());import puppeteer from 'puppeteer-core';
// Before: const browser = await puppeteer.launch();
const browser = await puppeteer.connect({ browserURL: 'http://127.0.0.1:9222' });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());Selenium (WebDriver)
Start a WebDriver server — no chromedriver binary needed — and point webdriver.Remote at it.
npx memora-orbis --protocol webdriver --port 9222# npx memora-orbis --protocol webdriver --port 9222
from selenium import webdriver
driver = webdriver.Remote(
command_executor="http://127.0.0.1:9222",
options=webdriver.ChromeOptions(),
)
driver.get("https://example.com")
print(driver.title)
driver.quit()import { Builder, Browser } from 'selenium-webdriver';
const driver = await new Builder()
.forBrowser(Browser.CHROME)
.usingServer('http://127.0.0.1:9222')
.build();
await driver.get('https://example.com');
await driver.quit();Up to 16 sessions run concurrently by default (--max-sessions). One process serves one protocol; run a second process on another port if you need CDP and WebDriver at once.
MCP server
Add Orbis to any MCP client that supports stdio servers — Claude Code, Claude Desktop, Cursor, Windsurf or Antigravity:
{
"mcpServers": {
"orbis": {
"command": "npx",
"args": ["-y", "memora-orbis", "--mcp"]
}
}
}The server lists 15 tools. Each memora_* tool also answers to an orbis_* alias (for example orbis_browse).
| Tool | What it does |
|---|---|
memora_browse | Load a URL; return Markdown, the action map and a token estimate |
memora_act | Click or type using an action ID or CSS selector |
memora_fill_form | Fill several fields at once and optionally submit |
memora_diff | Summarise what changed since the previous turn |
memora_fork | Copy the current page to explore another path |
memora_session | Save or load cookies and web storage |
memora_inspect | List the title and current interactive elements |
memora_extract | Pull text or attributes from matching elements |
memora_metadata | Read OpenGraph, Twitter card, canonical and JSON-LD |
memora_console | Read console logs, warnings and errors |
memora_storage | Read or write localStorage and sessionStorage |
memora_cookies | List, set or clear cookies |
memora_eval | Run JavaScript in the page |
memora_html | Get raw HTML for the page or one element |
memora_reload | Reload the page and refresh the action map |
--engine auto uses a system Chrome or Edge when it finds one. Add "--engine", "native" to the args to always use the Orbis engine, or chrome for pages that need a full renderer.OrbisAgent (TypeScript)
OrbisAgent is the high-level controller for LLM loops. OrbisAgent.launch() starts an in-process engine and a CDP server on cdpPort.
import { OrbisAgent } from 'memora-orbis';
const agent = await OrbisAgent.launch({ cdpPort: 9222 });
// Navigate and read the page
const snapshot = await agent.goto('https://app.example.com/login');
snapshot.actionMapText; // numbered actions
snapshot.markdown; // readable content
snapshot.tokenEstimate; // characters ÷ 4
// Act by ID (or CSS selector)
agent.act('@4', 'agent@example.com'); // type
agent.act('@8'); // click
// Fill a whole form, then submit
await agent.fillForm({ '@4': 'agent@example.com', '@6': process.env.APP_PASSWORD }, true);
// What changed?
console.log(agent.diff().summary);
// Branch, persist, clean up
const branch = agent.fork();
agent.saveSession('./sessions/app.json');
await agent.close();| Method | Returns | Notes |
|---|---|---|
goto(url, options?) | Promise<snapshot> | options: focusSelector, excludeSelectors |
snapshot() · markdown() | snapshot · string | Re-read the current page |
act(target, text?) | boolean | Types when text is given, otherwise dispatches a click |
fillForm(fields, submit = true) | Promise | Text, textarea, select, checkbox, radio, contenteditable |
diff() | PageDiff | Compared with the previous snapshot |
fork() | OrbisAgent | Copy of the page with cookies and storage |
saveSession(path) · loadSession(path) | void | JSON: url, cookies, localStorage, sessionStorage |
close() | Promise | Stops the engine and server |
act() dispatches the click event, which runs the page’s handlers. To follow a link or submit a form the way a user’s click would, call await agent.page.activate(target). For multi-step wizards use agent.page.fillForm({ steps: [...] }), where each step has fields and a submitActionId.Python client
The Python package is a small HTTP client for a running Orbis server. Import it from memora or orbis.
from memora import OrbisAgent
# Talks to a running server: npx memora-orbis --port 9222
agent = OrbisAgent.connect("http://127.0.0.1:9222")
snap = agent.goto("https://example.com")
print(snap.title, snap.token_estimate)
print(snap.action_map_text)
agent.act("@1")
agent.fill_form({"#username": "agent", "#password": "..."}, submit=True)| Method | Calls |
|---|---|
OrbisAgent.connect(base_url, api_key=None) | Checks /json/version |
goto(url) | GET /orbis/goto |
snapshot(focus_selector, exclude_selectors) | GET /orbis/snapshot |
markdown() | GET /orbis/snapshot?format=markdown |
act(action_id, text=None) | POST /orbis/act |
fill_form(fields, submit=True) | POST /orbis/fill-form |
diff() | GET /orbis/diff (returns a dict) |
save_session(path) · load_session(path) | /orbis/session |
to_browser_use() · to_langchain_tools() | Helpers for Browser-Use and LangChain |
Framework adapters
The npm package exports small helpers that produce the configuration each framework expects:
| Export | Returns |
|---|---|
getBrowserUseConfig(agentOrPort = 9222) | { cdpUrl, disableSecurity, extraChromiumArgs } |
getStagehandConfig(agentOrPort = 9222) | { env: 'LOCAL', localBrowserUrl } |
createOrbisLangChainTools(agent) | Tool objects: navigate, snapshot, act, diff, fill_form |
CLI
memora-orbis [options] and memora-orbis serve [options] start the automation server.
| Option | Default | Description |
|---|---|---|
-p, --port <number> | 9222 | Port for CDP / WebDriver / HTTP. |
-h, --host <string> | 127.0.0.1 | Interface to bind. Use 0.0.0.0 inside containers. |
--protocol <cdp|webdriver> | cdp | cdp for Playwright and Puppeteer, webdriver for Selenium. One protocol per process. |
--engine <native|chrome|auto> | auto | Engine for --mcp mode. auto uses a system Chrome or Edge when one is found, otherwise the native engine. |
--max-sessions <number> | 16 | Maximum concurrent WebDriver sessions. |
--no-stealth | — | Disable the stealth profile (it is on by default). |
--eval <url> | — | Fetch a URL, print the snapshot and exit. |
--mcp | — | Run as a Model Context Protocol server over stdio. |
fetch
memora-orbis fetch [options] <url> prints a page in an LLM-ready format and exits.
| Option | Default | Description |
|---|---|---|
-d, --dump <format> | markdown | markdown, html, structured_data, semantic_tree or action_map (semantic_tree and action_map are the same output). |
--dump-selector <selector> | — | Restrict output to elements matching a CSS selector. |
--dump-max-bytes <bytes> | — | Cap the output size. |
--wait-ms <ms> | — | Wait after loading, for content that appears a moment later. |
--no-stealth | — | Disable the stealth profile. |
Every command also accepts the network options.
Snapshot & action map
A snapshot has these fields:
| Field | Description |
|---|---|
url · title | Current page |
actionMapText | The numbered action map as text |
actions[] | actionId, role, text, type, value, placeholder, href, selector, disabled, checked, expanded, selected… |
markdown | Readable content |
interactiveCount | Number of actions |
tokenEstimate | Characters ÷ 4 across title, action map and Markdown |
Each line of the action map has the same shape:
[@ID] [role] "label" type="…" placeholder="…" href="…" [CHECKED] (selector: …)
[@4] [input] "alex@memora.company" type="email" placeholder="alex@memora.company" (selector: #username)
[@8] [input] "[Untitled input]" type="checkbox" (selector: #remember)
[@9] [button] "Sign In with Credentials" type="submit" (selector: #btn-submit)
- Role is the ARIA role when present, otherwise link, button or the tag name.
- Label comes from aria-label, then placeholder, title, value and text content (up to 80 characters). With none of these it reads
[Untitled input]. - Selector prefers an id, then test attributes (data-testid, data-cy…), name, aria-label, placeholder, a unique class, and finally an nth-of-type path.
- Hidden elements —
hidden,aria-hidden,type=hiddenor inline display/visibility hidden, including on an ancestor — are left out. - IDs are positional. They are assigned in page order on every snapshot; take a fresh snapshot after the page changes.
Diff
diff() compares the page with the previous snapshot, matching elements by selector. It returns urlChanged, titleChanged, addedActions, removedActions, mutatedActions (changes to text, value, disabled, checked, expanded, selected) and a text summary:
=== PAGE STATE DELTA ===
Added (2 elements):
+ [@6] [input] "Card Number" (selector: #card)
+ [@7] [button] "Pay Now" (selector: #btn-pay)
The first call on a page returns a one-line description of the initial load instead.
CDP methods & REST
Alongside the standard Page, Runtime, DOM, Network, Target and Input domains, Orbis adds agent methods. They are available under the Orbis and Memora domains.
| Method | Params | Returns |
|---|---|---|
Orbis.getSemanticSnapshot | { focusSelector?, excludeSelectors? } | Snapshot: action map, actions, Markdown, token estimate. |
Orbis.getMarkdown | { focusSelector?, excludeSelectors? } | Distilled Markdown for the current page. |
Orbis.getStructuredData | — | OpenGraph, Twitter card, canonical, meta and JSON-LD. |
Orbis.diff | — | Changes since the previous snapshot, plus the current snapshot. |
Orbis.act | { actionId, type?: "click" | "type", text? } | Click or type by action ID or selector. |
Orbis.fillForm | { fields, submit?, steps? } | Fill several fields, optionally submit; multi-step wizards via steps. |
Orbis.saveSession / loadSession | { filePath } | Write or restore cookies and web storage. |
The same server answers HTTP requests. Every route is also available under /memora/….
| Method | Path | Input | Description |
|---|---|---|---|
| GET | /orbis/snapshot | ?focus=&exclude=&format=markdown&id= | Snapshot as JSON, or Markdown with format=markdown. |
| GET | /orbis/goto | ?url= | Navigate the default page and return the result with a snapshot. |
| POST | /orbis/act | { actionId, type?, text?, id? } | Click or type; returns success and a fresh snapshot. |
| POST | /orbis/fill-form | { fields, submit? } | Fill fields; submit defaults to true. |
| GET · POST | /orbis/diff | — | State comparison against the previous call. |
| GET · POST | /orbis/session | — | Export or restore session state. |
| POST | /orbis/session/save | { filePath } | Save session state to a file. |
curl "http://127.0.0.1:9222/orbis/goto?url=https://example.com"
curl "http://127.0.0.1:9222/orbis/snapshot?format=markdown"
curl -X POST http://127.0.0.1:9222/orbis/act \
-H "Content-Type: application/json" -d '{"actionId":"@1"}'WebDriver coverage
Implemented W3C WebDriver Classic commands:
- Sessions & navigation — status, new/delete session, timeouts, URL, back, forward, refresh, title, page source.
- Elements — find element(s) and nested finds by CSS selector, tag name, link text, partial link text and XPath; active element.
- Element state & actions — selected, attribute, property, CSS value (inline styles), text, name, rect, enabled, displayed, computed role and label, click, clear, send keys.
- Scripts, cookies, input — execute sync and async, cookies, actions.
Frames, alerts, shadow roots and printing are not supported, and screenshots return a blank image.
Stealth profile
Every request carries Chrome 130 on Windows headers: User-Agent, Accept, Accept-Language, Sec-Ch-Ua* client hints and Sec-Fetch-*. With stealth on (the default), Orbis also sets page-visible values to match before any page script runs: navigator.webdriver = false, platform, languages, hardware concurrency and device memory, plugins and MIME types, screen, media devices, battery, window.chrome and the Permissions API.
--no-stealth when testing your own sites.robots.txt
npx memora-orbis --obey-robots
npx memora-orbis fetch https://example.com --obey-robots --robots-agent MyCrawler- robots.txt is fetched once per origin and cached for 24 hours.
- The main navigation, every redirect hop, sub-resources and page
fetch()calls are checked. - A 4xx robots.txt allows everything; a 5xx or network error disallows everything.
- Blocked navigations fail with
net::ERR_BLOCKED_BY_CLIENTover CDP.
Network options
These options work on the server and on fetch:
| Option | Description |
|---|---|
--http-header "Name: Value" | Add a header to every request. Repeatable. |
--user-agent <ua> | Override the User-Agent header. |
--http-timeout <ms> | Navigation request timeout. |
--use-env-proxy | Use HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY from the environment. |
--proxy-bypass <hosts> | Comma-separated hosts that skip the proxy, e.g. localhost,.internal.corp. |
--insecure-disable-tls-host-verification | Accept self-signed or intercepting-proxy certificates. |
--obey-robots | Fetch robots.txt per origin and block disallowed requests (RFC 9309). |
--robots-agent <token> | Product token matched against robots.txt User-agent lines. Default MemoraBrowser. |
--http-proxy, --proxy-bearer-token | Reserved for the managed proxy network in Orbis Cloud (coming soon). |
When embedding the engine as a library, HTTP, HTTPS and SOCKS5 proxies (with credentials and bypass lists) can be set through the proxy network option.
Version check
When the server starts it asks orbis.memora.company whether a newer version is available. Set ORBIS_NO_TELEMETRY=1 to skip the check. It is also skipped when NODE_ENV=test.
Limitations
Orbis trades rendering for speed and agent-ready output. Know the trade-offs before you migrate a suite:
- No pixels. No screenshots, PDFs or visual comparison. CDP
Page.captureScreenshotreturns a 1×1 placeholder. - Synthetic layout. Element boxes come from a simple grid, not real CSS layout.
- No file uploads, frames or alerts over CDP or WebDriver.
- Runtime APIs. Page scripts get
fetch, timers and web storage.XMLHttpRequestis not available andMutationObserveris a no-op. - Cookies are kept in one jar per page and are not scoped by domain.
- fork() rebuilds the copy from HTML and storage, so in-memory JavaScript state is not carried over.
For pages that need a full browser, MCP users can switch to --engine chrome.
License
Orbis is distributed under the Orbis Commercial & Community Evaluation License v1.0. You may install and run it for personal development, learning, testing and evaluation, including with local agents built on frameworks such as LangChain, Browser-Use, CrewAI or Stagehand in non-production environments.
Production or commercial use requires a commercial subscription or written agreement. Reverse-engineering the distributed build, reselling it, or hosting it as a competing multi-tenant service is not permitted.