# Session Invalidation Flow When Switching Task Spaces in ego-lite: A Deep Dive

> Understand the ego-lite session invalidation flow when switching task spaces. Learn how ego-lite automatically invalidates and re-establishes CDP sessions for seamless task space transitions.

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

---

**When you call `switchTaskSpace()` in ego-lite, the framework automatically invalidates the current CDP session and creates a fresh one bound to the newly-selected task space through a three-step validation and re-attachment process.**

The **session invalidation flow** is a critical mechanism in ego-lite that ensures AI agents operate in isolated browser contexts when navigating between task spaces. This article examines how the framework handles CDP session lifecycle management using actual source code from the `citrolabs/ego-lite` repository.

## How `switchTaskSpace` Triggers Session Invalidation

The entry point for task space switching is **`switchTaskSpace(nameOrId)`** in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts). This function performs ownership validation before delegating to the native runtime.

### Step 1: Resolving and Selecting the Target Space

When invoked, `switchTaskSpace` first calls `findTaskSpace` to locate the requested space. It enforces a strict ownership check:

- `ownership === "agent"` — fully agent-controlled
- `ownership === "agentDelegatedToUser"` — delegated but still agent-managed

If validation passes, control flows to **`selectTaskSpace`**, which invokes **`ego.useTaskSpace`** (see [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) lines 52-64). This native SDK call notifies the Ego runtime which tab should become the active CDP target for the current Node process.

```javascript
// Example: Switch to another agent-owned task space
await switchTaskSpace('my-new-space');   // throws if user-owned

// The runtime now knows space-B is active, but CDP session still points to space-A

```

The runtime switch happens immediately, but the **existing CDP session remains attached** to the previous tab. This is where lazy invalidation becomes essential.

## Step 2: Detecting Stale Sessions in `browserCdp`

The Ego runtime does **not** proactively detach CDP sessions when tabs change. Instead, staleness detection happens on the next CDP command through `browserCdp` in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts).

### The `ensureSession` Validation Logic

Every CDP-bound helper (`js()`, `cdp()`, element resolvers) routes through `browserCdp`, which calls `ensureSession` first. This function checks two conditions:

| Check | Purpose |
|-------|---------|
| **TTL expiration** | Session older than 2 seconds triggers refresh |
| **Target ID mismatch** | `state.sessionTargetId` differs from active tab |

If either condition is true, `ensureSession` calls **`invalidateSession()`** before proceeding (see [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) lines 46-55).

```javascript
// Pseudo-code illustrating the staleness detection
function ensureSession() {
  if (state.sessionId && !isExpired(state.sessionAt) && 
      state.sessionTargetId === getActiveTargetId()) {
    return;  // Session still valid
  }
  invalidateSession();  // ← clears all pending state
  // ... proceed to attach new session
}

```

This lazy evaluation pattern avoids unnecessary session churn during rapid task space switches without intervening CDP calls.

## Step 3: `invalidateSession` and Fresh Session Creation

The **`invalidateSession()`** function performs surgical cleanup of session state:

- Clears `state.sessionId` and `state.sessionTargetId`
- Wipes `state.sessionAt` timestamp
- Discards pending dialog handlers
- Flushes buffered CDP events
- Rejects/voids pending CDP promises

After invalidation, `ensureSession` executes the attachment sequence:

```javascript
// Re-attachment flow following invalidateSession()
const tabs = await ego.listTabs();              // enumerate available tabs
const targetTab = tabs.find(t => t.taskSpaceId === selectedSpaceId);
const { sessionId } = await Target.attachToTarget(targetTab.targetId);
await enablePageEvents(sessionId);              // Page.* events for DOM interaction

// Update state for future validation
state.sessionId = sessionId;
state.sessionTargetId = targetTab.targetId;
state.sessionAt = Date.now();

```

The new session is now **scoped exclusively** to the selected task space. All subsequent helper calls operate against this isolated context without additional agent intervention.

## Complete Invalidation Flow: Visual Summary

| Phase | Function | File | Action |
|-------|----------|------|--------|
| **Initiation** | `switchTaskSpace` → `selectTaskSpace` | [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) | Validates ownership, calls `ego.useTaskSpace` |
| **Detection** | `browserCdp` → `ensureSession` | [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) | Compares `sessionTargetId` vs. active tab; checks TTL |
| **Invalidation** | `invalidateSession` | [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) | Wipes `sessionId`, caches, pending dialogs, events |
| **Re-creation** | `ensureSession` (post-invalidation) | [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) | Attaches to new target via `Target.attachToTarget` |
| **Activation** | `enablePageEvents` | [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) | Enables Page domain for DOM operations |

## Practical Code Examples

### Automatic Session Recovery After Switch

```javascript
// Space A is currently active with an established CDP session
await js('document.title');  // executes against space A

// Switch to space B — session becomes stale but not yet invalidated
await switchTaskSpace('space-B');

// Next CDP call triggers full invalidation + re-attachment
const url = await js('window.location.href');  // new session to space B
console.log('Now operating on:', url);

```

### Invalidation Side-Effects Are Transparent

```javascript
await switchTaskSpace('checkout-flow');
await Promise.all([
  js('document.querySelector("#total").textContent'),   // triggers ensureSession
  js('document.querySelector("#tax").textContent')      // reuses fresh session
]);
// Both execute against checkout-flow's isolated context

```

## Key Implementation Files

Understanding the **session invalidation flow** requires familiarity with these source locations:

- **[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)** — `switchTaskSpace` implementation, ownership validation, `ego.useTaskSpace` delegation
- **[`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)** — `browserCdp`, `ensureSession`, `invalidateSession`, and CDP attachment logic
- **[`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts)** — Mutable runtime state including `sessionId`, `sessionTargetId`, `sessionAt`, and associated caches

These modules collectively ensure that task space isolation is enforced at the CDP protocol level, preventing cross-contamination between agent-owned and user-owned browsing contexts.

## Summary

- **`switchTaskSpace`** changes the active tab via `ego.useTaskSpace` but leaves the CDP session attached to the previous target
- **Stale sessions are detected lazily** in `ensureSession` through TTL checks and `targetId` comparison
- **`invalidateSession`** performs complete cleanup of session state, pending operations, and event buffers
- **Fresh sessions attach automatically** to the newly-selected task space's tab without manual agent intervention
- This **lazy invalidation pattern** balances performance with isolation guarantees across task space boundaries

## Frequently Asked Questions

### What triggers session invalidation in ego-lite?

Session invalidation is triggered when `ensureSession` detects either a **2-second TTL expiration** or a **mismatch between `state.sessionTargetId` and the currently active tab** selected by `switchTaskSpace`. This check runs on every CDP-bound operation, making invalidation lazy rather than immediate.

### Does `switchTaskSpace` immediately detach the CDP session?

**No.** The `switchTaskSpace` function only calls `ego.useTaskSpace` to update the runtime's active tab selection. The existing CDP session remains attached until the next CDP command flows through `browserCdp`, at which point `ensureSession` detects the staleness and invokes `invalidateSession`.

### What happens to pending operations during invalidation?

`invalidateSession` clears **all pending state**: unresolved CDP promises are rejected, buffered events are discarded, and dialog handlers are removed. This ensures no stale callbacks execute against the new task space's context. The agent must re-issue any interrupted operations after the switch completes.

### Can agents prevent automatic session invalidation?

**No.** The invalidation flow is mandatory and ensures security isolation between task spaces. Agents cannot bypass the `targetId` validation in `ensureSession`. However, agents can minimize session churn by batching CDP operations within a single task space before switching, reducing the frequency of re-attachment overhead.