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

# Watch a Devin session with the SDK

> Attach the Devin SDK to sessions you did not start and receive every update as typed events, whoever is driving the turn.

`run()` and `stream()` cover the turn you sent. `session.events()` covers everything that
happens in the session — turns started from the web app, Slack or another process included —
as the same typed events, until the connection closes. Attach to sessions you did not start and
the cloud pushes each status change the moment it happens:

```ts theme={null}
const session = await devin.attach("devin-…"); // no onPermission: observers don't decide
for await (const event of session.events()) {
  switch (event.type) {
    case "status": // working / blocked / finished, with what the user must do
      console.log(event.status, event.userActionRequired ?? event.message);
      break;
    case "pull_request": // a PR attached to the session changed; refetch
      console.log(event.prUrl);
      break;
    case "plan": // what is left to do
      console.log(event.entries.filter((e) => e.status !== "completed"));
  }
}
```

Every session exposes two views of its updates. `run()`, `stream()` and `streamUpdates()` are
scoped to a turn this client sent. `events()` and `updates()` are scoped to the session: every
live update for it, whoever started the turn, until the connection closes. Both typed flavours
use the same `SessionEventReducer`, so a `SessionEvent` means the same thing wherever it came
from.

> [!WARNING]
>
> The cloud pushes status transitions live but replays only the latest one, so a client that was
> offline cannot reconstruct what it missed. Seed from `listSessions()` (each entry's `_meta`
> carries `statusEnum`, `userActionRequired`, `pendingRequest`, `sessionPRs`, …) and stay attached
> for transitions. `session.meta` holds the latest session snapshot stamped on any update.

Observers should leave `onPermission` unset: an answer from an observer is a real decision, and
the protocol has no "abstain".

See the [session-board example](https://docs.devin.ai/sdk/examples/session-board.md) for a dashboard built this way.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.