Difference Between Local and Remote Operators in UI-TARS Desktop: Implementation Guide

UI-TARS Desktop distinguishes between local and remote operators by process ownership—LocalBrowserOperator spawns and manages new browser instances locally, while RemoteBrowserOperator attaches to existing browsers via Chrome DevTools Protocol without managing the underlying process.

The bytedance/UI-TARS-desktop repository abstracts browser automation through the BrowserOperator base class, providing two concrete implementations tailored to different automation scenarios. Understanding the difference between local and remote operators in UI-TARS Desktop enables developers to choose between isolated testing environments and integration with existing browser sessions.

Architectural Overview

Both operator types extend the abstract BrowserOperator class defined in packages/ui-tars/operators/browser-operator/src/browser-operator.ts, exposing identical high-level APIs for actions like click(), type(), and screenshot(). The critical distinction lies in how each implementation establishes the browser connection and manages the process lifecycle.

  • LocalBrowserOperator creates fresh browser processes using locally installed Chrome or Edge binaries discovered at runtime.
  • RemoteBrowserOperator connects to already-running browser instances via CDP endpoints, functioning as a singleton to share connections across the application.

LocalBrowserOperator: Launching Fresh Browser Instances

The LocalBrowserOperator implementation in multimodal/gui-agent/operator-browser/src/LocalBrowserOperator.ts handles complete browser lifecycle management from discovery to termination.

Browser Discovery and Launch

When initializing a local operator, the code constructs a BrowserFinder to locate installed browser executables, then calls LocalBrowser.launch() with the discovered path and browser type. According to the source at lines 29-56, this process:

  1. Detects Chrome, Edge, or other supported browsers on the host machine.
  2. Spawns a new process with the specified executable path.
  3. Optionally opens a default search-engine page if configured in the constructor.

Lifecycle Ownership

Local operators maintain full control over the browser process. You can close, restart, or kill the browser instance through the operator interface, making this approach ideal for automated testing scenarios requiring clean, isolated environments.

import { LocalBrowserOperator } from '@gui-agent/operator-browser';

async function runLocalAutomation() {
  // Create operator with optional search engine startup
  const operator = new LocalBrowserOperator({
    searchEngine: 'google',
    highlightClickableElements: true,
  });

  await operator.initialize();  // Launches fresh browser process
  await operator.goto('https://example.com');
  await operator.screenshot('capture.png');
  await operator.close();       // Terminates the browser process
}

Key implementation: The constructor and initialization logic (LocalBrowserOperator.ts lines 11-68) demonstrates how the operator wraps the local browser instance and manages its execution context.

RemoteBrowserOperator: Attaching to Existing CDP Sessions

The RemoteBrowserOperator class defined in packages/ui-tars/operators/browser-operator/src/browser-operator.ts (lines 815-843) takes a fundamentally different approach by connecting to browsers already running elsewhere.

Singleton Connection Pattern

Unlike local operators that instantiate fresh objects, RemoteBrowserOperator implements a singleton pattern via RemoteBrowserOperator.getInstance(cdpUrl). This ensures multiple application components share the same CDP connection without creating duplicate attachments, as seen in the desktop entry point at apps/ui-tars/src/main/remote/operators.ts.

CDP Endpoint Requirements

Remote operators require callers to supply a valid Chrome DevTools Protocol URL (e.g., http://localhost:9222/json/version). The operator creates a RemoteBrowser wrapper around this existing connection rather than spawning a new process. As implemented in the source code, this strategy:

  • Does not manage the remote browser process lifecycle.
  • Does not open default pages or search engines.
  • Attaches to whatever pages and tabs currently exist in the target browser.
import { RemoteBrowserOperator } from '@gui-agent/operator-browser';

async function attachToRemoteBrowser() {
  // Connect to Chrome started with --remote-debugging-port=9222
  const cdpUrl = 'http://localhost:9222/json/version';
  
  // Obtain singleton instance—no new process created
  const operator = await RemoteBrowserOperator.getInstance(cdpUrl);
  
  await operator.initialize();
  const pages = await operator.getPages();  // Access existing tabs
  await operator.click('#submit-button');
  const png = await operator.screenshot('result.png');
  // Browser process remains running after operator closes
}

Key implementation: The singleton creation and CDP attachment logic resides in browser-operator.ts lines 815-843, showing how the operator wraps the remote debugging connection.

Implementation Differences Compared

Capability LocalBrowserOperator RemoteBrowserOperator
Process Control Owns browser lifecycle—can launch and terminate Attaches only—no process management
Instantiation new LocalBrowserOperator(config) RemoteBrowserOperator.getInstance(cdpUrl)
Browser Discovery Uses BrowserFinder to locate local installs Requires explicit CDP URL parameter
Session State Fresh profile with optional search-engine page Existing tabs and user session
Architecture Multiple independent instances allowed Singleton pattern ensures shared connection
Typical Use CI/CD pipelines, headless automation, testing Debugging existing sessions, user-driven automation

When to Use Each Operator

Choose LocalBrowserOperator when you need isolated, reproducible browser environments for automated testing or when running UI-TARS Desktop in headless server environments where browser processes must be programmatically controlled according to the specific automation schedule.

Choose RemoteBrowserOperator when integrating with user-driven Chrome sessions, connecting to browsers in Docker containers exposing CDP ports, or when debugging requires preserving existing browser state and cookies. The desktop application entry point in apps/ui-tars/src/main/remote/operators.ts demonstrates production usage of the remote operator for attaching to existing user sessions.

Summary

Frequently Asked Questions

Can I run multiple RemoteBrowserOperator instances simultaneously?

No. RemoteBrowserOperator implements a singleton pattern via getInstance(cdpUrl) specifically to prevent multiple connections to the same CDP endpoint. If you need to connect to different remote browsers simultaneously, you must use different CDP URLs, as each unique endpoint maintains its own singleton instance according to the implementation in browser-operator.ts.

Does LocalBrowserOperator support headless mode?

Yes. The LocalBrowserOperator passes configuration options through to LocalBrowser.launch(), which supports standard Puppeteer-compatible headless flags. You can specify headless behavior in the constructor options, allowing the operator to spawn browsers without visible UI for server-side automation tasks.

What happens if the remote browser closes while using RemoteBrowserOperator?

Since RemoteBrowserOperator does not manage the browser process, it will lose connection and subsequent operations will fail with CDP connection errors. Unlike local operators that can restart their managed process, remote operators require you to manually restart the external browser and obtain a new CDP URL before reconnecting.

Are both operators available in the same npm package?

Yes. Both LocalBrowserOperator and RemoteBrowserOperator are typically exported from @gui-agent/operator-browser (or the appropriate package within the monorepo), allowing you to import either implementation based on your runtime requirements while maintaining the same interface for browser automation tasks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →