Understanding the bufferOutput Mechanism in Ego-Lite: When and How It Flushes
The bufferOutput mechanism in ego-lite captures all console.log output into an in-memory buffer and flushes it exactly once per heredoc run during process teardown, discarding the buffer and emitting only the hard-stop error message if a critical failure occurs.
The citrolabs/ego-lite repository implements a specialized output buffering system to manage agent logging during browser automation runs. Instead of writing directly to stdout, the bufferOutput mechanism intercepts all console.log calls and stores them in a controlled sink, enabling clean error handling and atomic output delivery. This architecture ensures that regular logs accumulate safely while providing immediate, uncluttered feedback when scripts encounter hard-stop failures.
How the bufferOutput Mechanism Works
The Output Sink Architecture
In src/output-sink.ts, ego-lite defines an internal output sink that redirects the agent’s console.log calls away from the process stdout. Each log fragment is pushed into an in-memory array (buffer: string[]) via the bufferOutput() function. This decouples log generation from log delivery, allowing the runtime to manipulate or discard output based on the execution outcome.
Internal State Tracking
The sink maintains three distinct states to manage the output lifecycle:
buffer– An array of formatted log lines accumulated during execution. ThebufferOutput()function pushes each chunk into this array (lines 33-35 insrc/output-sink.ts).hardStopMessage– Stores the first hard-stop error message when anEgoErroraborts the script. Set viamarkHardStop()(lines 51-55), this message supersedes the entire buffer if triggered.noticeTrailer– An optional footer appended after the final output, regardless of success or failure. Configured viasetNoticeTrailer()(lines 42-44).
When Is the Buffer Flushed?
The Flush Lifecycle via flushSink
The buffer flushes exactly once per heredoc run through the flushSink(stream, thrown) function. This method evaluates the execution context to determine output behavior:
- Normal completion: The buffered log lines write to the provided writable stream in chronological order.
- Hard-stop occurred: The entire buffer drops, and only the stored
hardStopMessageemits (or the uncaught error itself if the script terminated unexpectedly). - Notice trailer: After either path,
noticeTrailerappends if present.
Automatic Process Teardown Handling
To guarantee the sink drains without explicit CLI wrapper calls, installLifecycleFlush(stream) registers process event listeners at lines 108-114 of src/output-sink.ts. This utility attaches to process.on('beforeExit') for clean exits and process.on('exit') for uncaught async rejections, ensuring flushSink executes exactly once during termination.
Implementation in the Runtime
During run initialization, executionContext() in src/run.ts overwrites the global console.log to route formatted output into the buffer. All subsequent logging accumulates silently until the process teardown triggers the automatic flush.
Practical Code Examples
The following patterns demonstrate how to interact with the buffering system directly:
// 1️⃣ Replace console.log with the buffered version (run.ts)
import { bufferOutput } from "./output-sink.js";
console.log = (...args) => {
bufferOutput(`${args.map(formatCliLogValue).join(" ")}\n`);
};
// 2️⃣ Mark a hard-stop from anywhere in the runtime
import { markHardStop } from "./output-sink.js";
if (isHardStopError(err)) {
// Only the first hard-stop message survives; buffer is discarded
markHardStop(err.message);
}
// 3️⃣ Append an out-of-band notice (e.g., async version check)
import { setNoticeTrailer } from "./output-sink.js";
setNoticeTrailer("⚡ ego-lite update available – run `ego-browser --doctor`");
// 4️⃣ Register the automatic flush (once per process)
import { installLifecycleFlush } from "./output-sink.js";
installLifecycleFlush(process.stdout);
Summary
- The bufferOutput mechanism redirects
console.logto an in-memory array insrc/output-sink.ts, preventing immediate stdout writes during agent execution. - Flush timing occurs exactly once per run via
flushSink, triggered automatically byinstallLifecycleFlushduring processbeforeExitorexitevents. - Hard-stop handling discards the entire buffer and emits only the error message when
markHardStop()records a critical failure. - Notice trailers append after both success and error outputs through
setNoticeTrailer(), supporting auxiliary messaging like update notifications.
Frequently Asked Questions
What triggers the bufferOutput mechanism to flush in ego-lite?
The buffer flushes automatically when the Node.js process terminates. The installLifecycleFlush() function registers listeners for process.on('beforeExit') and process.on('exit') events, ensuring flushSink() runs exactly once to emit either the buffered logs or a hard-stop error message.
Does the bufferOutput mechanism preserve logs when a script fails?
It depends on the failure type. During a hard-stop (critical EgoError), the buffer discards all accumulated logs and only emits the hardStopMessage set via markHardStop(). For normal completions or non-critical errors, the full buffer writes to stdout.
How can I add a custom footer to ego-lite output regardless of success or failure?
Use the setNoticeTrailer() function imported from src/output-sink.ts. This stores a message that flushSink() appends after the main output (whether buffered logs or a hard-stop error), making it ideal for version warnings or diagnostic hints.
Where is the console.log override initialized in the ego-lite codebase?
The override occurs in src/run.ts within the executionContext() function. This module replaces the global console.log implementation with a wrapper that calls bufferOutput(), ensuring all agent logging routes through the centralized sink before the automatic flush occurs.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →