Skip to content

The live run view

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.

Updated View as MarkdownOpen in ChatGPTOpen in Claude

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:

{ "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.

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

Command reference: observe trace reads what the stream carried, after the fact.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close

Overlane Agent

Ask about the docs. Answers are quoted from the pages, with links. This panel cannot change anything.

    Questions are answered in your browser from the docs index. Nothing you type here is stored or sent anywhere.