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

# Sessions

> Start a session, watch it work, and continue, stop, or clean it up.

A session is one run of an agent on a task. It runs in its own sandbox, starts on the agent's current version in the environment you choose, and keeps a transcript you can stream, read, and continue.

<Note>
  You need an [agent](/recursion/agents) and an [environment](/recursion/environments). If the environment has a setup script, it must have passed a current setup run. Starting, messaging, moving, and stopping sessions needs the Developer or Admin role; the User role can list and read them, and can read a session's version state. API calls use a key from [API keys](/recursion/api-keys).
</Note>

## Start a session

Send the agent, the environment, and the task. Name the files you want back, so the agent saves them as deliverables.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Sessions**, then click **Launch session**.
    2. Under **Configuration**, select an **Agent** and an **Environment**.
    3. Under **Task**, write the **Opening message**: the task, the deliverables you want by name, and what done looks like.
    4. Optionally, choose vaults under **Credentials**, a mode under **Team**, and add **Custom metadata**.
    5. Click **Launch session**.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -H 'Idempotency-Key: weekly-risk-review-2026-09-25' \
      -d '{
        "agent_id": "b7e1c9a4-3d62-4f15-8a07-5c2e9f6d1b38",
        "environment_id": "4c8e2a61-9b3f-4d70-a5e2-1f6b8c9d0e47",
        "message": "Review the open incidents and write a risk summary named risks.md.",
        "metadata": {"customer_id": "acme-042", "run": "weekly"}
      }'
    ```
  </Tab>
</Tabs>

The session is accepted right away and starts in the background. The response is `202 Accepted`:

```json theme={"theme":"css-variables"}
{
  "session_id": "e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53",
  "status_path": "/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53"
}
```

Keep `session_id`. `202` means the session was accepted, not that it is running. `status_path` is relative to `https://api.recursion.labelbox.com` and already starts with `/managed-agents`, as does the `Location` response header.

The `Idempotency-Key` header is required. Use a value that identifies the work, such as a job id, and send it again on every retry: the same request with the same key returns the same session instead of starting a second one.

<Accordion title="All start fields">
  | Field | Required | Description |
  | - | - | - |
  | `agent_id` | Yes | The agent to run. The session starts on the agent's current version and reports it as `agent_version_id`. You cannot choose an older version here. To change the version later, see [Move a session to another version](#move-a-session-to-another-version). |
  | `environment_id` | Yes | The environment the sandbox is built from. If it has a setup script without a current passing setup run, the request fails with `422 environment_not_verified`. |
  | `message` | No | The opening message: the task and what done looks like. It stays in the agent's context for the whole run, so context compaction cannot summarize it away. A message of only whitespace returns `400`. |
  | `resources` | No | Up to 500 files to mount read-only in the sandbox before the first turn, as `[{ "type": "file", "file_id" }]`. One unknown file id refuses the whole request with `404`. See [Files](/recursion/files). |
  | `vault_ids` | No | Vaults to grant. Omit to use the agent's default vaults. `[]` grants none. See [Vaults](/recursion/vaults). |
  | `credential_refs` | No | `[{ "vault_id", "credential_id" }]`. Narrows access to exactly these credentials across the granted vaults. `[]` with non-empty `vault_ids` is rejected. |
  | `referenced_session_ids` | No | Up to 10 earlier sessions the agent may read. If you cannot read one of them, the request fails with `404` and nothing starts. See [Referenced sessions](/recursion/referenced-sessions). |
  | `metadata` | No | Your own string key/value pairs, such as a customer id. Up to 32 entries. Keys are up to 64 characters of letters, digits, `_`, `.`, and `-`. Values are 1 to 512 characters without control characters. Set only at start: it's returned on the root session by `getSession` and `listSessions`, you can filter on it, and there is no call to change it. See [Session operations](/recursion/session-operations) for metadata search. |
  | `outcome` | No | A definition of done for this session, as `{ "description", "rubric" }` with an optional `max_iterations`. The work is graded against the rubric, and the agent keeps revising until the rubric is met or the passes run out. Omit to use the agent's `default_rubric`, graded against `message`. See [Outcome rubrics](/recursion/outcomes). |
  | `skip_default_outcome` | No | `true` starts the session ungraded when the agent has a `default_rubric`. |
  | `team` | No | `{ "mode": "auto" \| "on" \| "off" }`. Omit to keep the agent's setting, which is `auto` unless the agent sets another. See [Teams](/recursion/teams). |
</Accordion>

