Buffered Output Sink in Ego-Lite: How It Prevents Duplicate Error Noise

The buffered output sink in ego-lite captures all console.log output in memory and flushes it selectively at process exit, ensuring agents see only clean, deduplicated messages—especially critical when hard-stop errors would otherwise spam duplicate lines on every loop iteration.

Ego-lite executes agent scripts inside isolated, short-lived Node processes where the agent reads output exclusively through console.log. Because user-controlled code can trigger repeated hard-stop errors (such as those produced by buildEgoError), the framework needs a mechanism to prevent noisy, duplicated error streams from overwhelming the agent's view. The buffered output sink, implemented in package/ego-browser/src/output-sink.ts, solves this by intercepting all log lines and applying intelligent filtering before final emission.

What the Buffered Output Sink Records

The sink maintains three distinct categories of output in internal state:

  • buffer – An array storing every regular console.log line captured during script execution.
  • Hard-stop message – The first error message captured by markHardStop, stored once and never overwritten.
  • Notice trailer – An optional footer added via setNoticeTrailer that appears at the end of all output.

This separation allows the sink to make context-aware decisions during the final flush.

How flushSink Decides What to Emit

When the Node process prepares to exit, flushSink evaluates the captured state and applies one of three behaviors:

Scenario Flush Behavior
Hard-stop was recorded Discard the entire buffer array and emit only the stored hard-stop message (single occurrence).
No hard-stop occurred Flush all buffered lines in original order, preserving complete script output.
Either case Append the notice trailer if setNoticeTrailer was called.

This logic prevents the agent from seeing a polluted stream where hard-stop errors repeat on every loop iteration while user code continues executing.

Lifecycle Integration and Process Isolation

The sink integrates with Node's process lifecycle through installLifecycleFlush, which registers handlers on beforeExit and exit events:

import {
  bufferOutput,
  markHardStop,
  setNoticeTrailer,
  installLifecycleFlush,
} from "./output-sink";

// 1. Register automatic flushing at process exit
installLifecycleFlush(process.stdout);

// 2. Normal logging captures to buffer instead of immediate stdout
console.log("Starting task…");
bufferOutput("Starting task…\n");

// 3. Record hard-stop once if encountered
try {
  // Operation that may trigger hard-stop
} catch (e) {
  if (isHardStop(e)) {
    markHardStop(e.message); // First occurrence only
  }
}

// 4. Optional footer appears at end of all output
setNoticeTrailer("🚀 Ego-lite update available – run `npm install -g ego-lite`");

// 5. Automatic flush occurs on process exit; manual flush available for tests
// flushSink(process.stdout, /*thrown=*/ false);

Because ego-lite spawns a fresh Node process for each heredoc execution, the sink's state is automatically isolated per run. The resetSink function exists solely for in-process test scenarios where multiple sink operations occur within the same process lifetime.

Source Code Locations

As implemented in citrolabs/ego-lite, the buffered output sink spans these key files:

  • package/ego-browser/src/output-sink.ts – Core implementation including bufferOutput, markHardStop, setNoticeTrailer, flushSink, and installLifecycleFlush.
  • package/ego-browser/src/output-sink.test.mjs – Unit tests verifying buffering mechanics, hard-stop deduplication, and trailer attachment.
  • package/ego-browser/src/run-output-sink.test.mjs – Integration tests validating sink behavior through the CLI runner.
  • package/ego-browser/src/index.ts – Entry point that defaults to the buffered sink when no custom CLI log sink is provided.

Summary

  • The buffered output sink intercepts all console.log calls to prevent immediate stdout emission.
  • It deduplicates hard-stop errors by capturing only the first occurrence and suppressing subsequent repetitions.
  • flushSink applies context-aware filtering: full buffer on success, single hard-stop message on error.
  • Lifecycle hooks guarantee output emission even on abrupt process termination.
  • Process-per-run isolation eliminates state leakage; resetSink supports test environments.

Frequently Asked Questions

What problem does the buffered output sink solve?

Without buffering, a hard-stop error triggered inside a user-controlled loop would print the same error message on every iteration, creating an unreadable stream of duplicates mixed with normal logs. The sink captures these once and presents a clean, single message to the agent.

When does the buffered output actually reach stdout?

Emission occurs during Node's beforeExit or exit events via installLifecycleFlush, or manually through flushSink. This deferred write enables the final filtering logic that separates successful output from hard-stop scenarios.

Can I use the buffered output sink outside of hard-stop handling?

Yes. The sink's architecture supports general output capture and transformation. The notice trailer mechanism (setNoticeTrailer) demonstrates this extensibility—allowing framework-level messages to appear consistently at the end of any script run, regardless of execution outcome.

How does ego-lite ensure sink state doesn't leak between script runs?

Each heredoc execution launches a completely new Node process, which instantiates fresh sink state. The resetSink utility exists only for internal testing where multiple sink lifecycles occur within one process.

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 →