Skip to content

Follow a run live

After you submit a Turn, open its Session Event stream to watch progress and receive the terminal outcome. Save each Event’s sequence after your own handler commits its work. The JSON page remains the recovery path if you need to inspect or repair history.

Examples use $WAMP_API for the Cloud API origin and $WAMP_TOKEN for a bearer credential with wamp.cloud.sessions:read. Opening the stream spends one ordinary rate-limit token. For example:

RateLimit-Policy: "wamp-cloud";q=600;w=60;wamp-burst=120
RateLimit: "wamp-cloud";r=117;t=1
Terminal window
curl -N -sS \
"$WAMP_API/v1/sessions/$SESSION_ID/events/stream?after=0" \
-H "Authorization: Bearer $WAMP_TOKEN" \
-H 'Accept: text/event-stream'

The stream first replays retained Events after the exclusive cursor, then waits for new appends. Each message carries the same SessionEvent object returned by GET /v1/sessions/{sessionId}/events:

id: 12
data: {"id":"d1f0a934-2b57-4c86-9e1d-3a0b7f625c48","type":"wamp.run.started","createdAt":"2026-08-12T09:21:07Z","sequence":12,"subject":{"sessionId":"9f2b7c14-59d3-4f7a-b8e1-2a6c05d4e731","runId":"c47a1e08-3d6b-4a92-9f15-8b70d2e5c6a4"},"data":{"status":"running"}}

There is no event: field: handle every message, including types a newer server adds. : keep-alive comments arrive at least every 15 seconds and are not Events. The id is the decimal sequence. The stream never skips a sequence; assert event.sequence === previous + 1 and stop on a gap.

A reconnect sends Last-Event-ID: <last committed sequence>; this header wins over ?after. On a first connection or a restart without an in-memory cursor, pass the cursor you persisted. after=0 replays from the beginning. The stream ends after 5–10 minutes, at credential expiry, or during a Cloud shutdown; open it again with a fresh credential and your saved cursor. A stream the server ends names its reconnection time in a final retry: field (milliseconds): at most a second when its lifetime ends, spread over up to 30 seconds when a Cloud shutdown ends every stream at once. Wait that long before reconnecting; when a stream ends without one (the connection was cut), wait a random 0–30 seconds. On 503, also honor Retry-After. During a release a reconnect can meet 503 service_unavailable, or an uncoded 502 or 504 from the edge, for several seconds, so keep trying for at least 20 seconds before you give up. The source App SDK’s streamSessionEvents() does this for you.

Four types terminate a Run: wamp.run.completed, wamp.run.failed, wamp.run.cancelled, and wamp.run.crashed. Match subject.runId to the Run you submitted. The SDK exports isTerminalRunEvent(event, runId) for that check. The terminal Event follows the retained timeline and Artifact Events for the Run, so stopping there does not miss earlier facts.

An Event log can be empty while a Run is queued or dispatching. A dispatch refusal can append a terminal failed or crashed Event without a preceding wamp.run.started. Read GET /v1/sessions/{sessionId}/runs/{runId} to show queued attempts and lastError.code; do not wait for started before accepting a terminal Event. Active wamp.run.progress Events describe bounded tool lifecycle facts. Assistant messages and presented files arrive while the Run works, a message as it completes rather than token by token; whatever the Run had not delivered by its end arrives at the terminal capture, before the terminal Event.

wamp.interaction.opened identifies a question, plan, or live approval. Fetch GET /v1/sessions/{sessionId}/interactions for currently open requests. Submit a new Turn with replyTo.interactionId for a question or plan; decide a live approval with PUT …/interactions/{interactionId}/decision, which resumes the same Run without a reply Turn. An Interaction is a durable edge, not a socket prompt. If your process was offline, read the Event log from its cursor and then read the open Interaction list before asking a user to answer anything.

Store your cursor in the same transaction as the side effect caused by each Event. A crash before that transaction commits replays the Event; make the handler idempotent on the Event id. A crash after it commits resumes at the saved sequence. If the stream drops, reconnect using Last-Event-ID; the server pages the same authorized log before waiting for new work.

Use the JSON page for a finite audit or repair:

Terminal window
curl -sS \
"$WAMP_API/v1/sessions/$SESSION_ID/events?after=$LAST_SEQUENCE&limit=100" \
-H "Authorization: Bearer $WAMP_TOKEN"

hasMore only says another page is available. An empty page says you reached the current tail, not that the Run ended. Archive does not erase the log; Events remain readable after the Session stops accepting work.

The stream requires fresh authorization at open and closes when the local revocation epoch advances. A refused open is JSON, with the usual machine error code. An authority can open at most 64 streams and the process at most 256; a slow reader is closed after more than 1 MiB remains unflushed. Reconnect from your last committed cursor rather than inferring a Run outcome from a closed connection.

A backend that follows many Sessions does not need one Event stream per Session. With an App installation credential, open GET /v1/sessions/stream: it tells you which of the installation’s Sessions moved and to which sequence, and you read each moved Session’s Event page from your own cursor. A person’s credential gets 403 installation_principal_required.

Terminal window
curl -N -sS "$WAMP_API/v1/sessions/stream?after=${WATERMARK:-0}" \
-H "Authorization: Bearer $WAMP_TOKEN" \
-H 'Accept: text/event-stream'
id: 1786526460000
id: 1786526467250
data: {"type":"wamp.session.events_appended","timestamp":"2026-08-12T09:21:07.000Z","data":{"sessionId":"9f2b7c14-59d3-4f7a-b8e1-2a6c05d4e731","lastEventSequence":12}}

Each data is a notice, not an Event: the Session’s log reached at least lastEventSequence. Page GET /v1/sessions/{sessionId}/events?after=<cursor> for that Session until hasMore is false. Appends within 250 ms reach you as one notice, notices can repeat, and they arrive in no particular order across Sessions, so a notice at or below your cursor needs no work. The notice reference lists its fields.

The id is a watermark: Cloud’s clock in milliseconds. Not every message carries one. When Cloud announces several Sessions at once, only the last notice of the batch carries the id, and it covers the notices before it. A message with an id and no data only moves the watermark; the stream opens with one. Save the newest watermark after you have acted on the notices before it.

To resume, send it as ?after= (or Last-Event-ID, which wins). The stream first re-announces every Session whose log may have moved since that watermark, looking back 120 seconds before it, so expect some Sessions you are already caught up on, and then goes live. With no saved watermark, pass after=0: the stream announces every Session the installation has created that has Events, which is how a first run catches up. Without either, the stream starts live from now. A watermark from the future counts as now.

The stream follows the same rules as the Session Event stream above. It needs fresh authorization at open, ends after 5–10 minutes, at credential expiry or at a Cloud shutdown, and counts against the same 64 streams per authority. Wait the retry: it ends with before reconnecting (a random 0–30 seconds when it ends without one), and keep retrying through a release. The source App SDK’s streamSessionChanges() does this and resends its newest watermark.