# How Ego-Lite Handles Buffered CDP Events: Browser Runtime Architecture

> Discover how Ego-Lite efficiently manages buffered CDP events using a capped in-memory queue and selective direct delivery, optimizing memory for browser runtime architecture.

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

---

**Ego-Lite buffers Chrome DevTools Protocol events in a capped in-memory queue that automatically discards the oldest entries when exceeding 10,000 events, while supporting selective direct delivery to registered subscribers to optimize memory usage.**

The [citrolabs/ego-lite](https://github.com/citrolabs/ego-lite) repository implements a specialized CDP client within its browser runtime to manage high-frequency Chrome DevTools Protocol traffic. Understanding how ego-lite handles buffered CDP events is essential for developers building automation agents that interact with headless Chrome instances, as the buffering strategy directly impacts memory consumption and event retrieval patterns.

## The In-Memory Event Queue Structure

### The events Array and MAX_BUFFERED_EVENTS Constant

At the core of ego-lite's buffering mechanism is a simple in-memory array named **`events`**, defined in [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts). To prevent unbounded memory growth during long-running browser sessions, the runtime enforces a hard cap of **10,000** events through the `MAX_BUFFERED_EVENTS` constant declared at lines 6-9.

When incoming CDP traffic pushes the queue beyond this limit, the runtime immediately truncates the oldest surplus entries using `Array.prototype.splice` at lines 87-90. This ensures the buffer maintains a predictable memory footprint regardless of session duration or event frequency.

## How CDP Events Enter the Buffer

### The handleMessage Function Logic

Every raw CDP message flows through the `handleMessage` function (lines 86-92), which discriminates between request responses and autonomous events. Only messages **without** an `id` property—indicating they are notifications rather than command responses—are pushed onto the `events` array.

The implementation applies the buffer cap check during this insertion phase. If the queue exceeds 10,000 entries after the push operation, the code calculates the overflow count and removes the oldest events from the front of the array, maintaining the most recent telemetry for analysis.

### Screencast Frame Handling

The buffering logic contains special provisions for screencast frames. Even when a subscriber receives direct delivery of a `Page.screencastFrame` event, the runtime still adds that frame to the general buffer. This ensures complete session recording while avoiding double-counting for other event types that support selective delivery.

## Consuming Buffered Events

### Draining the Queue with drainBrowserEvents()

Consumers retrieve accumulated telemetry through the **`drainBrowserEvents()`** function implemented at lines 64-67. This method returns a shallow copy of the current `events` array and atomically clears the internal queue in a single operation.

The shallow copy semantics mean that while the array container is new, the event objects themselves are references to the originals. This design optimizes memory usage during event hand-off while ensuring the internal buffer is immediately ready to collect new CDP traffic without duplication.

### Selective Direct Delivery via subscribeBrowserEvent()

Ego-lite provides an alternative consumption path through **`subscribeBrowserEvent`**, which bypasses the general buffer for specific event types. When a listener registers for a particular CDP method (except screencast frames), the runtime delivers that event directly to the callback and excludes it from the `events` array.

This selective delivery mechanism, active around lines 84-88, prevents noise in the general buffer for high-priority events while still maintaining the 10,000-event history for unfiltered consumption.

## Practical Implementation Examples

The following patterns demonstrate proper interaction with the buffering system using the `ego-browser` runtime APIs:

```typescript
// Retrieve and clear all buffered CDP events
import { drainBrowserEvents } from "ego-browser/src/browser-runtime.js";

async function analyzeSessionTelemetry() {
  const events = drainBrowserEvents();        // atomically empties internal queue
  console.log(`Collected ${events.length} CDP events from buffer`);
  console.dir(events, { depth: 2 });
}
await analyzeSessionTelemetry();

```

```typescript
// Subscribe to specific events while maintaining buffer integrity
import { subscribeBrowserEvent, waitForBrowserEvent } from "ego-browser/src/browser-runtime.js";

// Direct delivery bypasses the buffer for this event type
const unsubscribe = subscribeBrowserEvent(
  "Network.requestWillBeSent",
  undefined,
  (event) => {
    console.log("Intercepted request:", event.params?.request?.url);
  },
);

// Wait for a specific event condition with timeout
await waitForBrowserEvent(
  (e) => e.method === "Page.domContentEventFired",
  5000,
);

unsubscribe(); // Clean up direct subscription

```

## Summary

- **Ego-lite maintains a bounded FIFO queue** (`events` array) with a hard limit of 10,000 CDP entries defined by `MAX_BUFFERED_EVENTS` in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts).
- **Automatic eviction** removes the oldest events via `splice` when the buffer overflows, protecting against memory exhaustion during extended sessions.
- **`drainBrowserEvents()`** provides atomic retrieval and clearance, returning a shallow copy of accumulated events suitable for batched processing.
- **Selective direct delivery** via `subscribeBrowserEvent` allows high-priority events to bypass the buffer entirely (except screencast frames), optimizing memory usage for specific automation workflows.

## Frequently Asked Questions

### What is the maximum number of CDP events ego-lite can buffer?

The runtime enforces a hard cap of **10,000 events** through the `MAX_BUFFERED_EVENTS` constant in [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts). When this limit is exceeded, the oldest surplus events are immediately discarded to maintain bounded memory usage.

### How does ego-lite prevent memory exhaustion when handling high-frequency CDP events?

The buffering system prevents memory exhaustion through two mechanisms: **automatic queue truncation** using `Array.prototype.splice` when the 10,000-event threshold is crossed, and **selective direct delivery** that routes high-priority events directly to subscribers rather than storing them in the general buffer.

### Can I retrieve buffered events without clearing the queue?

No, the current implementation in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) only provides **`drainBrowserEvents()`**, which atomically returns and clears the buffer. There is no public API for peeking at events without removal; developers must implement their own caching layer if they require non-destructive inspection.

### Do all CDP events go through the buffer before reaching subscribers?

No, events delivered via **`subscribeBrowserEvent`** bypass the general buffer and route directly to registered listeners, with the exception of screencast frames which are always stored. This selective delivery ensures that frequently triggered handlers do not pollute the 10,000-event history intended for general telemetry analysis.