# Watch and steer live

Follow a run as it works, send it the next instruction, stop it, or take over its desktop. Every surface reads the same event stream.

A run is not a black box you wait on. While it works you can read its events as they happen,
send it follow-ups, cancel it, and in the console watch or drive the computer it is using. The
API, the console and the CLI read the same stream and write the same session.

_Figure: stream and follow up, on the page at /docs/concepts/watch-and-steer._
## The event stream

[`GET /o/{org}/sessions/{session}/events`](/docs/api/stream-session) is a server-sent event
stream. Each frame is an `event:` line, an `id:` and a JSON `data:` line.

| Event | When |
| --- | --- |
| `status` | Before the sandbox starts, and on every status change. Data: `{status}`. |
| `message`, `thinking` | The agent's own text and reasoning. |
| `tool_use`, `tool_result` | A tool call and its outcome, linked by `call_id`. |
| `result` | The turn's final answer. |
| `error` | A harness error. An `error` with no `type` (`{code, message}`, code `NOT_FOUND` or `STREAM_ERROR`) ends the stream early. |
| `done` | The last frame. Data: `{status, output, result}`. |

The stream closes after `done`. If your connection drops, reconnect with the `Last-Event-ID`
header (or `?cursor=`) set to the last `id` you saw, and the stream resumes after it. Comment lines
(`: keepalive`) hold idle connections open.

A session that has already finished sends only `status` and `done`, never its history. Read past
events from [`GET …/messages`](/docs/api/get-session-messages).

```bash
curl -N https://api.prix.dev/o/$ORG/sessions/$SESSION/events \
  -H "Authorization: Bearer $RUSH_API_KEY" -H "Last-Event-ID: $LAST_ID"
```

## Follow-ups

A follow-up is the next instruction in the same session:
[`POST …/messages`](/docs/api/post-session-message) with `{"input": "…"}`, the same text or
file blocks a run takes. It answers `202` with the session, `status` `running`, and wakes a parked
sandbox where it left off.

How a follow-up sent mid-turn behaves depends on who sends it:

- **An API key** must wait for the turn to end. While a turn is running the follow-up is refused
  with `409 TURN_ACTIVE`. Wait for `done`, or for `status` to leave `running`, then send it.
- **The console or the CLI** queue a text-only message sent mid-turn, up to 20, and deliver them in
  order when the turn ends. One more answers `429 QUEUE_FULL`. A message with files waits for
  the turn to end.

A follow-up to a closed session is `409 TASK_TERMINAL`.

## Cancel

[`POST …/cancel`](/docs/api/cancel-session), with no body, ends the run and releases its sandbox.
It answers `202` with the session: `status` `cancelled`, or `cancelling` while the sandbox is still
being released. A run that has already finished answers `409 RUN_STATE_CONFLICT`.

## Browser and computer

A run's sandbox has a desktop and a browser the agent uses. From the run's page in the console,
the run's owner or an org admin can:

- **Watch** the display, read-only. Any number of people can watch at once.
- **Take control** of mouse and keyboard.
- **Open a terminal** in the sandbox.

For anyone else in the org, a run's live surfaces answer `404`, as if the run did not exist. A run
with no desktop to show answers `409 VISUAL_UNAVAILABLE`.

## The control lease

_Figure: control lease, on the page at /docs/concepts/watch-and-steer._
Only one person drives a desktop at a time. Taking control grants a **control lease** for 5
minutes, which the holder can renew while they work, up to 15 minutes ahead. While someone holds
it, another request to take control answers `409 CONTROL_HELD` with the holder's name and when
their lease expires.

The holder releases it when done, and a run owner or org admin can release anyone's lease. When
the run ends (`completed`, `failed` or `cancelled`) the lease ends with it.

[Steer a run as a team](/docs/use-cases/steer-a-run-as-a-team) walks through handing a run
between people.
