# Understanding the CDP Transport Layer in ego-lite: Architecture and Implementation

> Explore the CDP transport layer in ego-lite. This promise-based abstraction simplifies browser interaction by managing sessions retries and event buffering.

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

---

**The CDP transport layer in ego-lite is a promise-based abstraction around the Chrome DevTools Protocol that handles session management, automatic retries, and event buffering, allowing agents to interact with browser internals without managing low-level message serialization.**

The citrolabs/ego-lite repository provides a browser automation framework built on the Chrome DevTools Protocol (CDP). At its core, the CDP transport layer translates high-level JavaScript calls into raw CDP messages, manages page session lifecycles, and handles transient failures transparently. This system resides primarily in [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts) and exposes a clean `browserCdp` API that hides the complexity of tab attachment, timeout handling, and session recovery.

## Core Architecture

The transport layer follows a layered design that separates raw message serialization from session lifecycle management. This separation allows the system to recover gracefully from transient failures while maintaining a consistent API surface for agent scripts.

### The Entry Point: `browserCdp`

The primary interface for CDP communication is the `browserCdp` function defined at lines 79‑85 of [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts). This function serves as the gatekeeper for all CDP traffic, implementing a test-override check (`state.cdpOverride`) before processing any real commands.

When invoked, `browserCdp` determines the appropriate session context automatically. If the requested method is not a top-level *Browser* or *Target* command, it delegates to `ensureSession()` to guarantee a valid page session exists before transmitting the message. This automatic session resolution ensures that domain-specific commands (such as `Page.getNavigationHistory` or `Runtime.evaluate`) always execute within the correct browsing context.

### Raw Message Transport: `rawCdp`

Beneath the session management layer, the `rawCdp` function (lines 38‑77 of [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)) handles the actual wire protocol. It constructs CDP request objects with the standard `{id, method, params, sessionId?}` structure and transmits them via `globalThis.ego.sendCDPMessage`.

The function registers response handlers using `runtime.onCDPMessage` and error callbacks via `runtime.onSendCDPMessageError`. Incoming responses route through a `pending` map keyed by request ID, enabling promise-based resolution of asynchronous CDP operations. A hard timeout of **15 seconds** (`RESPONSE_TIMEOUT_MS = 15000`) ensures that unresponsive commands reject rather than hang indefinitely.

### Session Lifecycle Management: `ensureSession`

Session state management occurs in `ensureSession` (lines 107‑143 of [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)). This function implements an intelligent caching mechanism that reuses existing sessions while they remain fresh, using a time-to-live (TTL) threshold of approximately **2 seconds**.

When no valid session exists, the system:
1. Lists available tabs via `ego.listTabs()`
2. Selects the preferred or active tab
3. Attaches to the target using `Target.attachToTarget`
4. Enables page-level events for the session
5. Caches the resulting `sessionId` for subsequent calls

Session loss detection relies on a `SESSION_LOST` regex (defined at lines 9‑10) that matches error messages containing "Session not found" or "Target closed". When detected, the system invalidates the cached session and triggers recovery logic.

### Error Recovery and Retry Logic

The transport layer implements automatic resilience against transient session failures. When `browserCdp` encounters a `SESSION_LOST` error at lines 95‑104, it checks whether the request was explicitly targeted at the browser level. For page-level commands, the system:
- Invalidates the stale session via `invalidateSession()`
- Retries the request with a fresh session
- Preserves the original caller's intent without throwing

This retry mechanism operates transparently, ensuring that agents experience minimal disruption when tabs close or devtools sessions detach unexpectedly.

### Event Handling Infrastructure

The runtime buffers CDP events in an internal `events` array and exposes three utility functions for event consumption (referenced at lines 64‑95 of [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)):
- **`drainBrowserEvents`** – Retrieves and clears the current event buffer
- **`waitForBrowserEvent`** – Returns a promise that resolves when a specific event type arrives
- **`subscribeBrowserEvent`** – Registers persistent listeners for domain-specific events (Network, DOM, etc.)

