---
title: "The live run view"
description: "How a run's events reach a viewer over a WebSocket within seconds, how a dropped viewer resumes without gaps or repeats, and who may connect."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.overlane.app/llms.txt
> Use this file to discover all available pages before exploring further.

# The live run view

A run reports events as it works: it started, took a step, logged, spent, decided, ended. The project's Durable Object stores them in order and pushes each new one to every viewer watching that run. In the test suite on the Workers runtime, an event reached a connected viewer in tens of milliseconds; the requirement is under five seconds.

## The protocol

A viewer opens a WebSocket on the run's stream route (`/v1/tenants/{tenantId}/projects/{projectId}/runs/{runId}/stream`) and sends one message:

```json
{ "type": "resume", "after": -1 }
```

`after` is the last sequence the viewer applied, or `-1` to start at the beginning. The server answers `hello` with the run's newest sequence, then every event after the cursor, then live events as they arrive. When the run's last event, `run.ended`, has been delivered, the server sends `end`. `ping` is answered with `pong` without waking the object, so an idle viewer costs nothing.

Events are domain events: `run.started`, `run.step`, `run.log`, `run.cost`, `run.decision` and `run.ended`. Each carries the tenant, the project, a per-run `sequence` starting at 1, and a payload with the run ID, agent, status, and tool, cost and tokens where there are any.

## No gaps, no repeats

The sequence is assigned by the project and never reused. A producer that sends the same event ID twice stores it once. A viewer that drops reconnects and resumes from its last sequence; the server sends exactly the events after it. A socket receives no live event before it has sent `resume`, so the replay and the live feed cannot interleave.

```js run
// A resume list is gapless only when it starts right after the cursor and skips nothing.
assert.equal(isGapless([{ sequence: 4 }, { sequence: 5 }], 3), true);
assert.equal(isGapless([{ sequence: 4 }, { sequence: 6 }], 3), false);
// A repeated event ID keeps the first copy, in sequence order.
const ordered = orderRunEvents([
  { sequence: 2, eventId: "evt-b" },
  { sequence: 1, eventId: "evt-a" },
  { sequence: 3, eventId: "evt-a" },
]);
assert.equal(JSON.stringify(ordered.map((e) => e.sequence)), "[1,2]");
```

## Who may connect

The upgrade goes through the same checks as any API call. The caller must belong to the team in the path, have a role that reads, and, for a token scoped to one project, name that project. Another team's path is refused with 403 before anything else. A browser that sends an `Origin` header must send one the Overlane app uses; any other origin is refused, so a page on another site cannot open the stream with the session cookie.

## Reporting events

An agent reports with `POST` to the project's observe events route, up to 100 events per call, each with an event ID, a run ID, a type, the agent and a status. Text in `label` and `detail` is redacted before it is stored. See [Logs and traces](/observability/logs-and-traces/).

Command reference: [observe trace](/cli/observe-trace/) reads what the stream carried, after the fact.

Source: https://docs.overlane.app/observability/live-run-view/index.mdx
