RUNTIME / STREAMING EVENTS

Observe the run as it happens.

Each run exposes a typed async event stream alongside a promise for the final result. Build a terminal renderer, UI, audit log, or JSONL output around the same events.

Consume events

Start the run, consume its event stream, then await the result. Event consumption is optional, but it is the way to observe progress while the model and tools run.

tsnah
const run = runAgent({ model, prompt, system, tools });

for await (const event of run.events) {
  switch (event.type) {
    case "text-delta":
      process.stdout.write(event.text);
      break;
    case "tool-call":
      console.log("tool", event.toolName, event.input);
      break;
    case "tool-result":
      console.log("result", event.output, event.isError);
      break;
  }
}

const result = await run.result;

Event types

run-startStep and token budgets for the run.
step-startThe next model round-trip is starting.
text-deltaA streamed text fragment.
tool-callTool name, input, call ID, and step.
tool-resultCapped textual output and error flag.
step-finishCumulative usage at the end of a step.
compactedTranscript counts and summary size after compaction.
user-messageA mid-turn message you sent: delivery, and queued or delivered.
finishFinal stop reason, assistant text, and usage.
errorAn error emitted before the run rejects.

The finish.reason is one of completed, max-steps, max-tokens, aborted, or error.

Mid-turn messages

A message you send while the run is in flight emits user-message twice: once when the harness accepts it, and once when it actually enters the transcript. Those are different moments, and a UI needs both — render a pending chip on the first and clear it on the second.

tsnah
{ type: "user-message", delivery: "steer", phase: "queued" }
{ type: "user-message", delivery: "steer", phase: "delivered" }

delivery is steer or follow-up. For the semantics, and why a pending message prevents the run from ending, see steering a running turn.

Cancellation and errors

Pass an AbortSignal through abortSignal, or call run.interrupt(). Cancellation reaches executing tools, so a long-running command stops rather than merely appearing to. An aborted run emits a finish event with reason aborted. Other failures emit an error event and reject the result promise, so handle that promise with your normal error handling.

tsnah
const controller = new AbortController();
const run = runAgent({ ...options, abortSignal: controller.signal });

setTimeout(() => controller.abort(), 30_000);
try {
  const result = await run.result;
} catch (error) {
  console.error("Agent run failed", error);
}