How to Trace the Data Flow from Agent JavaScript to Browser Runtime in Ego-Lite

TLDR: In citrolabs/ego-lite, agent-provided JavaScript is read by runMain() (src/run.ts), wrapped in an async function with a helper façade, and executed—where helper calls transform into CDP commands sent through the globalThis.ego bridge to the browser runtime via src/browser-runtime.ts.

The citrolabs/ego-lite repository enables AI agents to control a browser by executing raw JavaScript snippets. Understanding the ego-lite data flow from agent JavaScript to browser runtime is critical for developers building autonomous browser agents or contributing to the project. This article traces the exact pipeline through the source code, explaining how code transforms from a CLI string into real browser actions like navigation, clicks, and DOM queries.

The Ego-Lite Data Pipeline: An Overview

The architecture of ego-browser consists of a strict sequence of modules that translate user intent into browser actions. By mapping out the agent JavaScript to browser runtime pipeline, there is absolute clarity on how state flows through the system.

  1. CLI Entry PointrunMain() reads the heredoc script.
  2. Context ConstructionexecutionContext() calls helperContext() to build the facade.
  3. Global Injection → The facade is assigned to globalThis.
  4. Code Execution → An AsyncFunction wraps the user code and runs it.
  5. CDP Transliteration → Helpers call cdp() and evaluate().
  6. Bridge Communicationsend() passes the command through globalThis.ego.
  7. Browser Runtimebrowser-runtime.ts processes the session and executes actions.
  8. Result Decoding → CDP responses are unwrapped and returned to the agent script.
  9. Output FlushingflushSink() writes console logs to stdout.

Step 1: The CLI Reads the Agent Script (src/run.ts)

Every data flow begins in the runMain() function inside src/run.ts. The CLI reads the JavaScript snippet either from stdin or from the stdinText option. This is the entry point where the raw agent JavaScript enters the ego-lite system.

// Simplified from src/run.ts
const code = process.env.QUIET_MODE 
  ? readStdin() 
  : browserConfig.stdinText;

This raw snippet becomes the foundation for the entire execution context.

Step 2: Building the Execution Context and Helper Façade (src/helpers.ts)

The execution context is created by calling executionContext() which invokes helpers.helperContext(agentHelpers). According to the source in src/helpers.ts, this is where the public façade objects (page, browser, taskSpaces, site, fetch, cdp, evaluate) are assembled.

