Linux Chrome Agent
930
A Docker image that runs a headless Chrome browser with a VLM-powered browser automation agent, exposed via A2A and OpenAI-compatible HTTPS APIs.
Implements an agent using the ReAct (Reasoning + Acting) pattern for intelligent Chrome orchestration.
The container bundles:
The agent observes the browser through screenshots, reasons about the next action, and executes it — repeating until the task is complete or the step budget is exhausted.
| Feature | Details |
|---|---|
| Browser automation | Full Chrome control via CDP — click, type, scroll, drag, keypress, and more |
| VLM-driven agent | Any OpenAI-compatible VLM (GPT-4o, Claude, Gemini, local models) |
| Element detection | DOM-based interactive element detection with visual annotation |
| OmniParser | Optional vision model for richer element detection (replaces DOM detection) |
| UI Locator model | Optional secondary model (UI-TARS / Fara-7B) resolves natural-language element descriptions to pixel coordinates |
| Popup detection | Automatic detection of modals, dialogs, cookie banners, and overlays |
| New-tab handling | Automatically follows links that open in a new tab and switches the CDP session |
| Network idle wait | Waits for XHR/fetch to settle after clicks, keypresses, and JS evaluation |
| Loop detection | Detects repeated identical actions and warns the VLM to try a different strategy |
| Cookie export | Exports browser cookies in Netscape cookies.txt format for sharing with other agents |
| Prior context | Loads prior task summary from trajectory when the same context_id is reused |
| Mid-task injection | Operator can inject instructions or pause/resume the agent mid-execution via A2A |
| Cost tracking | Tracks VLM token usage and cost per run; appended to trajectory summary |
| S3 workspace | Optional shared S3 workspace and blackboard for multi-agent coordination |
| A2A API | Google Agent-to-Agent JSON-RPC 2.0 protocol |
| OpenAI-compatible API | /v1/chat/completions endpoint — drop-in for OpenAI clients |
| HTTPS | Self-signed TLS certificate generated at startup |
| Trajectory recording | Saves per-step screenshots, element lists, VLM requests/responses, and locator logs |
| Visual debugging | TigerVNC + noVNC web client for watching the agent live |
git clone <repo>
cd apps/linux_chrome_agent
docker build --build-context a2a_pkg=../../packages/a2a_agent -f Dockerfile.ubuntu -t linux_chrome_agent:ubuntu .
docker run -d \
--name chrome_agent \
-p 443:443 \
-p 6901:6901 \
-p 5901:5901 \
-p 9222:9222 \
-e VLM_URL=https://api.openai.com/v1 \
-e VLM_API_KEY=sk-... \
-e VLM_MODEL=gpt-4o \
linux_chrome_agent:ubuntu --wait
For development it is convenient to run the Docker image and the agent server separately.
Build the dev image:
docker build --build-context a2a_pkg=../../packages/a2a_agent -f Dockerfile.dev -t linux_chrome_agent_dev:ubuntu .
Run Chrome + VNC only:
docker run -d \
--name chrome_agent_dev \
-p 6901:6901 \
-p 5901:5901 \
-p 9222:9222 \
linux_chrome_agent_dev:ubuntu --wait
Run the agent server locally:
VLM_URL=https://api.openai.com/v1 \
VLM_API_KEY=sk-... \
VLM_MODEL=gpt-4o \
CDP_HOST=localhost \
CDP_PORT=9222 \
uv run python -m app.server
With optional API auth:
API_KEY=your-token \
VLM_URL=https://api.openai.com/v1 \
VLM_API_KEY=sk-... \
VLM_MODEL=gpt-4o \
CDP_HOST=localhost \
CDP_PORT=9222 \
uv run python -m app.server
| Service | URL / Address |
|---|---|
| Agent API (A2A + OpenAI) | https://localhost/ |
| noVNC web viewer | http://localhost:6901/?password=vncpassword |
| VNC native client | localhost:5901 (password: vncpassword) |
| Chrome CDP | http://localhost:9222 |
curl -sk -X POST https://localhost/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "message/send",
"params": {
"id": "task-1",
"message": {
"role": "user",
"parts": [{"text": "Go to example.com and tell me the page title"}]
}
}
}'
curl -sk -X POST https://localhost/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "browser-agent",
"messages": [{"role": "user", "content": "Go to example.com and tell me the page title"}]
}'
curl -sk https://localhost/.well-known/agent.json
| Variable | Default | Description |
|---|---|---|
VLM_URL | https://api.openai.com/v1 | Base URL of the VLM API |
VLM_API_KEY | — | API key for the VLM |
VLM_MODEL | gpt-4o | Model name |
API_KEY | — | Bearer token to protect the agent API (optional) |
PORT | 443 | HTTPS port for the agent server |
AGENT_URL | — | Public URL published in the AgentCard (e.g. https://my-agent.example.com/). Defaults to https://localhost:{PORT}/ |
CDP_HOST | localhost | Chrome DevTools Protocol host |
CDP_PORT | 9223 | Chrome DevTools Protocol port (internal) |
VIEWPORT_WIDTH | 1920 | Browser viewport width in pixels |
VIEWPORT_HEIGHT | 1080 | Browser viewport height in pixels |
SEND_IMAGE | true | Set to false for text-only LLMs (uses element list only, no screenshot) |
TRAJECTORY_DIR | — | Directory to save trajectory data; each run creates a YYYYMMDD_HHMMSS/ subdirectory |
OmniParser is a visual element detection model. When configured, it replaces the default DOM-based element detection and returns richer UI element data.
| Variable | Default | Description |
|---|---|---|
OMNIPARSER_URL | — | Base URL of the OmniParser API (e.g. http://omniparser:8000) |
OMNIPARSER_API_KEY | — | Bearer token for OmniParser API auth (optional) |
The locator is a secondary vision model (e.g. UI-TARS, microsoft/Fara-7B) specialised in resolving natural-language element descriptions to exact pixel coordinates. When configured, the agent describes elements by name (query) instead of guessing x/y coordinates, and the locator finds the exact click target.
Enabling the locator also disables the marked-screenshot annotation (since coordinates come from the locator, not visual inspection).
| Variable | Default | Description |
|---|---|---|
LOCATOR_URL | — | OpenAI-compatible API base URL for the locator model |
LOCATOR_API_KEY | — | Bearer token for locator API auth (optional) |
LOCATOR_MODEL | ui-tars | Model name passed to the locator API |
When configured, the agent writes a blackboard entry to S3 (or a local mount) so other agents can observe its status. Use with multi-agent orchestration workflows.
| Variable | Default | Description |
|---|---|---|
WORKSPACE_ID | — | Logical workspace name shared across agents (e.g. project-abc) |
WORKSPACE_MOUNT_PATH | /workspace | Local filesystem path for the workspace (FUSE mount or local dir) |
S3_BUCKET | — | S3 bucket for direct S3 mode (skips FUSE mount) |
S3_ENDPOINT_URL | — | S3-compatible endpoint URL (omit for AWS S3) |
S3_ACCESS_KEY_ID | — | S3 access key |
S3_SECRET_ACCESS_KEY | — | S3 secret key |
S3_REGION | us-east-1 | S3 region |
S3_PREFIX | — | Optional prefix prepended to workspace_id in the bucket |
AGENT_ID | linux_chrome_agent | Unique agent name written to the blackboard |
| Variable | Default | Description |
|---|---|---|
AGENT_NAME | web.browser.chrome | Agent name published in the A2A agent card |
AGENT_VERSION | 0.1.0 | Agent version published in the agent card |
AGENT_DESCRIPTION | (built-in) | Description published in the agent card |
| Variable | Default | Description |
|---|---|---|
VNC_PW | vncpassword | VNC password |
VNC_RESOLUTION | 1920x1080 | Screen resolution |
When TRAJECTORY_DIR is set, each agent run creates a timestamped subdirectory containing one folder per step:
$TRAJECTORY_DIR/
20260225_181523/ # run timestamp (YYYYMMDD_HHMMSS)
summary.md # task, result, reusable plan, and cost summary
turn_0000/
screenshot.png # browser state at start of step
screenshot_marked.png # annotated with detected elements (DOM mode)
screenshot_annotated.png # annotated with the proposed click target
elements.json # detected interactive elements
llm_0_request.json # messages sent to the VLM
llm_0_response.json # raw VLM reply + parsed action/args/reasoning
locator.json # locator model request + response (when LOCATOR_URL is set)
turn_0001/
...
20260225_182041/ # next run
...
Example:
docker run -d \
-e VLM_URL=... -e VLM_API_KEY=... \
-e TRAJECTORY_DIR=/data/trajectory \
-v /tmp/agent-logs:/data/trajectory \
linux_chrome_agent:ubuntu --wait
The agent detects interactive UI elements at each step and passes them to the VLM. Three modes are available:
A JavaScript snippet scans the page for interactive elements (buttons, links, inputs, etc.) using checkVisibility() and elementFromPoint() reachability checks. Elements are deduplicated by a centre-point grid and annotated on the screenshot with numbered badges.
When OMNIPARSER_URL is set, the screenshot is sent to OmniParser instead of running DOM detection. OmniParser returns visual element bounding boxes with interactivity scores.
When LOCATOR_URL is set, the agent describes elements in plain language (e.g. "Sign in button", "email input field") and sends the query + screenshot to the locator model. The locator returns the exact pixel coordinates, eliminating the need for the VLM to estimate positions.
Click strategy priority when locator is active:
click with query — describe the element (always preferred)click with id — use element id from the detected listclick_button / click_link — text-based fallbackfocus_element with query + type — for input fieldsevaluate_js — last resortThe agent automatically detects popups, modals, dialogs, and cookie banners at each step using a combination of:
role="dialog", role="alertdialog", aria-modal="true")modal, popup, cookie, consent, gdpr, overlay)When a popup is detected, the VLM receives its role, label text, position, and whether it has a close button — and is instructed to dismiss it before continuing the main task.
| Tool | Description |
|---|---|
navigate_to | Navigate to a URL |
screenshot | Capture current page as base64 PNG |
click | Click a UI element by query (locator), id, or (x, y) |
double_click | Double-click a UI element by query, id, or (x, y) |
scroll | Scroll at (x, y); positive scroll_y = down, negative = up |
type | Type text via keyboard events |
wait | Wait N milliseconds |
move | Move mouse to (x, y) |
keypress | Press a key or modifier combo (e.g. ["Control","a"]) |
drag | Drag along a path of coordinates |
left_mouse_down | Press and hold left mouse button |
left_mouse_up | Release left mouse button |
get_current_url | Return current page URL |
get_dimensions | Return viewport width and height |
get_page_info | Page title, URL, and DOM snippet |
get_request_headers | Headers from last network request |
get_response_headers | Headers from last network response |
get_raw_html | Raw HTML body of last response |
get_all_page_requests | All network requests since page load |
evaluate_js | Run JavaScript and return result |
click_button | Click a button by CSS selector or visible text |
click_link | Click a link by CSS selector, href, or visible text |
focus_element | Focus an input by query, id, or (x, y) |
focus_element_css | Focus an input by CSS selector |
export_cookies | Export browser cookies in Netscape cookies.txt format; optionally filter by domain |
# Default — start Chrome, VNC, and API server, keep running
docker run linux_chrome_agent:ubuntu --wait
# Headless mode — no VNC, CDP + API only
docker run linux_chrome_agent:ubuntu --no-vnc
# Debug — verbose startup output
docker run linux_chrome_agent:ubuntu --debug
# Skip startup — run a custom command instead
docker run linux_chrome_agent:ubuntu --skip bash
┌───────────────────────────────────────────────────┐
│ Docker Container │
│ │
│ Chrome (CDP :9223) ◄─ socat ◄─ :9222 (ext) │
│ │ │
│ CDPClient Network idle tracking │
│ │ New-tab detection │
│ Agent (browser.py) │
│ │ │
│ LangGraph (graph.py) │
│ take_screenshot │
│ │ OmniParser (optional) │
│ │ Popup detection │
│ call_vlm ──► VLM (OpenAI-compatible) │
│ execute_action │
│ │ Locator model (optional) │
│ │ Network idle wait │
│ │ Loop detection │
│ check_updates ── drain TaskInbox │
│ │ handle pause/resume │
│ ├── (loop) → take_screenshot │
│ ├── (done) → generate_summary ──► END │
│ └── (max steps) ──► END │
│ │
│ FastAPI Server (:443 HTTPS) │
│ ├── A2A router (POST /) │
│ └── OpenAI router (/v1/...) │
└───────────────────────────────────────────────────┘
MIT
The agent image exposes an MCP (Model Context Protocol) server on the same port as A2A at /mcp. Any MCP client — Claude Desktop, Cursor, Windsurf, ChatGPT Connectors, OpenAI Agents SDK — can list and invoke these tools using the pod's API_KEY as Bearer.
In production, Core proxies https://linux-chrome-agent.agents.forfetch.ai/mcp → the worker pod's /mcp.
| Tool (skill_id) | Description |
|---|---|
navigate | Navigate the browser to a URL |
click | Click on an element or coordinates |
double_click | Double-click on an element or coordinates |
type | Type text into a focused element |
scroll | Scroll the page up, down, left, or right |
screenshot | Take a screenshot of the current page |
extract_text | Extract text content from a page or element |
evaluate_js | Execute JavaScript in the browser |
hover | Hover over an element |
select_option | Select an option from a dropdown |
Input schemas are authored in apps/agents_mcp/app/seed.py. Skills without an explicit schema advertise a single {task: str} freeform parameter.
Content type
Image
Digest
sha256:640816d2d…
Size
727.1 MB
Last updated
5 months ago
docker pull superbizon007/linux-chrome-agent