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

> Discover how the ego-lite output sink buffers and handles console.log output asynchronously using CDP commands. Learn about its patched console and circular buffer implementation.

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

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/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)](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)](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)](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)](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:

```javascript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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:

```javascript
// 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:

```typescript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) | Builds injected globals including the patched `console` object | `helperContext()` |
| [`package/ego-browser/src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) | Maintains runtime state including the circular `outputBuffer` | `outputBuffer`, `OUTPUT_BUFFER_CAP` |
| [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts) | Implements CDP command handlers for host communication | `Runtime.getBufferedOutput` handler |
| [`package/ego-browser/src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) tags each entry with its log level (`"log"`, `"error"`, `"warn"`, or `"info"`) before pushing it to the buffer in [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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.