# How the Ego Lite CDP Transport Layer Handles Session Management and Auto Re-Attach

> Discover how Ego Lite CDP transport layer manages sessions, caching them for 2 seconds and automatically re-attaching on connection loss. Learn about on-demand session creation and command retries.

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

---

**The Ego Lite CDP transport layer caches sessions for 2 seconds, automatically creates new sessions on demand, and retries failed commands with fresh sessions when the underlying Chrome DevTools Protocol connection is lost.**

The **Chrome DevTools Protocol (CDP)** transport layer in [citrolabs/ego-lite](https://github.com/citrolabs/ego-lite) provides browser automation agents with a resilient, self-healing connection to browser targets. This article examines how [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) implements **session management and auto re-attach** mechanisms that shield calling code from connection volatility.

## Session Lifecycle and Caching Strategy

The runtime maintains session state through a **time-bound caching strategy** to minimize unnecessary re-attachments.

### Session TTL and Cache Validation

Every session remains valid for **2 seconds** (`SESSION_TTL_MS = 2000` at [line 5](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L5)). The `ensureSession()` function checks two conditions before reusing a cached session:

- `state.sessionId` exists
- `state.sessionAt` timestamp is within the TTL window

```javascript
// Simplified logic from lines 7-10 of browser-runtime.ts
if (state.sessionId && Date.now() - state.sessionAt < SESSION_TTL_MS) {
  return state.sessionId;  // Reuse fresh session
}

```

This short TTL balances **connection stability** against ** tab switch responsiveness** — agents benefit from connection reuse during rapid command sequences while automatically picking up navigation or tab changes.

### Deduplicating Concurrent Session Creation

When multiple commands execute simultaneously without a valid session, `ensureSession()` prevents redundant `Target.attachToTarget` calls. The `state.sessionInflight` promise ([lines 11-13](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L11-L13)) ensures all concurrent callers await a single session creation operation:

```javascript
// Parallel callers share the same pending promise
if (state.sessionInflight) {
  return state.sessionInflight;  // Wait for in-progress creation
}
state.sessionInflight = createSession();  // Single creation attempt

```

## Attaching to Browser Targets

When the cache misses, `ensureSession()` executes a full attachment flow ([lines 14-36](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L14-L36)):

1. **List available tabs** via `browserEgo().listTabs()`
2. **Select the preferred active tab** from the returned array
3. **Compare against cached target** — skip re-attachment if unchanged
4. **Call `Target.attachToTarget`** for new or changed targets
5. **Store session ID and timestamp**, clear `sessionInflight`

### Enabling Page Events Per Session

After successful attachment, `enablePageEvents(sessionId)` ([lines 38-45](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L38-L45)) ensures the runtime receives page-level CDP events. A `pageEnabledSessions` Set tracks which sessions have already been initialized, preventing duplicate `Page.enable` commands:

```javascript
// From the source: enablePageEvents guards against duplicate enables
if (!pageEnabledSessions.has(sessionId)) {
  await rawCdp('Page.enable', {}, sessionId);
  pageEnabledSessions.add(sessionId);
}

```

## Automatic Re-Attach on Session Loss

The `browserCdp()` wrapper function implements **transparent recovery** from session failures without requiring caller intervention.

### Detecting Session-Lost Conditions

The runtime matches error messages against the `SESSION_LOST` regex pattern ([lines 9-10](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L9-L10)) to identify when the browser has detached or destroyed the target. Common triggers include:

- Target crashed or was closed
- Navigation to a different origin (in some Chrome configurations)
- Browser process restart

### Retry Logic Implementation

When `browserCdp()` encounters a session-lost error **and no explicit session ID was provided**, it automatically recovers ([lines 95-102](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L95-L102)):

```javascript
// Simplified from browser-runtime.ts lines 95-102
async function browserCdp(method, params, explicitSessionId) {
  try {
    return await rawCdp(method, params, explicitSessionId ?? state.sessionId);
  } catch (err) {
    if (!explicitSessionId && SESSION_LOST.test(err.message)) {
      invalidateSession();           // Clear stale state
      await ensureSession();         // Fresh attachment
      return browserCdp(method, params);  // Retry once
    }
    throw err;  // Propagate non-recoverable errors
  }
}

```

**Critical distinction**: Explicit session IDs bypass auto-retry. This allows callers who manage their own sessions to handle failures according to their own logic.

## Explicit Session Invalidation

The runtime exposes `invalidateSession()` for cases requiring **immediate session refresh**, such as:

- Known navigation events
- Target switching
- Recovering from extended operation timeouts

```javascript
// Force immediate session invalidation
await invalidateSession();                   // Clears state.sessionId
const fresh = await ensureSession();         // Guaranteed new attachment
await browserCdp('Page.reload', {}, fresh);   // Execute on fresh session

```

### Reactive Invalidation via CDP Events

The runtime also listens for browser-initiated session termination. When `Target.detachedFromTarget` or `Target.targetDestroyed` events arrive for the active target, `invalidateSession()` executes automatically ([lines 52-66](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L52-L66)):

- Clears `state.sessionId` and `state.sessionAt`
- Removes session from `pageEnabledSessions`
- Clears `pendingDialogs` buffer

This proactive cleanup prevents the 2-second TTL window from masking target destruction events.

## Practical Usage Patterns

### Basic Automatic Mode

For most automation scenarios, rely on the default behavior — no session management required:

```javascript
// Runtime handles session creation and recovery automatically
const result = await browserCdp('Runtime.evaluate', {
  expression: 'document.title'
});
// Returns: { result: { type: 'string', value: 'Page Title' } }

```

### Explicit Session Control

When managing multiple targets or implementing custom retry logic, obtain and pass session IDs explicitly:

```javascript
// Acquire current session for reuse across multiple calls
const session = await ensureSession();

// All calls use the same session; no auto-retry on failure
await browserCdp('DOM.enable', {}, session);
await browserCdp('DOM.querySelector', { nodeId: 1, selector: 'h1' }, session);

// Caller handles session loss explicitly

```

### Post-Navigation Recovery

After navigation that invalidates the target, force session refresh:

```javascript
// Navigation likely destroyed previous target
await browserCdp('Page.navigate', { url: 'https://example.com' });
await invalidateSession();  // Clear potentially stale session

// Subsequent calls attach to new target automatically
const metrics = await browserCdp('Performance.getMetrics');

```

## Architecture: Key Files and Responsibilities

| File | Role in Session Management |
|---|---|
| **[`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)** | Core implementation: `ensureSession()`, `invalidateSession()`, `browserCdp()`, event listeners, and state mutations |
| **[`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts)** | Public API surface exposing `browserCdp` to agent scripts |
| **[`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts)** | Mutable state container: `sessionId`, `sessionAt`, `sessionInflight`, `pageEnabledSessions` |
| **[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)** | Context binding exposing session helpers to evaluated agent code |

## Summary

- **Session TTL**: 2-second cache window minimizes re-attachment overhead while responding to tab changes
- **Concurrent deduplication**: `sessionInflight` promise prevents redundant session creation under load
- **Automatic recovery**: `browserCdp()` detects session-loss errors and retries once with fresh attachment — unless caller supplies explicit session ID
- **Event-driven cleanup**: CDP `Target.*` events trigger immediate invalidation, keeping state synchronized with browser reality
- **Explicit control**: `ensureSession()` and `invalidateSession()` APIs support advanced use cases requiring manual session management

## Frequently Asked Questions

### How long does Ego Lite cache a CDP session?

The cache duration is **2 seconds** (`SESSION_TTL_MS = 2000`). After this window expires, `ensureSession()` creates a new attachment even if the previous session ID remains technically valid in the browser.

### Can I disable automatic re-attach behavior?

Yes. Pass an explicit `sessionId` to `browserCdp()` — this bypasses the retry logic entirely. All session management becomes your responsibility, including detection and recovery from `SESSION_LOST` errors.

### What happens when multiple commands run without a cached session?

Concurrent callers automatically share a single session creation operation through the `sessionInflight` promise mechanism. Only one `Target.attachToTarget` call executes; all waiting callers receive the same session ID.

### When should I call `invalidateSession()` manually?

Call it after operations known to destroy or replace the target — particularly **cross-origin navigation** or **tab closure/replacement**. Also use it when implementing custom recovery logic that must bypass the 2-second TTL window.