These facade functions are not directly interacting with the browser. Instead, they are driver modules. For example, the page methods invoke nav.goto, pointer.click, and keyboard.press from the src/driver/* folder. This abstraction layer is critical for the ego-lite browser runtime to remain isolated from complex protocol logic.

Step 3: Injecting Helpers into the Global Scope

runMainCommand takes the façade and assigns it to globalThis. This means the agent script does not require any imports to use page, browser, taskSpaces, etc. Inside the executed script, the agent can directly access globalThis.page and it will resolve to the helper façade.

Step 4: Executing User Code with AsyncFunction

The raw string code is dynamic. To execute it, ego-lite uses the AsyncFunction constructor. The user code is wrapped in an async function whose parameters are the helper names. This instantiates the code within the correct scope context and allows it to await browser operations.

const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
const fn = new AsyncFunction(...Object.keys(agentHelpers), code);

This is the exact point where the agent JavaScript to browser runtime execution flow begins.

Step 5: How Helpers Convert to CDP Commands

This core data flow consists of the helper calls invoking lower-level CDP wrappers.

The cdp() and evaluate() Wrappers in src/cdp-eval.ts

In src/cdp-eval.ts, the cdp() function sends the command via send() to the CDP bridge. The evaluate() function wraps Runtime.evaluate and handles decoding of unserializable values like NaN, Infinity, and negative zero.

// Pseudo code based on src/cdp-eval.ts
import { send } from './state';

async function cdp(method: string, params: object) {
  const targetId = await getTargetId();
  const response = await send(targetId, method, params);
  return response;
}

The Ego Bridge in src/state.ts

The bridge is established through globalThis.ego. When cdp() is invoked, it sends the serialized request to state.cdpOverride for test scenarios or to send() otherwise. This module is the actual internal-facing API to the embedded ego-lite browser binary. The binary then forwards these CDP commands to Chrome via Chrome DevTools Protocol.

Step 5: Browser-Runtime Processing (src/browser-runtime.ts)

Centralising on the browser side, src/browser-runtime.ts handles the lifecycle of all CDP sessions. It manages:

  • Session ID Caching – reuse a single session across multiple actions.
  • Event Buffering – maintains a queue for browser events.
  • Actions – implements higher-level operations including snapshots, element resolution, and task-space management.

This module is the actual "browser runtime" referenced in your question. It processes the raw CDP commands and turns them into physical browser state changes.

Step 6: Handling the Result and Decoding Values

After the runtime executes, the result flows back stage by stage.

  • cdp() resolves to the raw CDP result.
  • evaluate() handles serialization edge cases (e.g., NaN, Infinity).
  • Higher-level helpers translate protocol responses into friendly JavaScript values. For example, page.url() returns a simple string, or locator.textContent() returns the DOM node's string.

This reverse path ensures that the data flow from the browser runtime back to the agent script is accurate and type-safe.

Step 7: Flushing Output to Stdout (flushSink)

As the agent script executes, any console.log calls are buffered via a specially-designed sink. After the script finishes, flushSink() writes the output to the CLI stdout. If the script threw an exception, the buffer is discarded, and the error propagates.

Code Of Examples: Seeing the Data Flow in Action

The following script demonstrating the data flow:

async function main() {
  await page.goto('https://example.com');   // nav.goto -> CDP('Page.navigate')
  await page.waitForLoadState();            // waits.waitForLoadState -> CDP('Page.loadEventFired')
  const title = await page.title();         // evaluate(() => document.title)
  console.log('Page title is', title);
}
const loginBtn = page.locator('button#login');
await loginBtn.waitFor();                  
await loginBtn.click();                     
const {result} = await cdp('Network.getCookies');
console.log('Cookies:', result);

Key Source Files for the Ego-Lite Data Flow

File Role Link
src/run.ts CLI entry point, reads script, builds execution context, runs code run.ts
src/helpers.ts Constructs the public facing façade (page, browser, etc.) helpers.ts
src/cdp-eval.ts Low-level CDP wrapper (cdp, evaluate) and value decoding cdp-eval.ts
src/browser-runtime.ts Manages CDP session lifecycle, event buffering, snapshots browser-runtime.ts
src/state.ts Manages global mutable runtime state; provides the ego bridge state.ts
src/driver/* Implements concrete actions that call cdp() driver folder

Summary

The data flow pipeline in ego-lite lets developers run agent JavaScript directly in the browser.

  • The agent JS is read by runMain() and wrapped in an AsyncFunction.
  • The helper façade (page, locator, browser) drives cdp() calls.
  • The bridge inside global scope passes commands to the browser.
  • The browser-runtime layer serializes and handles the raw browser actions.
  • All responses flow back to the agent script efficiently using standard CDP serialization.
  • A console.log obstacle, buffered and flushed, removes complexity from the CLI.

Frequently Asked Questions

What is the entry point for agent JavaScript in ego-lite?

The entry point is the runMainCommand() function located in src/run.ts. It reads the JavaScript and then constructs the execution context to start the data flow.

How do helpers like page.goto() translate into browser actions?

When JavaScript calls page.goto(), the helper translates it into a cdp() invocation with the Page.navigate method. The cdp() function sends this command via the globalThis.ego bridge, which communicates with the browser through the Chrome DevTools Protocol.

What role does the src/browser-runtime.ts module play in the data flow?

The module is the central processing unit in the runtime for browser sessions. It manages CDP session IDs, buffers events, and handles high-level operations like element resolution and task spaces. During the "ego bridge" hands it an action, it performs the actual operation in the browser.

How are results decoded and returned to the agent?

Results are decoded in evaluate() function of src/cdp-eval.ts. It handles non-serializable values (like NaN or Infinity) and wraps the CDP response into friendly JavaScript values, ensuring the agent script receives data that is easily represented by standard JavaScript types. flushSink then writes the console output to the CLI.

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 →