# What Components Does the ego‑browser Runtime Own? A Deep Dive into ego‑lite's Core CDP Layer

> Explore the ego-browser runtime's core CDP layer and discover its key components including transport, session management, event buffering, and runtime services.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: deep-dive
- Published: 2026-08-29

---

**The ego‑browser runtime owns the Chrome DevTools Protocol (CDP) transport, session management, event buffering, and runtime-wide services that higher-level automation helpers depend on.**

The **ego‑browser runtime** is the foundational layer of the [ego‑lite](https://github.com/citrolabs/ego-lite) agent-automation stack. It sits below navigation, pointer actions, and site-specific learning systems, providing deterministic low-level browser control through CDP. Understanding what the **ego-browser runtime** owns helps you extend the codebase or debug protocol-level issues.

## CDP Transport and Message Plumbing

At its core, the runtime manages all raw CDP communication. This happens through two key functions in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts):

- **`rawCdp()`** – Sends arbitrary CDP messages via `globalThis.ego.sendCDPMessage` and returns parsed JSON responses
- **`browserCdp()`** – A wrapped variant that injects the current session ID automatically

```typescript
import { browserCdp } from "./browser-runtime.js";

// Execute a raw CDP command through the runtime
const version = await browserCdp("Browser.getVersion", {}, sessionId);

```

The transport layer handles WebSocket-level framing, message correlation via `id` fields, and error conversion through `handleSendError()` (lines 24–33). When `globalThis.ego.sendCDPMessage` fails, the runtime produces standardized `EgoError` objects with consistent wording.

## Session Management and Lifecycle

The **ego-browser runtime** owns session attachment and caching. The `ensureSession()` function (lines 107–154):

1. Checks [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts) for a cached session ID with 2-second TTL
2. Attaches to the current target via `Target.attachToTarget` if needed
3. Returns a valid session for subsequent CDP calls

When sessions detach unexpectedly—whether from target crashes or navigation—the runtime automatically re-attaches. Call `invalidateSession()` to force a fresh attachment on the next call.

```typescript
import { ensureSession, invalidateSession } from "./browser-runtime.js";

async function robustOperation() {
  try {
    const session = await ensureSession();
    return await browserCdp("Runtime.evaluate", { expression: "1+1" }, session);
  } catch (err) {
    invalidateSession(); // Force re-attachment for next attempt
    throw err;
  }
}

```

Session state lives in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts), which exports a singleton storing the current ID, TTL timestamp, and runtime overrides.

## Event Buffering and Subscription System

The runtime maintains a **ring buffer of 10,000 CDP events** for all messages not explicitly subscribed to. This enables retrospective inspection and race-condition-free event handling.

| Component | Purpose | Location |
|-----------|---------|----------|
| `events` array | Circular buffer storage | Lines 8–11 |
| `MAX_BUFFERED_EVENTS` | 10,000 entry limit | Constant definition |
| `drainBrowserEvents()` | Consume buffered events in chronological order | Lines 58–66 |

For targeted event handling, the runtime provides:

- **`subscribeBrowserEvent(method, listener)`** – Register persistent listeners for specific CDP methods (e.g., `Target.detachedFromTarget`)
- **`waitForBrowserEvent(predicate, timeout)`** – Promise-based wait that resolves when incoming events match a condition

```typescript
import { waitForBrowserEvent, subscribeBrowserEvent } from "./browser-runtime.js";

// One-off wait for navigation completion
const loaded = await waitForBrowserEvent(
  (ev) => ev.method === "Page.loadEventFired",
  30_000
);

// Persistent subscription for target detachments
subscribeBrowserEvent("Target.detachedFromTarget", (ev) => {
  console.warn("Target lost:", ev.params.targetId);
});

```

The `eventSubscribers` set (lines 88–96) tracks persistent listeners, while `eventWaiters` holds transient promise resolvers matched in `handleMessage()` (lines 69–84).

## JavaScript Dialog Tracking

Page dialogs (alerts, confirms, prompts) require special tracking because they block execution. The runtime owns `pendingDialogs`, a Map keyed by `targetId` that stores:

- `Page.javascriptDialogOpening` events when dialogs appear
- `Page.javascriptDialogClosed` events when they dismiss

Drivers query this map to detect and handle blocking dialogs without polling CDP repeatedly.

## Snapshot Reference Mapping

After accessibility tree snapshots, the runtime converts backend node IDs to a workable `RefMap` via `browserSnapshotRefsToRefMap()` (lines 293–326). This utility bridges CDP's `Accessibility.getFullAXTree` responses with the higher-level element resolver in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts).

## What the Runtime Explicitly Does NOT Own

The **ego-browser runtime** is deliberately thin. Per the source architecture, these responsibilities live elsewhere:

| Responsibility | Owner Module |
|---------------|--------------|
| Navigation and history | [`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/nav.ts) |
| Pointer actions (clicks, drags) | [`src/driver/pointer.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/pointer.ts) |
| Keyboard input | [`src/driver/keyboard.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/keyboard.ts) |
| Element resolution from selectors | [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) |
| Site-specific learning data | Learning subsystem (separate package) |
| Public helper API surface | [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) and [`src/help-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/help-runtime.ts) |

These modules import from the runtime but the runtime has no reverse dependencies—maintaining clean layer separation.

## Key Files and Their Roles

- **[`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)** – Core runtime implementation with all components above
- **[`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts)** – Singleton state storage consumed by the runtime
- **[`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts)** – Thin `cdp()` and `js()` wrappers built on runtime primitives
- **`src/driver/*.ts`** – High-level automation building on the runtime API
- **[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)** – Public-facing helpers that agents import; forwards to runtime internally

## Summary

- The **ego-browser runtime** owns CDP transport, session lifecycle, and event infrastructure in ego-lite
- Key functions: `browserCdp()`, `ensureSession()`, `waitForBrowserEvent()`, `subscribeBrowserEvent()`, `browserSnapshotRefsToRefMap()`
- Core file: [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) with supporting state in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts)
- 10,000-entry ring buffer enables retrospective event inspection
- Explicitly excludes navigation, pointer actions, and site logic—those live in `src/driver/*`

## Frequently Asked Questions

### How does the runtime handle session expiration?

The runtime caches session IDs with a 2-second TTL in [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts). `ensureSession()` checks this cache before attaching; `invalidateSession()` clears it to force re-attachment. Automatic re-attachment also triggers on detected session loss.

### Can I use the runtime directly without the driver helpers?

Yes. Import from [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) for full control over CDP commands, event waiting, and session management. The driver helpers in `src/driver/*` are optional conveniences built atop these primitives.

### What's the difference between `subscribeBrowserEvent` and `waitForBrowserEvent`?

`subscribeBrowserEvent()` registers persistent listeners for specific CDP methods that fire repeatedly. `waitForBrowserEvent()` creates one-time promise-based waits that resolve when a predicate matches any incoming event, then auto-cleanup.

### How large is the event buffer and can it be configured?

The buffer holds 10,000 events via `MAX_BUFFERED_EVENTS` (line 9). This is currently a compile-time constant; adjust it in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) and rebuild if your use case demands different retention.