<Accordion title="Retry a start safely">
  | You send | Result |
  | - | - |
  | No key | `400 invalid_request`. |
  | A key that is empty, longer than 256 characters, has non-visible characters, or is sent more than once | `400 invalid_request`. |
  | The same key, exact HTTP method, escaped path, raw query, and raw body bytes | The original `202` response again, with `Idempotency-Replayed: true`. No second session starts. The first response carries `Idempotency-Replayed: false`. |
  | The same key with any different request-identity bytes | `409 idempotency_conflict`. |
  | The same key while the first request is still being processed | `409 idempotency_in_progress` with `Retry-After`. Wait, then resend the same request byte for byte. |
  | The same key after the first attempt failed with a non-2xx response | The key is released. The retry runs as a new attempt. |
  | The key cannot be checked or reserved | `503 idempotency_unavailable`. The request did not run, so resend the same request with the same key. |

  Keys share one namespace across keyed mutations in your organization, so include the operation and work identity. Completed receipts last about 24 hours, pending claims can be reclaimed after about 1 hour, and deduplication is not guaranteed after expiry. The session id is derived deterministically from the durable receipt, so an exact replay returns the same `session_id`. See [Idempotent mutations](/recursion/api#idempotent-mutations).
</Accordion>

## Watch it work

Stream the session's events to follow along as the agent works. The stream replays what already happened, then stays open for new events, and closes when the session reaches a terminal status.

<Tabs>
  <Tab title="Console">
    Open the session from **Sessions**. The transcript updates live, and the status badge shows the current state.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -N 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/events/stream' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

You see the agent's steps as they happen. This output is abridged; each `data` line carries the full event.

```text theme={"theme":"css-variables"}
event: session.status_running
event: agent.tool_use
event: agent.tool_result
event: agent.message
data: {"type":"agent.message","content":[{"type":"text","text":"Found 12 open incidents."}]}
event: agent.artifact
event: session.status_idle
data: {"type":"session.status_idle","stop_reason":"end_turn"}
```

[Events](/recursion/events) lists every frame type and shows how to resume a dropped stream. To poll instead, read the session with `view=summary` and stop on any terminal status, not only `completed`, and also when the session needs you.

<Warning>
  An interrupted or sleeping session stays `active`. A loop that waits only for a terminal status keeps waiting until someone sends a message, the wake time arrives, or the session is cancelled.
</Warning>

## What success means

When the agent loop finishes, `status` becomes `completed` whether or not the task succeeded. Read the final message and open the files it saved to judge the work.

To have the work checked against a rubric before the session is done, see [Write outcome rubrics](/recursion/outcomes).

| You see | Result |
| - | - |
| `status: completed`, `execution_state: idle`, `stop_reason: end_turn` | The agent finished. Check its final message and deliverables against your task. |
| `status: failed` | The session hit an error. Read `failure`. |
| `status: cancelled` | The session was cancelled. Events and deliverables stay readable. |
| `status: active`, `execution_state: idle`, `stop_reason: interrupted` or `sleeping` | Not finished. The session is paused and waits for a message or its wake time. |
| `status: active`, `execution_state: idle`, `stop_reason: requires_action` | Not finished. The session needs your review before it continues. |
| `status: active`, `execution_state: idle`, `stop_reason: out_of_credit` | Not finished. Your tenant ran out of credit. Buy credits; it continues on its own within about a minute if it paused in the last 72 hours. |

When a session fails, `failure` holds `phase`, `code`, `message`, `retryable`, `category`, and `at`. `category` is `transient` (retry), `caller_error` (fix the request or setup), or `internal`.

Deliverables appear in the session's **Files** tab. Files the agent leaves elsewhere stay in `/workspace` for later turns, even after a stopped sandbox is released, and can be used when the workspace reopens successfully, but they aren't captured as deliverables. Deleting the session removes access to its workspace. See [Deliverables and artifacts](/recursion/artifacts) and [Download session deliverables](/recursion/files#download-session-deliverables). To give a session input files, attach them in **Launch session**, with `resources` at start, or in the session's **Files** tab while it runs.

<Accordion title="Read a session's details">
  In **Sessions**, click a session. The **Session** tab shows its state, agent, environment, and **Custom metadata**. A session an automation started also shows **Started by**: the **Automation**, the **Trigger** that fired, and the **Run**, each linking back to the automation. See [Automations](/recursion/automations).

  With the API, `view=full` (the default) returns the whole session, including the agent and model snapshots and the resolved configuration. `view=summary` omits `agent_snapshot`, `model_snapshot`, and `config`, which makes polling cheaper. Any other `view` value returns `400`.

  ```bash theme={"theme":"css-variables"}
  curl 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53?view=summary' \
    -H "Authorization: Bearer $RECURSION_API_KEY"
  ```

  ```json theme={"theme":"css-variables"}
  {
    "session_id": "e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53",
    "root_session_id": "e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53",
    "session_path": "/",
    "kind": "chat",
    "status": "completed",
    "execution_state": "idle",
    "stop_reason": "end_turn",
    "agent_id": "b7e1c9a4-3d62-4f15-8a07-5c2e9f6d1b38",
    "agent_version_id": "9d2f7b1e-6c43-4a85-b0e9-3a7c5d8f2e16",
    "environment_id": "4c8e2a61-9b3f-4d70-a5e2-1f6b8c9d0e47",
    "metadata": { "customer_id": "acme-042", "run": "weekly" },
    "costUsd": "0.418204",
    "costState": "final",
    "created_at": "2026-09-25T14:02:11Z",
    "updated_at": "2026-09-25T14:19:48Z",
    "last_activity_at": "2026-09-25T14:19:48Z"
  }
  ```

  | Field | Description |
  | - | - |
  | `root_session_id`, `parent_session_id`, `session_path` | Where the session sits in its tree. A root has `session_path` `/` and no parent. |
  | `kind` | `chat` for sessions you start, `subagent` for sessions an agent starts, `session_analyst` for the [session analyst](/recursion/session-operations). |
  | `agent_version_id` | The agent version the session runs. It changes only when you [move the session to another version](#move-a-session-to-another-version). |
  | `metadata` | Set at start. Present on root sessions only. |
  | `wake_at` | When a sleeping session wakes on its own. |
  | `concurrency_slot_held` | Whether the session counts against the agent's `max_concurrent_sessions`. |
  | `costUsd`, `costState` | What the whole session tree cost, and whether that is `final` or `so_far`. See [Usage and cost](/recursion/usage-and-cost). |
  | `failure` | Present only after a failure. |
</Accordion>

## Continue the conversation

Send a follow-up to add to the task or ask for changes. A running session reads it at its next turn. An idle or finished root session picks the work up again, and applies a [version move](#move-a-session-to-another-version) that is waiting before it does. The full send options, including the delivery states in the response, are in [Events](/recursion/events).

<Tabs>
  <Tab title="Console">
    Open the session, type in **Message the agent…**, and click **Send**. While the agent is working the button reads **Queue**.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/events' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"message": "Also add a mitigation for each high-severity item."}'
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{ "ok": true, "delivery_state": "resumed", "events_accepted": 1 }
```

Only root sessions resume. A message to a subagent session in a terminal status returns `409 conflict`; message the root session instead.

To change direction mid-turn, interrupt instead:

* **Interrupt** stops the current model call and gives a running tool a short grace period to stop. The session waits, with its sandbox, for your next message.
* **Interrupt and send** stops the current turn and starts a new one with your message, in one call.

<Accordion title="Interrupt, and interrupt and send">
  An interrupt stops the current model call right away and gives a running tool a short grace period to stop. If messages are queued, the newest starts a fresh turn. Otherwise the session stays `active` and goes `idle` with `stop_reason: interrupted`, ready for your next message. The sandbox stays up. In the console, click **Interrupt** under the last event while the agent is working.

  ```bash theme={"theme":"css-variables"}
  curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/interrupt' \
    -H "Authorization: Bearer $RECURSION_API_KEY" \
    -H 'Content-Type: application/json' \
    -d '{}'
  ```

  The response is `{ "interrupted": true }`. A session with nothing running (`idle` and not sleeping or waiting on its subagents, or already finished) answers `409 conflict`, and nothing is recorded. `interrupted: false` means the stop was recorded but reaching the running work was not confirmed, usually because the run ended as the request arrived.

  `interruptAndSendSessionMessage` stops the current turn and starts a fresh one with your message in one call. Send `message` for plain text, or `content` for content blocks; `content` wins if you send both. In the console, type your message and click **Interrupt & send**, which appears while the agent is working.

  ```bash theme={"theme":"css-variables"}
  curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/interrupt-and-send' \
    -H "Authorization: Bearer $RECURSION_API_KEY" \
    -H 'Content-Type: application/json' \
    -d '{"message": "Stop. Only cover incidents opened this week."}'
  ```

  The response has the same shape as a send, with `events_accepted: 2`: the interrupt and your message.
</Accordion>

### Send an image

To show the agent a screenshot or chart, attach it to a message. The model sees the image when its model accepts images. In the session's message box, click **Attach image**, choose the image, write your message, and click **Send**. To use an image from your library, click **Attach from Files**. Send PNG, JPEG, or WebP; the type is detected from the bytes. An image the session's image limits refuse returns `400` with the reason. See [Limits](/recursion/limits).

## Move a session to another version

A session runs the agent version it started on until you move it. Move it to give work in progress new instructions, tools, skills, or access without starting over: the transcript, sandbox, and files stay.

<Tabs>
  <Tab title="Console">
    1. Open the session. When its agent has a newer version, a notice under the header names both, for example "This session runs Version 2 of backend-fixer. The agent is now on Version 3".
    2. Click **Use Version 3**.
    3. Read the comparison of the two versions, then click **Use Version 3** to confirm.

    The notice changes to "Moving to Version 3" and says when the move applies. Click **Withdraw** to cancel a move that hasn't applied yet. If the move is refused, the notice says why and offers **Try again**.

    To move several sessions at once, open the agent and choose the **Sessions** tab. Select the sessions marked **Older version**, click **Use Version 3**, and confirm.

    The console moves a session to its agent's latest version. To move to any other version, including an older one, use the API.
  </Tab>

  <Tab title="cURL">
    Send the version the session should run with `replaceSessionAgentVersion`:

    ```bash theme={"theme":"css-variables"}
    curl -X PUT 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/agent-version' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"agentVersionId": "5a0c8e37-1f94-4b2d-a6c3-7e9d2b4f8a15"}'
    ```
  </Tab>
</Tabs>

The request records the version to move to. It doesn't interrupt the session, wake it, or start any work. The session changes before its next model call, so the response is `200` with `state: pending`:

```json theme={"theme":"css-variables"}
{
  "agentVersionId": "5a0c8e37-1f94-4b2d-a6c3-7e9d2b4f8a15",
  "currentAgentVersionId": "9d2f7b1e-6c43-4a85-b0e9-3a7c5d8f2e16",
  "currentVersion": 2,
  "state": "pending",
  "pending": {
    "agentVersionId": "5a0c8e37-1f94-4b2d-a6c3-7e9d2b4f8a15",
    "version": 3,
    "requestedAt": "2026-09-25T15:04:09Z",
    "requestedByUserId": "usr_01a0867d1b0875be9f9a9938ffb90971"
  },
  "lastFailure": null,
  "latestAgentVersion": {
    "agentVersionId": "5a0c8e37-1f94-4b2d-a6c3-7e9d2b4f8a15",
    "version": 3
  },
  "members": [
    {
      "sessionId": "e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53",
      "name": "",
      "role": "root",
      "adoption": "pending",
      "agentVersionId": "9d2f7b1e-6c43-4a85-b0e9-3a7c5d8f2e16",
      "version": 2
    }
  ]
}
```

The version must belong to the session's agent, and it can be older or newer than the one the session runs. The request needs no `Idempotency-Key`:

* **Repeating a move that is pending, or that the session already applied, changes nothing.** You get the current state back.
* **Repeating a move after it failed asks again.**
* **The later of two different requests wins.**
* **Sending the version the session runs now withdraws a pending move.**

This operation's fields are camelCase. The session itself still reports its version as `agent_version_id`.

<Accordion title="Which sessions you can move, and who can move them">
  * **Root sessions that run an agent.** That includes sessions you start in the console or through the API, and sessions an automation or Slack starts. A subagent session follows its root, so move the root. Sessions the service runs itself, such as the session analyst, stay on the version they started with, and so do pull request review sessions.
  * **Live sessions, and finished ones that can continue.** A session that a follow-up message can start again can be moved. A session that is still stopping, or that can no longer continue, can't.
  * **To any version of the same agent.** A version of another agent returns `404 not_found`.
  * **With create and update access.** A move can give the session the vaults a new session on that version would receive, so it needs both, and reading the state needs read access. The Developer and Admin roles have all three; the User role can read the state and can't move a session. See [Organizations and roles](/recursion/organizations-and-roles).
  * **Not through the MCP server.** An agent that calls the API through the [Recursion MCP server](/recursion/ai-coding-agents) can read a session's version state with `getSessionAgentVersion`. It can't move a session: `replaceSessionAgentVersion` isn't among the MCP tools.
</Accordion>

### Check the move

`getSessionAgentVersion` returns the version the session runs, the move that is waiting, the agent's latest version, and how far each agent in the session's tree has followed. The state changes as the session works, so read it again instead of keeping a copy. A subagent session, or a session that wasn't started from an agent, answers `409 conflict`.

In the console, the transcript shows "Now on Version 3" once the session has moved, and the **Session** tab of the details panel shows the version in the **Agent version** row. For a session with subagents or teammates, **Agent version by member** on the same tab lists how far each one has followed.

```bash theme={"theme":"css-variables"}
curl 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/agent-version' \
  -H "Authorization: Bearer $RECURSION_API_KEY"
```

After the session has moved, the read shows the new version and nothing waiting:

```json theme={"theme":"css-variables"}
{
  "agentVersionId": "5a0c8e37-1f94-4b2d-a6c3-7e9d2b4f8a15",
  "currentAgentVersionId": "5a0c8e37-1f94-4b2d-a6c3-7e9d2b4f8a15",
  "currentVersion": 3,
  "state": "current",
  "pending": null,
  "lastFailure": null,
  "latestAgentVersion": {
    "agentVersionId": "5a0c8e37-1f94-4b2d-a6c3-7e9d2b4f8a15",
    "version": 3
  },
  "members": [
    {
      "sessionId": "e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53",
      "name": "",
      "role": "root",
      "adoption": "current",
      "agentVersionId": "5a0c8e37-1f94-4b2d-a6c3-7e9d2b4f8a15",
      "version": 3
    }
  ]
}
```

| `state` | Meaning | What to do |
| - | - | - |
| `current` | The session runs `currentAgentVersionId`. Nothing is waiting. | Nothing. |
| `pending` | A move is recorded and takes effect before the session's next model call. | Nothing. Send a message if the session is finished or you want it to move sooner. |
| `failed` | The last move was refused. The session still runs `currentAgentVersionId`, and `lastFailure` says why. | Fix the cause, then send the request again. |

<Accordion title="Response fields">
  | Field | Description |
  | - | - |
  | `agentVersionId` | The version the session is set to run: the pending version while a move is waiting, otherwise the version it runs now. The only field you send. |
  | `currentAgentVersionId`, `currentVersion` | The version the session runs now, as an id and a number. `currentVersion` is left out when the number can no longer be read. |
  | `state` | `current`, `pending`, or `failed`. |
  | `pending` | The move that is waiting: `agentVersionId`, `version`, `requestedAt`, and `requestedByUserId` when a user asked. `null` when nothing is waiting. |
  | `lastFailure` | Why the most recent move was refused: `agentVersionId`, `version`, `code`, `message`, and `at`. `null` when there is none, and while a newer move is pending. |
  | `latestAgentVersion` | The agent's newest version, as `agentVersionId` and `version`. `null` when the agent was deleted. |
  | `members` | The root session and every open thread in its tree, root first. Each has `sessionId`, `name`, `role`, and `adoption`, with `agentVersionId` when it runs an agent of its own, `version` when that agent is the root's, and `failure` when its move was refused. `name` is the name the thread was given, and is empty for the root. |

  | `role` | Where the member's configuration comes from |
  | - | - |
  | `root` | The session itself. |
  | `self` | A copy of its parent's own agent. Teammates are copies of the leader. |
  | `worker` | A thread with no agent of its own. It takes its parent's model and tools. |
  | `roster` | Another agent, named by its parent's roster. |

  | `adoption` | Meaning |
  | - | - |
  | `current` | Nothing to adopt. |
  | `pending` | The member is running and adopts before its next model call. |
  | `idle` | The member isn't running. It adopts when it is next given work. |
  | `failed` | The member's move was refused, and it keeps the configuration it had. `failure` says why. |
  | `retained` | The member keeps the configuration it has, because its parent is closed or couldn't adopt, or its roster entry was removed. |

  On the root, `adoption` follows `state`: `pending` while a move waits, and `failed` after a refused one.
</Accordion>

### When the move applies

A move never cuts into a model call. The session finishes the call it is making and the tool calls that came from it, then changes version before its next model call.

* **A working session** moves before its next model call.
* **A sleeping session** moves when it next runs. Moving it doesn't wake it.
* **A finished session** moves when you send its next message.
* **A session retrying a failing model call** doesn't change model between retries of the same call. A move to a version with a different model applies before the session's next model call, once the retried call succeeds or you send the session a message. A move that keeps the model can apply at the next retry.

Moving a session to the version it already runs changes nothing: it doesn't resolve skills or list MCP tools again.

### What changes and what stays

| Follows the new version | Stays as it is |
| - | - |
| System prompt | Transcript and events |
| Model and model settings | Sandbox, `/workspace`, and running processes |
| Tool settings, such as web search, and custom tools | Environment |
| MCP servers, with their tools listed again | Attached files and referenced sessions |
| Skills, resolved again | Custom metadata |
| Granted apps and integrations | Team mode |
| Roster and delegation limits | Cost so far |
| [File history](/recursion/agent-file-history) setting | |
| Default vaults and credentials. See **Credentials after a move**. | |

The agent receives one notice about the move, a `message` event with `role: system` that you can read in the session's events. It names what changed: that its instructions were replaced, whether the model changed, and the skills, tools, MCP servers, integrations, credentials, and roster agents that were added or removed. It lists names only, never secret values. A skill that kept its name and changed its content is listed as updated, and the agent activates it again before relying on it. A session that hasn't done any work yet gets no notice, because it simply starts on the new version.

When the new version uses a different model, the earlier conversation is summarized for the new model before the session continues. A message the earlier model hadn't read yet stays as you wrote it, and the original events stay in the log.

<Accordion title="Credentials after a move">
  A move carries the new version's default credential changes onto the session:

  * **Vaults and credentials the new version adds are added.**
  * **Vaults and credentials the new version removes are removed.**
  * **What the person who launched the session changed is kept.** A vault they added stays, and a credential they unchecked stays out.

  Two cases differ:

  * **A session whose trigger set its own credentials keeps them.** When Slack or an automation started the session with its own grant, including an empty one, the move leaves that grant alone. The record of the move and the agent's notice both say that the version's defaults changed and the session kept its own.
  * **A session granted whole vaults gains an added vault whole.** A session started with `vault_ids` and no `credential_refs` holds every credential in each vault, so it receives every credential in a vault the new version adds, including any added to that vault later.

  A vault or credential the new version names that no longer exists is skipped, and the session's events record a warning.

  When each kind of credential takes effect:

  | Credential | After the move |
  | - | - |
  | Token for an MCP server | Immediately. An added token is sent from the session's next MCP request, and a removed one is no longer sent. |
  | Granted apps and integrations | At the move. Access is issued for the new version's apps. |
  | Environment-variable secret, added | On the session's next sandbox. A running sandbox doesn't receive it. |
  | Environment-variable secret, removed | Still present in a running sandbox until that sandbox is replaced. Its value stays masked in tool output while it is there, as long as the credential is still in its vault with the same value. |

  <Warning>
    Removing an environment-variable secret from a version doesn't take it out of a sandbox that is already running. To revoke it, rotate the secret at its provider. See [Rotate a credential](/recursion/vaults#rotate-a-credential).
  </Warning>
</Accordion>

<Accordion title="Subagents and teams">
  Every open thread in the session's tree follows the move before its own next model call, after its parent has followed. A thread that is running finishes its current model call first, and one that isn't running follows when it is next given work. Each receives its own notice.

  | Member | After the move |
  | - | - |
  | A copy of its parent's agent, including teammates | Its parent's new configuration in full: prompt, model, skills, tools, MCP servers, and grants. |
  | A general worker with no agent of its own | Its parent's new model, skills, tools, MCP servers, and grants. It keeps the general worker prompt. |
  | A different agent from the roster | What the new roster says. A changed `version` moves it to that version, and an entry without `version` moves it to that agent's latest version. |
  | An agent the new roster no longer lists, or lists under the same name for another agent | Keeps the configuration it has and finishes its work. |

  A thread whose parent is closed, or whose parent couldn't adopt, also keeps the configuration it has. `members` reports it as `retained`.

  Team mode and seats are fixed when the session starts. A move never adds seats or removes a teammate, and the board keeps its tasks, claims, and notes. Seats can shrink: when the new version's `max_concurrent_threads` is lower than the team size, the team fills only that many seats from then on, and teammates already working stay.
</Accordion>

### If a move fails

A move that can't be applied never stops the session. The session keeps the version it has and keeps working, `state` becomes `failed`, and `lastFailure` gives a `code` and one fixed sentence in `message`. Fix the cause and send the request again.

<Accordion title="Failure codes">
  | `code` | Meaning |
  | - | - |
  | `agent_unavailable` | The session's agent no longer exists. |
  | `agent_version_unavailable` | The requested version no longer exists. |
  | `agent_changed` | The agent changed while the session was moving. Send the request again. |
  | `session_not_eligible` | The session can no longer move to another version. |
  | `evaluation_toolset` | The version uses a toolset this kind of session can't run. |
  | `model_not_allowed` | The version's model isn't available to this session. |
  | `model_unavailable` | The version's model couldn't be found. |
  | `model_config_invalid` | The version's model settings aren't valid for its model. |
  | `project_required` | The version's model requires a project, and the session has none. |
  | `configuration_invalid` | The version's configuration can't run in this session. |
  | `temporarily_unavailable` | A temporary problem stopped the move. Send the request again. |

  The list can grow, so accept codes that aren't in it.
</Accordion>

A temporary problem doesn't fail a move at once. The session tries again before its next model calls, and after three attempts records `temporarily_unavailable`. An MCP server that is new in the version and can't be reached, or that refuses its token, doesn't fail the move either: the session moves without that server's tools and records a warning, as it does at start.

Each move is recorded in the session's events as a `session_status` event:

| Outcome | `event_status` | `content.metadata` |
| - | - | - |
| Moved | `agent_version_changed` | `fromAgentVersionId`, `toAgentVersionId`, `fromVersion`, `toVersion`, `requestId`, `requestedByUserId` when a user asked, a `message`, and `changes`: the names of what was added, updated, and removed. |
| Refused | `warning` | `code` is `agent_version_change_failed`, `reason` is the failure code, and `message` is its sentence, with the same version ids. |
| A thread kept its configuration | `agent_version_changed` | `retained` is `true`. |

A thread's events carry `applyId` in place of `requestId`.

<Accordion title="Limits of a move">
  * **Team mode and seats are fixed at start.** A move doesn't turn team mode on or off, and seats can shrink but never grow.
  * **A running sandbox keeps its environment variables.** Environment-variable secrets, and the network destinations a sandbox may reach for granted apps, are set when a sandbox is created. A move changes them on the session's next sandbox.
  * **A thread adopts after its parent.** A thread whose parent hasn't followed yet waits for it.
  * **A session doesn't change model between retries of one model call.** While a session retries a failing model call, a move to a version with a different model waits for its next model call, which comes once the retried call succeeds or you send a message.
  * **Moving to the current version does nothing.** It isn't a way to load a newer skill version or list an MCP server's tools again.
  * **The environment never changes.** To use another environment, start a new session.
</Accordion>

## Stop or delete a session

| Action | What happens | Console |
| - | - | - |
| Cancel | Stops the session now. In-flight work is abandoned, the sandbox is torn down, and the status becomes `cancelled` with `stop_reason: interrupted`. Events and deliverables stay readable, and a follow-up message to a cancelled root session starts it again in a new sandbox. | **Session actions** › **Force stop…** › **Stop now** |
| Delete | Removes the session and all its subagent sessions from lists and reads, stops any running work, and tears down the sandbox. The transcript is retained, not erased. There is no undo. | **Session actions** › **Delete session…** › **Delete** |

<Tabs>
  <Tab title="Cancel">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/cancel' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>

  <Tab title="Delete">
    ```bash theme={"theme":"css-variables"}
    curl -X DELETE 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

Cancel answers `{ "cancelled": true }` and delete `{ "deleted": true }`. You can cancel any session that is not completed, failed, or already cancelled, including an idle one. The cancel response is the same when the session was already finished, so it is safe to repeat. Sandbox teardown can finish shortly after the response.

## Find sessions

`listSessions` returns sessions in your organization, most recently updated first. Filter by status, kind, agent, agent tags, or your metadata.

<Tabs>
  <Tab title="Console">
    In the sidebar, click **Sessions**. The **Status**, **Agent**, **Agent tags**, and **Metadata** filters cover every session. The search box matches id, agent, state, or metadata on the page shown; paste a full session id to find a session on any page. Click a column header to sort the page.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/sessions?status=failed&metadata=customer_id:acme-042&root_only=true&limit=50' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

`root_only=true` returns one row per run, without the subagent sessions under it. List rows are summaries: they leave out snapshots, `config` is empty, and token totals aren't included. Each row carries the session tree's `costUsd` and `costState` (see [Usage and cost](/recursion/usage-and-cost)).

<Accordion title="List parameters and paging">
  | Parameter | Description |
  | - | - |
  | `status` | One status, such as `failed`. |
  | `kind` | Comma-separated kinds, matched as OR: `chat`, `subagent`, `session_analyst`. Analyst sessions appear only when you name `session_analyst`. |
  | `agent_id` | Sessions of one agent. |
  | `tag_ids` | Comma-separated agent tag ids, up to 32. A session matches only if its agent currently has every tag. An unknown tag id returns `404`. |
  | `metadata` | `key:value`, repeatable up to 32 times. All pairs must match. The value is everything after the first colon. Child sessions of a matching root also match, so add `root_only=true` for one row per run. |
  | `metadata_key` | A key that must be present, repeatable up to 32 times. |
  | `root_only` | `true` returns root sessions only. |
  | `limit` | Page size. Default 100, maximum 1000. Larger values are reduced to 1000. |
  | `page_token` | The `next_page_token` from the previous page. |

  A page token is valid for one hour and only with the same filters and `limit`. Changing either returns `400 invalid_request`; start again without a token. Because the list is ordered by last update, a session that changes while you page can appear twice or be skipped. Deduplicate by `session_id`, or filter on a value that does not change.
</Accordion>

## How a session runs

Two fields describe a session. `status` says whether it's still live. While it's `active`, `execution_state` says what the agent loop is doing. When the loop isn't running, `stop_reason` says why.

| `stop_reason` | Meaning | What to do |
| - | - | - |
| `end_turn` | The agent finished its turn with nothing left to do. | Read the final message and deliverables. |
| `requires_action` | The session was stopped for review after failing the same way repeatedly, or it is waiting on a browser hand-off (`status: awaiting_human`). | After a review stop, fix what blocked it, then reply to continue. |
| `sleeping` | The agent chose to wait. The session stays `active`. | Nothing, or send a message to wake it before `wake_at`. |
| `awaiting_subagents` | A coordinator is waiting for subagents to report back. | Nothing. See [Multi-agent sessions](/recursion/multi-agent). |
| `out_of_credit` | Your tenant ran out of credit. The session paused before its next model request. Requests already in flight for the tenant complete and are charged, so the balance can end slightly below zero. Its subagents pause the same way. | Buy credits. A session paused in the last 72 hours continues on its own within about a minute, subagents included; send a message to continue it sooner or after that. See [Billing](/recursion/billing#when-credits-run-out). |
| `tenant_suspended` | Your account is suspended. The session paused without finalizing its work. | Contact support to restore the account, then send a message to continue. |
| `interrupted` | Someone interrupted or cancelled the run. | Send a message to continue. |
| `error` | The loop stopped on an error. | Read `failure`. |

<Accordion title="Statuses and execution states">
  | `status` | Meaning |
  | - | - |
  | `active` | The session is live. Check `execution_state` for what it is doing. |
  | `completed` | The agent loop finished. This does not by itself mean the task succeeded. See [What success means](#what-success-means). |
  | `failed` | The session stopped on an error. The `failure` object explains it. |
  | `cancelled` | Someone cancelled the session. Its sandbox is torn down. |
  | `awaiting_human` | The agent asked a person to handle a step in its browser display, such as a sign-in, which can happen only when the environment has computer use on. `active_handoff` describes the request. The agent resumes when you send a `handoff_resolved` event (see [Events](/recursion/events)), or on its own when the hand-off deadline passes: 30 minutes unless the agent set another, at most 4 hours. Interrupting or cancelling the session also ends the hand-off. `execution_state` is `idle` with `stop_reason: requires_action`. |

  | `execution_state` | Meaning |
  | - | - |
  | `provisioning` | The sandbox is starting. This normally takes under a minute. After 5 minutes the session fails with `sandbox_provision_timeout`. |
  | `queued` | Waiting for a free slot under the agent's `max_concurrent_sessions`, or for sandbox capacity. |
  | `running` | The agent is working. |
  | `idle` | The loop is not running. `stop_reason` is always set. |

  `provisioning`, `queued`, `running`, and `idle` are execution states of a session whose `status` is `active`. `completed`, `failed`, and `cancelled` are terminal statuses: the session does not change on its own after reaching one. A follow-up message to a root session in a terminal status starts it again from `provisioning`, or from `queued` when the agent is at its cap. After a cancel, the session gets a new sandbox. After a completed or failed run, the sandbox is reused when possible.

  When a root session is stopped for review and no subagent is working, its sandbox is stopped with `/workspace` preserved, and restored from `/workspace` before the agent's next tool call; processes it started are gone. A session whose setup ran on its own compute keeps its sandbox until the environment's idle stop.
</Accordion>

<Accordion title="Every state transition">
  | From | When | To |
  | - | - | - |
  | Start | The start request is accepted | `provisioning`, or `queued` when the agent is at its `max_concurrent_sessions` cap |
  | `queued` | A slot or sandbox capacity frees up | `provisioning` |
  | `provisioning` | The sandbox is ready | `running` |
  | `provisioning` | No sandbox capacity is free yet | `queued` |
  | `provisioning` | The sandbox isn't ready within 5 minutes | `failed` |
  | `running` | The run is interrupted, the agent sleeps or waits for subagents, the session is stopped for review, or your tenant runs out of credit | `idle`, with a `stop_reason` |
  | `running` | The agent finishes its work | `completed` |
  | `running` | The agent asks a person for a browser hand-off | `awaiting_human` |
  | `running` | An unrecoverable error | `failed` |
  | `idle` (`sleeping` or `awaiting_subagents`) | A message, the wake time, a subagent's report, or a notice that every subagent it's waiting on has gone silent | `running` |
  | `idle` (`awaiting_subagents`) | Every subagent it was waiting on turns out to have finished or closed already, and `multiagent.interventionist.mode` is `shadow` or `off`, so there is no notice to read | `completed` |
  | `idle` (any other `stop_reason`) | A follow-up message | `provisioning`, or `queued` at the cap |
  | `awaiting_human` | A `handoff_resolved` event or the hand-off deadline | `running` |
  | Any live state | Cancel | `cancelled` |
  | `completed`, `failed`, `cancelled` | A follow-up message to a root session | `provisioning`, or `queued` at the cap |
</Accordion>

## What can go wrong

The most common problems:

* **The session stays `queued`.** The agent is at `max_concurrent_sessions`, or a slot freed while your tenant was out of credit. A queued session starts only when a slot is free and credit is available. Wait, cancel other sessions, raise the agent limit, or add credits.
* **`422 environment_not_verified` on start.** The environment's setup script has not passed a current setup run. Run environment setup, then start again. See [Environments](/recursion/environments).
* **A version move stays `pending`.** A move applies before the session's next model call, and a finished or sleeping session makes none until it gets a message or wakes. Send a message. See [When the move applies](#when-the-move-applies).
* **`status: completed` but the task is not done.** The loop finishing does not mean the task succeeded. Read the final message, then send a follow-up with what is missing.

<Accordion title="Every session error">
  | Symptom or code | Cause | Fix |
  | - | - | - |
  | `400 invalid_request` on `Idempotency-Key` | The header is missing, malformed, or sent twice. | Send exactly one key of 1 to 256 visible characters. |
  | `409 idempotency_conflict` | The key was used for a different request. | Use a new key for different work. |
  | `409 idempotency_in_progress` | The first request with this key is still running. | Wait for `Retry-After`, then retry with the same key. |
  | `404 not_found` on start | An agent, environment, vault, or referenced session is missing or not visible to you. | Check each id and your access. |
  | `400 invalid_request` on `metadata` | Too many entries, a bad key, or an empty or too-long value. | Keep to 32 entries and the key and value rules in **All start fields**. |
  | `400 invalid_request` on `page_token` | The token expired or the filters changed. | List again without a token. |
  | The session stays `provisioning`, then fails with `sandbox_provision_timeout` | The sandbox did not start within 5 minutes. | Start again. If it repeats, check the environment. |
  | A poller never returns | The session is interrupted or sleeping and stays `active`. | Stop on `idle` states you care about, or send a message. |
  | `409 conflict` when messaging or deleting | A cancel is still finishing. | Retry after a few seconds. |
  | `409 conflict` when messaging a subagent | Subagent sessions in a terminal status do not resume. | Message the root session. |
  | `404 not_found` when moving a session | The session or the version is missing, deleted, or not visible to you, or the version belongs to another agent. Any `agentVersionId` that isn't a version of the session's agent answers this, including a value that isn't an id at all. | Send a version of the session's own agent. |
  | `409 conflict` on `session_id` when moving a session | The session is a subagent session, a session the service runs itself, a pull request review session, or one that is still stopping or can no longer continue. A session whose agent was deleted has no other version to move to. | Move the root session. A session that can't continue can't be moved. |
  | `409 conflict` on `session_id` when reading a session's version state | The session is a subagent session, or it wasn't started from an agent. | Read the root session. A session that wasn't started from an agent has no version to read. |
  | `409 conflict` on `agentVersionId` | The version uses a toolset a running session can't adopt. | Send another version. |
  | `409 conflict` with `details.retryable: true` when moving a session | Other requests kept changing the session's pending move while yours was recorded. | Send the request again. |
  | `400 invalid_request` on `agentVersionId` | The field is missing or empty, or the version's model runs only for a named project and the session has none. | Send one version id from `listAgentVersions`. |
  | `400 invalid_request` on `model` when moving a session | The version's model isn't available to this session. | Send a version whose model the session can run. |
  | `403 forbidden` when moving a session | Your role or key doesn't have both create and update access. | Use the Developer or Admin role. |
  | A move stays `pending` | The session hasn't made its next model call yet: it is finished or sleeping, or it is retrying a failing model call and the version changes the model. | Send the session a message, or wait for the retried call to succeed. |
  | A move shows `failed` | The version can't run in this session. The session keeps its earlier version. | Read `lastFailure`, fix the cause, and send the request again. |
  | `429 rate_limit_exceeded` | Too many requests. | Wait for `Retry-After`. Retry only when the operation's retry strategy in **Endpoints** permits replay. |

  Every error is a flat object with required `code` and `message` fields and optional `details`, which can include `field`, `requestId`, and `retryable`. See [Errors](/recursion/errors).
</Accordion>

Limits on metadata, referenced sessions, concurrency, and provisioning are in [Limits](/recursion/limits).

## Next steps

<CardGroup cols={2}>
  <Card title="Events" icon="wave-pulse" href="/recursion/events">
    Stream, send, and read every kind of session event.
  </Card>

  <Card title="Session operations" icon="gauge" href="/recursion/session-operations">
    Search metadata, inspect compute, ask the analyst, and read the tree.
  </Card>

  <Card title="Files" icon="file" href="/recursion/files">
    Give a session input files and download its outputs.
  </Card>

  <Card title="Deliverables and artifacts" icon="box-archive" href="/recursion/artifacts">
    Ask for deliverables and see how they're kept.
  </Card>
</CardGroup>
