Bring Your Own Front End
A short guide — render the agent loop's event stream however you like.
The loop is headless — it never writes to a screen. It emits a typed
AgentEvent stream (onEvent), and the model emits StreamEvents; you decide
how to present them. This guide picks up where the
single-turn agent loop leaves off, and
shows the two ways to render a run.
Part 1 — Render the Event Stream Yourself
A renderer is just a function over the event stream. Switch on the event type
and present each one however you like — stdout here, but swap it for DOM nodes in
the browser, React state, a TUI, or a log sink without touching the loop:
import { runAgent, AgentEventType } from "@open-agent-loops/core";
import type { AgentEvent } from "@open-agent-loops/core";
// A renderer is a function over the event stream — pass it as `onEvent`.
function render(e: AgentEvent) {
switch (e.type) {
case AgentEventType.AgentStart: console.log(`▶ start · session ${e.sessionId}`); break;
case AgentEventType.TurnStart: console.log(`\n— turn ${e.step} —`); break;
case AgentEventType.ReasoningDelta: process.stdout.write(`\x1b[2m${e.text}\x1b[22m`); break; // dim: the thinking channel
case AgentEventType.TextDelta: process.stdout.write(e.text); break;
case AgentEventType.Message: console.log(`\n· ${e.message.role} message complete`); break;
case AgentEventType.ToolStart: console.log(`→ ${e.toolName}(${JSON.stringify(e.args)})`); break;
case AgentEventType.ToolEnd: console.log(`← ${e.toolName} [${e.isError ? "error" : "ok"}]: ${e.result}`); break;
case AgentEventType.AgentEnd: console.log(`\n■ done · ${e.steps} steps`); break;
}
}
await runAgent({ model, memory, sessionId, prompt, tools, onEvent: render });Because the loop is platform-free, the same handler drives a CLI or a browser UI unchanged — render to the terminal on the server, to the DOM in the client.
Part 2 — Ready-Made: The Tracer
Don't want to hand-write a renderer? Point a Tracer at the same events for a
timestamped timeline and a per-turn trajectory view:
import { runAgent, Tracer } from "@open-agent-loops/core";
const tracer = new Tracer();
await runAgent({ model, memory, sessionId, prompt, tools, onEvent: tracer.sink });
console.log(tracer.format()); // timestamped timeline
console.log(tracer.formatTrajectory()); // per-turn: action → observationMore on the Tracer
The Tracer can also wrap your ModelClient to capture model-boundary stream
events and batch trace I/O off the loop's hot path — see the
API reference. Nothing about the loop assumes a display, so the
same run drives a CLI or a browser UI unchanged.
Channels
A short tutorial — connect a live, bursty transport (Slack, Discord) to the agent without letting a fast socket overwhelm a slow model. One bounded, coalescing queue does the impedance matching.
Bring Your Own Model Client
A short guide — implement the one-method ModelClient seam for any provider, transport, or wire format.