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

# Session Lifecycle

> How a session moves between open, closing, closed and deleting — and what each status permits.

A session carries a `status` that governs what may be done with it. A freshly created session is
`open` and fully writable. From there it can be **closed** — frozen read-only while its history is
preserved — **reopened**, or **deleted**, which permanently removes it and everything it contains.

```mermaid theme={null}
stateDiagram-v2
    [*] --> open : create_session
    open --> closing : close_session
    closing --> closed : executions drained
    closed --> open : reopen_session
    open --> deleting : delete_session
    closing --> deleting : delete_session
    closed --> deleting : delete_session
    deleting --> [*] : purged
```

## Statuses

| Status     | Reads | Writes | Meaning                                                                        |
| :--------- | :---- | :----- | :----------------------------------------------------------------------------- |
| `open`     | Yes   | Yes    | The normal working state. Objects, threads, graphs and executions are allowed. |
| `closing`  | Yes   | No     | A close is in progress: writes already rejected, running executions stopping.  |
| `closed`   | Yes   | No     | Frozen. Nothing is running, nothing can change, all history remains readable.  |
| `deleting` | Yes\* | No     | Teardown in progress. The session and its contents are being removed.          |

<Note>
  `closed` is **read-only, not read-blocked**. Everything in a closed session — objects, thread items,
  graph elements, execution history — stays fully readable. That is the point of closing rather than
  deleting.

  \* Reads during `deleting` succeed only until the underlying data is purged; once teardown completes
  the session no longer exists and reads return `403`.
</Note>

## Transitions

Only these transitions are legal. Every one is applied atomically, so concurrent callers cannot both
win — the loser receives `409`.

| From                          | Command          | To         |
| :---------------------------- | :--------------- | :--------- |
| `open`                        | `close_session`  | `closing`  |
| `closing`                     | *(automatic)*    | `closed`   |
| `closed`                      | `reopen_session` | `open`     |
| `open` / `closing` / `closed` | `delete_session` | `deleting` |

Anything else is rejected:

| Error code                 | Status | Meaning                                                           |
| :------------------------- | :----- | :---------------------------------------------------------------- |
| `session_not_open`         | 409    | A write or `close_session` was attempted on a non-`open` session. |
| `session_not_closed`       | 409    | `reopen_session` was attempted on a session that is not `closed`. |
| `session_already_deleting` | 409    | `delete_session` was attempted while a delete is already running. |

`close_session`, `reopen_session` and `delete_session` all require the **write** role on the blob.

## The write gate

The moment a session leaves `open`, every mutating operation on it is rejected with `409`
`session_not_open`. This includes `upload_session_object`, `delete_session_object`,
`post_session_thread_item`, `apply_session_graph_mutations` and `create_execution`.

Reads are never gated: `get_session`, `list_sessions`, `list_session_events`,
`download_session_object`, `list_session_objects` and the thread and graph read commands all keep
working, in every status, until the session is actually gone.

<Warning>
  Editing the workflow **definition** is not affected. A session's status governs that session's data,
  not the revision it belongs to — you can keep editing the workflow while a session is closed.
</Warning>

## Closing is asynchronous

`close_session` returns immediately with `status: "closing"`. The write gate is already in force at
that point, so no new work can start. In the background BlobHub then **stops every execution still
running in the session** and waits for each to reach a terminal status. Only once the session is
quiescent does it become `closed`.

A close therefore takes a little time to complete even though the command returns at once —
typically tens of seconds when executions must be stopped, and near-immediate when there is nothing
running. Clients should treat `closing` as a transient state and wait for `session_closed` (or poll
`get_session`) rather than assuming the close is finished when the command returns.

<Note>
  Executions stopped by a close or delete finish with status `stopped` and do **not** emit
  `execution_ended`, which is reserved for executions that run to completion on their own. To observe
  a drained execution, read its status with `get_execution` rather than waiting for that event.
</Note>

## Deleting is permanent

`delete_session` is a **hard delete**. There is no tombstone, retention window or undo. Once
teardown completes, the session and everything belonging to it are gone:

* every session object, including thread items and graph elements
* the session's execution history and execution events
* the session's stored events
* the session row itself

The command may be issued from any live status. It returns immediately with `status: "deleting"` and
the teardown proceeds in the background.

### Nothing is purged while work is running

Deletion **drains before it purges**. Any execution still running is stopped and must reach a
terminal status *before* the first piece of data is removed. This ordering is what prevents an
in-flight execution from writing into a session that is being torn down and leaving orphaned data
behind.

Deleting an `open` session therefore performs the whole sequence — stop the work, then purge — as a
single action. It does **not** pass through `closed`, and consequently emits no `session_closed`
event on the way (see [Session Events](/blob-types/workflow/workflows/session-events)).

### After a delete

Once the session is gone, requests that reference it return `403`, including
`download_session_object` for objects it used to contain, and it no longer appears in
`list_sessions`. Its per-revision session quota is released.

## Choosing between close and delete

<Tip>
  **Close** when the work is finished but the record matters — a completed run you may want to inspect,
  audit or resume later. It stops anything still running, freezes the contents, and can be undone with
  `reopen_session`.

  **Delete** when the data itself should not persist. It is irreversible.
</Tip>

## Reacting to lifecycle changes

Four events mark the transitions — `session_closing`, `session_closed`, `session_deleting` and
`session_deleted`. They are delivered over the realtime WebSocket API and, apart from
`session_deleted`, also recorded in the session's event stream. See
[Session Events](/blob-types/workflow/workflows/session-events) for payloads and the ordering rules,
including why a delete from `open` produces no `session_closed`.

`reopen_session` emits no event: the caller learns the new status from the command's response.

## Related

<CardGroup cols={2}>
  <Card title="Close Session" href="/blob-types/workflow/operations/close-session">
    Freeze a session read-only.
  </Card>

  <Card title="Reopen Session" href="/blob-types/workflow/operations/reopen-session">
    Return a closed session to writable.
  </Card>

  <Card title="Delete Session" href="/blob-types/workflow/operations/delete-session">
    Permanently remove a session.
  </Card>

  <Card title="Session Events" href="/blob-types/workflow/workflows/session-events">
    The events emitted by each transition.
  </Card>
</CardGroup>
