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 regularconsole.logline 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
setNoticeTrailerthat 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 includingbufferOutput,markHardStop,setNoticeTrailer,flushSink, andinstallLifecycleFlush.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.logcalls to prevent immediate stdout emission. - It deduplicates hard-stop errors by capturing only the first occurrence and suppressing subsequent repetitions.
flushSinkapplies 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;
resetSinksupports 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →