How the Output Sink in Ego‑Lite Buffers and Handles Console.log Output

The output sink in ego-lite intercepts console.log calls via a patched console object injected by helperContext(), stores formatted messages in a bounded circular buffer defined in package/ego-browser/src/state.ts, and exposes them to the host through CDP commands for asynchronous retrieval.

Ego‑Lite is a secure JavaScript runtime that executes untrusted code inside an isolated browser context. When debugging agents or monitoring execution flows, understanding how the framework captures standard output is essential for observability. The output sink mechanism ensures that every console.log, console.error, and console.warn statement is safely buffered and made available to the host without breaking the sandbox boundaries.

How the Console Output Sink Works

Injecting the Virtual Console via helperContext()

When the runtime initializes, the helperContext() function in [package/ego-browser/src/helpers.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) constructs the global object provided to executed scripts. This construction includes a patched console object whose methods (log, error, warn, info) are overridden to forward arguments to the output sink rather than writing to the browser's native console.

The interception occurs before user code executes, ensuring that all subsequent console.log calls route through the ego-lite runtime instead of the underlying engine.

Storing Output in the Circular Buffer

The sink persists log entries in an in-memory circular buffer defined in [package/ego-browser/src/state.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts). This outputBuffer maintains the most recent 10,000 lines by default, acting as a fixed-capacity queue.

When a script invokes console.log, the patched method pushes a new entry onto the buffer. If the buffer has reached capacity, the oldest entry is silently overwritten. This bounded approach prevents unbounded memory growth when scripts log excessively, mirroring back-pressure handling strategies used in embedded environments.

Normalizing Log Messages with Metadata

Before storage, the console wrapper normalizes each message. Arguments are stringified using a util.inspect-style formatter with depth limits, then concatenated with spaces to match standard Node.js console output. Each entry is enriched with a timestamp and a log-level tag (e.g., "log", "error", "warn"), creating a structured record that the host can parse for severity filtering.

The cdp() function in [package/ego-browser/src/cdp-eval.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/cdp-eval.ts) facilitates the low-level communication channel used to transmit these formatted entries from the isolated context to the runtime state manager.

Retrieving Buffered Output via CDP

The host application retrieves accumulated logs by issuing CDP (Chrome DevTools Protocol) commands. Specifically, the runtime exposes a method such as Runtime.getBufferedOutput (implemented in [package/ego-browser/src/browser-runtime.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts)) that returns the current contents of the outputBuffer.

After retrieval, the host may optionally signal the runtime to clear the buffer, preparing it for the next execution cycle. This pull-based model decouples script execution from log consumption, allowing the host to display logs in a UI or forward them to remote aggregation services.

Practical Implementation Examples

Example 1: Basic Logging Inside an Ego‑Lite Script

When running code through the ego-lite CLI or agent API, standard logging works as expected, but output is captured by the sink:

// Executed inside the ego-lite sandbox
console.log('Agent started');
console.log('Processing item:', { id: 42, status: 'active' });
console.error('Warning: deprecated API usage');

These calls trigger the patched console methods, immediately pushing structured entries into the circular buffer in state.ts.

Example 2: Host-Side Retrieval of Buffered Output

The host application pulls logs using the CDP bridge. While the exact API depends on your host implementation, the pattern follows this structure:

// Host-side pseudo-code demonstrating retrieval
const response = await ego.sendCDPMessage('Runtime.getBufferedOutput', {});
console.log('Captured ego-lite output:', response.lines);
// response.lines contains the array of buffered log strings

This retrieves all entries accumulated since the last fetch or since the runtime started.

Example 3: Adjusting the Buffer Capacity

To modify the default 10,000-line limit, edit the constant in the state module:

// package/ego-browser/src/state.ts
export const OUTPUT_BUFFER_CAP = 25000; // Increase from default 10,000

After modifying the source, rebuild the package with npm run build to apply the new limit. Increasing the cap allows longer log retention for verbose debugging sessions, while decreasing it reduces memory overhead in resource-constrained environments.

Key Source Files and Functions

File Purpose Key Export
package/ego-browser/src/helpers.ts Builds injected globals including the patched console object helperContext()
package/ego-browser/src/state.ts Maintains runtime state including the circular outputBuffer outputBuffer, OUTPUT_BUFFER_CAP
package/ego-browser/src/browser-runtime.ts Implements CDP command handlers for host communication Runtime.getBufferedOutput handler
package/ego-browser/src/cdp-eval.ts Provides low-level CDP messaging primitives cdp()

These files collectively implement the output-sink pipeline that buffers console.log output for safe host retrieval.

Summary

  • Interception: The helperContext() function in helpers.ts replaces the native console object with a patched version that captures all output.
  • Buffering: Log entries are stored in a circular buffer defined in state.ts with a default capacity of 10,000 lines, preventing memory leaks through automatic eviction of old entries.
  • Formatting: Messages are normalized with timestamps and severity levels using util.inspect-style serialization before storage.
  • Retrieval: The host fetches buffered output via CDP commands exposed in browser-runtime.ts, enabling asynchronous log consumption without blocking script execution.
  • Configuration: Buffer capacity can be adjusted by modifying OUTPUT_BUFFER_CAP in state.ts and rebuilding the package.

Frequently Asked Questions

What happens when the console buffer reaches its limit in ego-lite?

When the outputBuffer reaches its capacity (default 10,000 entries), the oldest log lines are silently overwritten by new entries. This circular buffer behavior ensures that the runtime never consumes unbounded memory, even if a script logs continuously in an infinite loop.

How can the host application retrieve logs from the output sink?

The host retrieves logs by sending a CDP command such as Runtime.getBufferedOutput to the ego-lite runtime. This command, handled in package/ego-browser/src/browser-runtime.ts, returns the array of buffered log lines accumulated since the last retrieval or since the runtime started.

Does ego-lite distinguish between console.log and console.error?

Yes. The patched console object in helpers.ts tags each entry with its log level ("log", "error", "warn", or "info") before pushing it to the buffer in state.ts. This allows the host to filter retrieved output by severity or route error messages to separate monitoring systems.

Where is the console buffer size configured in ego-lite?

The buffer size is defined by the OUTPUT_BUFFER_CAP constant in package/ego-browser/src/state.ts. Developers can modify this value to increase retention for verbose debugging or decrease it to conserve memory, then rebuild the package to apply changes.

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 →