# Sessions and runs

A session is where state lives; a run is one request inside it. Statuses, one-active-run, idempotent retries, and what to do when a run waits for a person.

A **session** is the continuity boundary for one enabled agent version: its workspace, files, and
interactions. A **run** is one admitted unit of execution inside it. Keep both IDs: the session
tells you where state lives; the run tells you what happened during one request.

- Create a **session** once per conversation or unit of work; start a **run** per request. Runs in a session execute one at a time.
- Send an `idempotency_key` on both so a retry returns the original instead of duplicating work.
- The **run resource** is the authoritative status. The event stream is how you watch it; reconnect with `Last-Event-ID`.
- A run can pause for a person (`awaiting_input: true`); a session can be stopped and resumed, and idle sessions are suspended for you.

Session `ready` → Run 1 `running` → interaction `pending` (`awaiting_input: true`) → Run 1
`succeeded` → session `idle` → Run 2 `running` → Run 2 `failed` → session `idle`.

Run 2 starts only after Run 1 is terminal; the failed second run does not erase the first or end
the session.

## Choose the invocation boundary

| Choose | When it fits | Tradeoff |
|---|---|---|
| [One-shot invocation](/api/agents/invoke-agent) | One independent request where immediate streaming is the main goal. | Tanomis creates a session and run and returns their IDs in `X-Session-ID` and `X-Run-ID`, but retrying the request creates a new session boundary. |
| [Explicit session](/api/sessions/create-session) | Retries, multiple runs, durable interaction, files, or reconnecting to events matter. | Two requests to begin: create the session, then start the run. |

One-shot still creates durable resources; record the IDs it returns. For anything your product
depends on, create the session explicitly.

## Read resource state

Session status describes whether the continuity boundary can accept or continue work:

| Session status | Meaning |
|---|---|
| `creating` | Tanomis is provisioning the session. |
| `ready` | The session is prepared and can accept a run. |
| `running` | A run is active. |
| `idle` | No run is active; the session can be reused. |
| `stopping` | Stop is fencing new work and releasing the active runtime. |
| `stopped` | The session was explicitly stopped and must be resumed before another run. |
| `failed` | Session activation or runtime failed; explicit resume can attempt recovery. |
| `expired` | The session reached its lifecycle limit; explicit resume can reactivate it. |

Run status describes one execution attempt:

| Run status | Meaning |
|---|---|
| `queued` | Admitted and waiting for dispatch. |
| `running` | Executing or waiting on a durable human interaction. |
| `succeeded` | Completed successfully. |
| `failed` | Ended with an error; inspect `error_code` and `error_message` when present. |
| `cancelled` | Ended after cancellation. |

`succeeded`, `failed`, and `cancelled` are terminal. A terminal run stays readable through the
[run resource](/api/runs/start-run), and the session returns to `idle` for the next run.

The public session status does not reveal worker residency. An `idle` session may keep a runtime
warm or be transparently suspended and restored when work arrives. Do not call resume for that;
reserve it for `stopped`, `failed`, or `expired` sessions.

## One run at a time

One session admits at most one active run, where active means `queued` or `running`. Starting
another run while one is active returns `409 Conflict`. Wait for a terminal state, use another
session for independent concurrency, or retry the same run idempotently. Terminal runs do not hold
the slot, so a reused session is an ordered history:

```text
session A
  run 1: succeeded
  run 2: failed
  run 3: running
```

Files and durable interactions belong to the session. Attachments select session files for one
run; they do not move the files anywhere.

## Make retries deliberate

Use a stable idempotency key for one logical operation and never recycle it for new intent. Keys are
opaque, at most 255 bytes, and scoped differently at each boundary:

| Operation | Same key | Changed intent or no key |
|---|---|---|
| Create session | For the same authenticated caller, tenant, and provisioning inputs, returns the original non-terminal session with `200 OK`. | Changed provisioning inputs return `409 Conflict`. Reusing the key after the original session is `stopped`, `failed`, or `expired` returns `412 Precondition Failed`. Omitting the key creates a new session. |
| Start run | Within the same session, returns the original active or terminal run; a JSON replay is `200 OK`, while a streaming replay follows the original event stream. | Changed input, model, or work does not replace the original run, so bind the key to one immutable request in your client. Attachment-set drift returns `409 Conflict`. A new or absent key starts a new run only when no run is active; otherwise it returns `409 Conflict`. |

The run key is scoped to the session; the same string in another session refers to nothing.
A one-shot retry creates another session first, so a run key in its body cannot make the whole
one-shot operation retry-safe.

## Inspect, cancel, stop, and resume

Resource reads are authoritative. A stream close, command exit, or accepted cancellation does not
replace them.

| Action | Request | Then |
|---|---|---|
| Read | `GET /v1/sessions/{session_id}` and `GET /v1/sessions/{session_id}/runs/{run_id}` | Use the run's status; the session read carries `awaiting_input`. |
| Cancel | `POST /v1/sessions/{session_id}/runs/{run_id}/cancel` | Read the run until it is terminal. The session's history is untouched. |
| Stop | `POST /v1/sessions/{session_id}/stop` | Stop fences new runs and settles active work before `stopped`. A conflict during admission means the boundary is still settling: inspect and retry. |
| Resume | `POST /v1/sessions/{session_id}/resume` | Resume only a `stopped`, `failed`, or `expired` session. It keeps the session ID and pinned version and returns to `ready`; on a live session it conflicts. |

## Handle a run waiting for a person

> **Availability:** Durable interactions are on in hosted Tanomis and operator-enabled when self-hosted. See [What is available where](/reference/availability).

A pending interaction adds no run status. The run remains `running`; a session read exposes the
derived signal `awaiting_input: true`. List the session's pending interactions, answer by the
stable `interaction_key`, and keep observing the same run. Tanomis may suspend and restore the
runtime while it waits without changing the public model. The full first-answer-wins flow is in
[Approval-required actions](/guides/run/approval-required-actions).

A run may instead bind to durable settlement work that can continue
from a later session; see [Durable work and settlement](/guides/run/durable-work-and-settlement).

## Keep lineage separate

A later run continues the same session. It is not a fork and it is not a delegated session; those
create separate lineage and authority and have their own guides.

## Where to go next

- [Invoke from your application](/guides/run/invoke-from-your-application): the six-step flow
  from a backend.
- [Stream and replay events](/guides/run/stream-and-replay-events): watching a run safely.
- [Session workspace and state](/guides/build/session-workspace-and-state): what persists.
