Open Agent Loops

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 → observation

More 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.

On this page