These helpers allow agents to monitor browser activity without manually parsing the low-level CDP message stream or managing event subscription state.

### Centralized State Management

All session metadata lives in [`package/ego-browser/src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts), exported as a singleton. The transport layer tracks:
- **`state.sessionId`** – Active CDP session identifier
- **`state.sessionAt`** – Timestamp of last session activity
- **`state.sessionTargetId`** – Target tab identifier
- **`state.sessionInflight`** – Pending operation counter

This centralization guarantees that every helper function (including those in [`cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/cdp-eval.ts)) operates against a consistent CDP context, preventing race conditions during rapid-fire automation sequences.

## High-Level Helper APIs

Building upon the transport foundation, [`package/ego-browser/src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/cdp-eval.ts) (lines 7‑10) exposes convenient wrappers for common operations:
- **`cdp(method, params?)`** – Arbitrary CDP command execution
- **`js(expression)`** – JavaScript evaluation in the page context

These functions import `browserCdp` directly and provide type-safe interfaces for agent scripts, transforming raw protocol operations into intuitive JavaScript function calls.

## Practical Usage Examples

The following examples demonstrate interaction patterns with the ego-lite CDP transport layer:

```javascript
// Retrieve navigation history for the active page
const history = await cdp('Page.getNavigationHistory');
console.log('Current index:', history.currentIndex);

```

```javascript
// Evaluate JavaScript in the page context and return the result
const title = await js('document.title');
console.log('Page title:', title);

```

```javascript
// Enable network monitoring for the current session
await browserCdp('Network.enable');
const events = await drainBrowserEvents();
console.log('Captured network events:', events);

```

```javascript
// Direct low-level usage with full control over session targeting
const response = await browserCdp('Runtime.evaluate', {
  expression: 'window.location.href',
  returnByValue: true
});

```

## Summary

- The **CDP transport layer** in citrolabs/ego-lite abstracts Chrome DevTools Protocol complexity through the `browserCdp` function in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts).
- **Session management** automatically handles tab attachment, 2-second TTL caching, and transparent retry logic when sessions disconnect.
- **Raw transport** (`rawCdp`) implements 15-second timeouts and promise-based response routing via `globalThis.ego.sendCDPMessage`.
- **Event buffering** supports asynchronous monitoring of browser events through `drainBrowserEvents` and `waitForBrowserEvent`.
- **State centralization** in [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts) ensures consistent context across all automation helpers.

## Frequently Asked Questions

### How does ego-lite handle CDP session timeouts?

The transport layer enforces a default timeout of **15 seconds** (`RESPONSE_TIMEOUT_MS`) in the `rawCdp` function. If the browser fails to respond within this window, the promise rejects with a `EGO_CDP_SEND_FAILED` error (defined in [`ego-errors.ts`](https://github.com/citrolabs/ego-lite/blob/main/ego-errors.ts)), allowing calling code to implement fallback logic or retry strategies.

### Can I use the CDP transport without automatic session management?

Yes. While the high-level `cdp()` and `js()` helpers automatically manage sessions, you can import `browserCdp` directly from [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) and pass explicit session identifiers in the parameters. However, for Browser and Target domain commands, the system bypasses session resolution automatically, allowing direct communication with the browser endpoint.

### What happens when a tab closes during automation?

When a command fails with a "Session not found" or "Target closed" message (matched by the `SESSION_LOST` regex), the transport layer invalidates the cached session, re-attaches to an available target via `ensureSession`, and retries the original request. This recovery happens transparently unless the original request explicitly targeted a specific closed browser session.

### Where is the CDP transport state stored?

All session state—including `sessionId`, `sessionAt`, and `sessionTargetId`—resides in the singleton exported from [`package/ego-browser/src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts). This design ensures that multiple helper modules (such as [`cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/cdp-eval.ts) and [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts)) share a consistent view of the active CDP context without passing state objects through every function call.