# How ego-browser Manages CDP Sessions and Automatic Reconnections

> Discover how ego browser manages CDP sessions and reconnections with automatic reattachment, retry logic, and TTL-based refresh for seamless control.

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

---

**ego-browser maintains a single lightweight Chrome DevTools Protocol session per active tab with automatic reattachment, transparent retry logic, and a 2-second TTL-based refresh mechanism.**

`ego-browser` runs inside the **ego-lite** browser runtime and communicates with Chrome DevTools Protocol (CDP) through the global `ego.sendCDPMessage` API. All session handling is centralized in **[`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)** and consumed by high-level helpers like `goto` and `switchTab`. Understanding how `ego-browser` manages CDP sessions and reconnections is essential for building resilient browser automation agents.

## CDP Session Lifecycle

### Session Creation and Reuse

The `ensureSession()` function controls whether to reuse an existing session or create a new one. It checks the cached `state.sessionId` and compares its age against `SESSION_TTL_MS = 2000` milliseconds.

```ts
// browser-runtime.ts:7-9
// Pseudocode showing TTL check logic
if (state.sessionId && (Date.now() - state.sessionAt) < SESSION_TTL_MS) {
  return state.sessionId;  // reuse existing session
}
// otherwise, attach to new target

```

If the session exceeds the TTL, the runtime sends `Target.attachToTarget` via `rawCdp`, stores the returned `sessionId` in `state.sessionId`, and records the target ID in `state.sessionTargetId` ([`browser-runtime.ts:27-35`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L27-L35)).

### Page Event Enablement

After successful attachment, `Page.enable` is called once per session. A `Set` named `pageEnabledSessions` tracks which sessions already have page events enabled, preventing redundant calls ([`browser-runtime.ts:5-13`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L5-L13)).

### Activity Tracking and Invalidation

Every successful attach updates `state.sessionAt = Date.now()`. When targets detach or are destroyed, the runtime clears the cached session:

```ts
// browser-runtime.ts:52-64
function handleTargetDetached(sessionId: string) {
  if (sessionId === state.sessionId) {
    invalidateSession();
    clearPendingDialogs();
  }
}

```

## Automatic Reconnection Mechanism

The `browserCdp()` wrapper provides transparent session recovery. When a CDP call fails with a *session-lost* error matching the `SESSION_LOST` regex, and the call was not explicitly targeted at a specific session, the runtime:

1. Calls `invalidateSession()` to clear stale state
2. Invokes `ensureSession()` to establish a fresh session
3. Retries the original CDP method with the new session

([`browser-runtime.ts:94-103`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L94-L103))

```ts
// browserCdp() wrapper with automatic retry
try {
  return await rawCdp(method, params);
} catch (err) {
  if (isSessionLost(err) && !params.sessionId) {
    invalidateSession();
    await ensureSession();
    return rawCdp(method, params);  // retry once
  }
  throw err;
}

```

## TTL-Based Session Design

The deliberately short 2-second TTL ensures frequent session refresh. This design is safe because the underlying `ego` bridge can re-attach to the same target with negligible cost. Benefits include:

- **Rapid recovery** from transient network hiccups
- **Quick adaptation** to tab navigation and page reloads
- **Minimal stale state** accumulation

## Session State Storage

All session metadata lives in the singleton `state` object defined in [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts):

```ts
// state.ts
export const state = {
  sessionId: null as string | null,        // active CDP session ID
  sessionTargetId: null as string | null,  // attached target ID
  sessionAt: 0,                            // timestamp of last attach
  sessionInflight: null as Promise<string> | null,  // deduplication lock
  preferredTargetId: null as string | null, // for tab switching
};

```

([[`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts))

## Higher-Level Helper Integration

Navigation helpers delegate session management to the runtime layer:

- **`goto`** calls `browserCdp("Page.navigate", ...)` which automatically resolves the active session
- **`switchTab`** explicitly invalidates the old session and sets `preferredTargetId` so the next attach targets the newly activated tab ([`nav.ts:54-60`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/nav.ts#L54-L60))

```ts
// nav.ts:57-60 - switchTab invalidation pattern
await invalidateSession();
setPreferredTargetId(newTargetId);
// next ensureSession() will attach to preferredTargetId

```

## Error Handling and Resilience

If `ego.sendCDPMessage` itself fails (for example, when the user's task becomes inactive), `handleSendError` rejects all pending requests with a unified `EgoError`. This prevents orphaned promises from infinitely blocking ([`browser-runtime.ts:22-30`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts#L22-30)).

## Practical Code Examples

### Automatic Session Handling in Navigation

```ts
import { goto } from "./driver/nav.js";

await goto("https://example.com");
// Session created or reused automatically; no manual management needed

```

### Manual Session Retrieval (Rarely Required)

```ts
import { ensureSession } from "./browser-runtime.js";

const sessionId = await ensureSession();
console.log("Current CDP session:", sessionId);

```

### Forced Reconnection After Target Change

```ts
import { invalidateSession, setPreferredTarget } from "./browser-runtime.js";
import { switchTab } from "./driver/nav.js";

await switchTab("target-123");
// Invalidates old session, attaches to new target
// Subsequent calls use fresh session automatically

```

### Low-Level CDP with Transparent Retry

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

const result = await browserCdp("Runtime.evaluate", { 
  expression: "document.title" 
});
console.log(result.result.value);
// Automatically re-attaches and retries if session is lost

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| [[`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts) | Core session management: `browserCdp`, `ensureSession`, `invalidateSession`, automatic reconnection |
| [[`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/nav.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/nav.ts) | Navigation helpers that leverage the session layer |
| [[`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) | Shared mutable state for session identifiers and metadata |

## Summary

- **Single session per tab**: `ego-browser` maintains one lightweight CDP session tied to the active target
- **2-second TTL**: Sessions refresh frequently to minimize stale state recovery time
- **Transparent retry**: `browserCdp()` wrapper automatically re-attaches and retries on session loss
- **Centralized state**: All session metadata in [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts) singleton with clear invalidation semantics
- **Helper delegation**: High-level functions like `goto` and `switchTab` rely on the runtime layer rather than managing sessions directly

This architecture ensures browser automation agents continue operating through navigation, tab switches, and transient connection issues without explicit error handling in user code.

## Frequently Asked Questions

### What triggers a CDP session reconnection in ego-browser?

A reconnection triggers when `browserCdp()` detects a session-lost error (matching the `SESSION_LOST` regex) or when the session TTL expires. The runtime automatically invalidates the stale session, creates a new one via `ensureSession()`, and retries the failed call once.

### Why is the session TTL only 2 seconds?

The short `SESSION_TTL_MS = 2000` milliseconds ensures rapid recovery from state changes. Since the `ego` bridge re-attaches to targets with negligible overhead, frequent refresh prevents accumulation of stale session state while maintaining near-zero performance impact.

### How does tab switching affect the CDP session?

`switchTab` explicitly calls `invalidateSession()` and sets `preferredTargetId` in state. The next CDP operation triggers `ensureSession()`, which attaches to the preferred target rather than the previous one, ensuring seamless continuation of automation on the new tab.

### Can I manually control session lifecycle in ego-browser?

Direct session control is rarely needed. Use `ensureSession()` to force attachment, `invalidateSession()` to clear state, or `setPreferredTarget()` to influence the next attach target. Most workflows should rely on the automatic handling in `browserCdp()` and navigation helpers.