How Page Events Are Buffered and Drained in the Ego Lite Runtime

The Ego‑Lite runtime buffers CDP events in a capped in‑memory array and drains them via drainBrowserEvents(), with subscribers receiving real‑time callbacks and waitForBrowserEvent() enabling predicate‑based resolution.

The Ego‑Lite browser automation framework provides a robust event‑handling system for Chrome DevTools Protocol (CDP) messages. Understanding how page events are buffered and drained in the runtime is essential for building reliable agents that capture browser activity without exhausting memory. This article examines the implementation in browser-runtime.ts, including buffer management, subscriber dispatch, and retrieval patterns.

Event Buffer Architecture

The runtime maintains a single in‑memory queue for all incoming page events. This design prioritizes low latency over persistence, making it ideal for ephemeral automation tasks.

Buffer Storage and Limits

The core buffer is defined in browser-runtime.ts:

  • events (line 20): An array that stores every ingested CDP event
  • MAX_BUFFERED_EVENTS (line 8): A constant fixed at 10 000 entries

When the buffer reaches capacity, the oldest events are discarded to maintain this bound. The enforcement logic appears at lines 88‑90, ensuring memory usage remains predictable during long‑running sessions.

Event Ingestion Pipeline

The handleMessage callback (lines 31‑87) processes every raw CDP message from the browser:

  1. Parses the message structure
  2. Checks against screencast frame duplicates (already delivered to subscribers are skipped)
  3. Invokes matching subscribers from eventSubscribers (lines 77‑85)
  4. Appends the event to events (line 87)

This ordering—notify first, buffer second—ensures subscribers receive events immediately while the buffer captures history for later retrieval.

Subscriber Matching Logic

Subscribers are stored in eventSubscribers and matched by:

  • method: The CDP method name (e.g., "Page.loadEventFired")
  • sessionId (optional): For multi‑session browser connections

When both criteria match, the listener fires synchronously before buffering continues.

Draining the Event Buffer

The runtime exports two primary mechanisms for consuming buffered events:

drainBrowserEvents()

Located at lines 64‑66, this function empties the entire buffer in a single operation:

import { drainBrowserEvents } from "./browser-runtime.js";

// Retrieve and clear all buffered events
const allEvents = drainBrowserEvents();
console.log(`Recovered ${allEvents.length} events from buffer`);

The implementation uses events.splice(0, events.length) for O(n) extraction and immediate queue reset. This pattern suits end‑of‑task cleanup or batch analysis scenarios.

waitForBrowserEvent(predicate, timeoutMs)

For targeted event retrieval, lines 70‑86 expose a promise‑based waiter:

import { waitForBrowserEvent } from "./browser-runtime.js";

// Pause execution until a specific network request completes
const targetEvent = await waitForBrowserEvent(
  (e) => e.method === "Network.responseReceived" 
    && e.params?.response?.url?.includes("/api/data"),
  5000  // 5 second timeout
);

Waiters register in eventWaiters (line 21) and evaluate against every incoming event (lines 92‑106). The first matching event resolves the promise and removes the waiter.

Practical Usage Patterns

Real‑Time Subscription

Capture live events without buffering overhead for the consumer:

import { subscribeBrowserEvent } from "./browser-runtime.js";

const unsubscribe = subscribeBrowserEvent(
  "Page.navigatedWithinDocument",
  undefined,
  (event) => console.log("Fragment navigation:", event.params.url)
);

// Later: unsubscribe() to stop receiving callbacks

Batch Event Recovery

Combine draining with processing for post‑action analysis:

// After completing a workflow...
const buffered = drainBrowserEvents();

const networkErrors = buffered.filter(
  e => e.method === "Network.loadingFailed"
);

console.error(`Detected ${networkErrors.length} failed requests`);

Key Implementation Files

File Role
browser-runtime.ts Core buffer (events), MAX_BUFFERED_EVENTS enforcement, drainBrowserEvents(), handleMessage() dispatch loop, and waitForBrowserEvent() implementation (lines 8, 20‑21, 31‑106)
state.ts Runtime‑wide configuration including default timeouts for waiter operations
driver/waits.ts Consumer patterns using drainBrowserEvents() for network idle detection

Summary

  • The Ego‑Lite runtime buffers CDP events in a 10 000‑entry capped array to prevent unbounded memory growth
  • Subscribers receive synchronous callbacks before events enter the buffer, eliminating latency for real‑time consumers
  • drainBrowserEvents() provides bulk retrieval with complete buffer clearance via splice()
  • waitForBrowserEvent() enables precise, predicate‑driven event resolution with configurable timeouts
  • The screencast frame deduplication logic prevents duplicate deliveries for frame‑heavy operations

Frequently Asked Questions

What happens when the event buffer exceeds 10 000 entries?

The oldest events are automatically removed. The runtime enforces MAX_BUFFERED_EVENTS at lines 88‑90 of browser-runtime.ts using array truncation, preserving only the most recent entries.

Can multiple subscribers receive the same event?

Yes. The eventSubscribers iteration at lines 77‑85 invokes every matching subscriber before buffering. There is no early‑return; all registered listeners for a given method and sessionId receive the event.

How does waitForBrowserEvent differ from subscribeBrowserEvent?

subscribeBrowserEvent registers a persistent callback for all future matching events, while waitForBrowserEvent creates a one‑time promise that resolves on the first predicate match. Waiters are destroyed after resolution; subscribers persist until explicitly unsubscribed.

Is the event buffer persisted to disk?

No. The events array exists only in memory. For durability, agents must call drainBrowserEvents() and store results externally before process termination.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →