# ego-browser Session TTL Mechanism: Why It's Set to 2 Seconds

> Discover the ego-browser session TTL mechanism, explaining the 2-second cache for CDP sessions to prevent stale states and optimize connections. Learn how ensureSession() works.

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

---

**The ego-browser session TTL mechanism caches Chrome DevTools Protocol (CDP) sessions for exactly 2 seconds to balance connection reuse against stale state detection, implemented via `SESSION_TTL_MS` in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) and enforced by the `ensureSession()` function.**

When automating browser interactions through the Chrome DevTools Protocol, creating a new CDP session is relatively expensive. The ego-browser runtime in **citrolabs/ego-lite** solves this with a lightweight caching system that reuses sessions—but only briefly. This article explains the implementation details, the rationale behind the 2-second window, and how the `ensureSession()` function governs session lifecycle.

---

## How the Session TTL Mechanism Works

The core logic lives in **[`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)**, where a constant defines the TTL window:

```ts
// src/browser-runtime.ts
const SESSION_TTL_MS = 2000;   // 2 seconds

```

When any helper needs a CDP session, it calls **`ensureSession()`**. This function maintains two pieces of state:

- **`state.sessionId`** — the identifier of the currently cached session
- **`state.sessionAt`** — the `Date.now()` timestamp when the session was last obtained

The TTL check is straightforward:

```ts
// src/browser-runtime.ts (excerpt)
if (state.sessionId && Date.now() - state.sessionAt < SESSION_TTL_MS) {
    return state.sessionId;
}

```

If both conditions pass—there's a cached session **and** it's younger than 2 seconds—the cached `sessionId` returns immediately. Otherwise, `ensureSession()` creates a **new CDP session**, updates both state variables, and returns the fresh identifier.

This guarantees no session ever exceeds the 2-second age limit.

---

## Why 2 Seconds? The Design Rationale

The 2000-millisecond value reflects three operational constraints typical of ego-browser workloads:

### Balance Between Performance and Reliability

Reusing a session avoids repeatedly calling `ego.sendCDPMessage` to attach new sessions. However, browser tabs can close, navigate away, or become invalid at any moment. A **short TTL ensures frequent validity checks** without excessive recreation overhead.

### Fast Turnover for Short-Lived Scripts

ego-browser targets AI-generated scripts that perform burst operations—click a button, extract text, exit. A 2-second window covers these quick sequences while allowing rapid cleanup of stale sessions.

### Avoiding Stale State in Concurrent Rounds

The harness executes multiple script "rounds" rapidly. The brief TTL forces a fresh session snapshot between rounds if the previous session aged out, preventing helpers from operating on outdated DOM or stale CDP objects.

The consequences of deviation are clear: a longer TTL risks *"session not attached"* errors from dead sessions; a shorter TTL degrades performance through constant recreation. The 2-second value is a pragmatic compromise validated by production use.

---

## Practical Examples

### Manual Session Management

```ts
// Example: Manually invoking ensureSession()
import { ensureSession } from "./browser-runtime.js";

async function demo() {
  // First call creates a new session
  const sess1 = await ensureSession();
  console.log("New session:", sess1);

  // Within 2 seconds the same session is reused
  const sess2 = await ensureSession();
  console.log("Re-used session:", sess2); // sess2 === sess1

  // Wait >2 seconds → a new session is created
  await new Promise(r => setTimeout(r, 2100));
  const sess3 = await ensureSession();
  console.log("New session after TTL:", sess3); // sess3 !== sess1
}
demo();

```

### Implicit Usage Through Helpers

```ts
// Example: Using a helper that internally calls ensureSession()
import { click } from "./driver/pointer.js";

async function clickButton() {
  // `click` will call ensureSession() under the hood.
  await click({ loc: "css:#submit" });
}

```

Helpers throughout the codebase rely on `ensureSession()` transparently—you rarely interact with it directly, but it governs every CDP operation.

---

## Key Source Files

The session TTL mechanism spans these locations in the ego-lite repository:

- **[`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)** — Defines `SESSION_TTL_MS`, implements `ensureSession()`, and maintains session state
- **[`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/nav.ts)** — Calls `ensureSession()` before navigation actions
- **[`src/driver/screencast.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/screencast.ts)** — Uses the cached session for screen capture
- **[`src/driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts)** — Relies on the session for DOM change observation
- **`src/browser-runtime.test.mjs`** — Validates TTL behavior including cached vs. expired session handling

These files demonstrate how ego-browser coordinates session lifecycle across navigation, observation, and interaction helpers.

---

## Summary

- The **session TTL mechanism** in ego-browser caches CDP sessions for 2 seconds via `SESSION_TTL_MS`
- **`ensureSession()`** in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) checks `state.sessionId` and `state.sessionAt` against this window
- The **2-second value** balances connection reuse with rapid detection of invalid tab states
- Helpers throughout `src/driver/` implicitly rely on this mechanism without manual session management
- The implementation prevents both stale session errors and unnecessary recreation overhead

---

## Frequently Asked Questions

### How does ego-browser handle session expiration during long-running operations?

If an operation exceeds 2 seconds, subsequent calls to `ensureSession()` automatically create a fresh session. The function checks timestamp freshness every invocation, so long operations simply trigger new session attachment on their next CDP interaction. No manual cleanup is required.

### Can I configure the session TTL value?

Currently no. The `SESSION_TTL_MS` constant is hardcoded at 2000ms in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts). Modifying it requires editing the source and rebuilding, as the value is not exposed through the public API. The maintainers selected this value based on typical workload patterns.

### What happens if a browser tab closes while a session is cached?

The next `ensureSession()` call after TTL expiration detects the stale condition and creates a new session. If the tab closed, the subsequent CDP operation will fail with an appropriate error rather than attempting to use the dead session. The short 2-second window minimizes the window for this race condition.

### Why not use a persistent session for the entire script duration?

Browser tabs can navigate, crash, or be closed by external processes. A persistent session would accumulate state mismatches between the CDP client's view and actual browser state. The 2-second TTL forces periodic reattachment that aligns client and server state while still amortizing session creation costs across rapid successive operations.