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

# Agent sessions

> A long-running agent loop with a cost cap, an iteration limit and sandbox rotation — state survives the sandbox it started in.

A sandbox is ephemeral; an agent's work often is not. An **agent session** is a
durable wrapper around a series of executions: it carries a budget, keeps
context across sandbox rotations, and can be reattached to after a client
disconnects.

Use a plain [sandbox](/stateset-sandbox/stateset-sandbox-api-flow) when the work
is one command. Use a session when an agent will run many commands over a long
enough period that the sandbox underneath it may be replaced.

<Note>
  Sessions are served from the sandbox controller at
  `https://api.sandbox.stateset.com/api/v1`, and authenticate with the sandbox
  scheme — `Authorization: ApiKey <key>`, not `Bearer`. The key needs
  [`sandbox:write`](/stateset-sandbox/stateset-sandbox-security-guide#scopes)
  for anything that mutates a session, and `sandbox:read` to inspect one.
</Note>

## Create a session

The budget is the point. All three limits are optional, and a session with none
of them set has nothing stopping it.

```bash theme={null}
curl --request POST "https://api.sandbox.stateset.com/api/v1/agent/sessions" \
  --header "Authorization: ApiKey $STATESET_SANDBOX_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "backlog-triage",
    "budget": {
      "costCapCents": 500,
      "iterationLimit": 200,
      "durationLimitSeconds": 7200
    },
    "rotation": {
      "preRotateBufferSeconds": 30,
      "includeProcessState": false
    },
    "sandbox": {
      "cpus": 2,
      "memory": "4Gi",
      "isolation": "gvisor",
      "timeoutSeconds": 3600
    },
    "clientId": "worker-7"
  }'
```

| Field                             | Purpose                                                       |
| --------------------------------- | ------------------------------------------------------------- |
| `budget.costCapCents`             | Spend ceiling for the whole session                           |
| `budget.iterationLimit`           | Maximum executions                                            |
| `budget.durationLimitSeconds`     | Wall-clock ceiling                                            |
| `rotation.preRotateBufferSeconds` | Grace period before a sandbox is replaced, for cleanup        |
| `rotation.includeProcessState`    | Carry process state across a rotation, not just files and env |
| `sandbox`                         | The sandbox spec each rotation is created from                |
| `clientId`                        | Your own handle for reattaching later                         |

<Warning>
  `costCapCents` is a cap on the session, not a per-execution limit, and it is
  accounted after each execution rather than predicted before one. A single
  expensive command can cross the cap; the session stops afterwards. Size the
  cap for what you can afford to overshoot by one execution.
</Warning>

## Run work in it

```bash theme={null}
curl --request POST "https://api.sandbox.stateset.com/api/v1/agent/sessions/$SESSION_ID/exec" \
  --header "Authorization: ApiKey $STATESET_SANDBOX_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "command": ["python3", "triage.py", "--batch", "7"],
    "workingDir": "/workspace",
    "timeoutMs": 120000
  }'
```

`timeoutMs` is bounded to 1s–10min. Each execution is charged against the
budget, and the response carries what remains.

## Rotation, and what survives it

A session outlives the sandbox it started on. When the underlying sandbox
reaches its timeout, the session enters `rotating`, provisions a fresh one, and
carries context across:

* the working directory
* environment variables
* any custom state you have written to the session context

```bash theme={null}
# Anything the next sandbox must know goes in the session context
curl --request POST "https://api.sandbox.stateset.com/api/v1/agent/sessions/$SESSION_ID/context" \
  --header "Authorization: ApiKey $STATESET_SANDBOX_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{ "cursor": "batch-7", "processed": 412 }'
```

<Warning>
  Files written outside the working directory do **not** survive a rotation, and
  neither do running processes unless `rotation.includeProcessState` is set.
  Anything the next sandbox needs belongs in the session context or in
  [artifact storage](/stateset-sandbox/stateset-sandbox-api-reference) — not in
  `/tmp`.
</Warning>

## Surviving a disconnect

A session is addressable after your client dies. Reattach with the `clientId`
you supplied at creation:

```bash theme={null}
curl --request POST "https://api.sandbox.stateset.com/api/v1/agent/sessions/reattach" \
  --header "Authorization: ApiKey $STATESET_SANDBOX_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{ "clientId": "worker-7" }'
```

Send `POST .../heartbeat` while you hold a session so the controller can tell a
live client from an abandoned one.

## Lifecycle

```text theme={null}
pending ──▶ running ──▶ rotating ──▶ running ──▶ completed
              │  ▲                                  ▲
              │  └── resume ── paused ──────────────┘
              └──────────────────────▶ failed | cancelled
```

| Endpoint                                            | Purpose                                     |
| --------------------------------------------------- | ------------------------------------------- |
| `POST /agent/sessions`                              | Create                                      |
| `GET /agent/sessions`                               | List, filterable by `status`                |
| `POST /agent/sessions/reattach`                     | Reattach by `clientId`                      |
| `GET /agent/sessions/{id}`                          | Session detail                              |
| `DELETE /agent/sessions/{id}`                       | Delete                                      |
| `POST /agent/sessions/{id}/start`                   | Start a pending session                     |
| `POST /agent/sessions/{id}/exec`                    | Execute, charged against the budget         |
| `POST /agent/sessions/{id}/pause` · `/resume`       | Hold and continue                           |
| `POST /agent/sessions/{id}/approve`                 | Approve a step that needs sign-off          |
| `POST /agent/sessions/{id}/cancel` · `/stop`        | End the session                             |
| `POST /agent/sessions/{id}/heartbeat`               | Keepalive                                   |
| `GET /agent/sessions/{id}/events`                   | Event history                               |
| `POST /agent/sessions/{id}/context`                 | Update carried context                      |
| `GET` · `POST /agent/sessions/{id}/tools`           | List and register agent tools               |
| `POST` · `GET /agent/sessions/{id}/files`           | Write and read files on the current sandbox |
| `GET /agent/sessions/{id}/files/list` · `/download` | Browse and fetch                            |

## Next steps

<CardGroup cols={2}>
  <Card title="Active-horizon agents" icon="clock-rotate-left" href="/next-temporal/active-horizon-agents">
    The durable supervisor that drives sessions like these across hours.
  </Card>

  <Card title="Sandbox API flow" icon="code" href="/stateset-sandbox/stateset-sandbox-api-flow">
    The one-shot path, for work that fits in a single sandbox.
  </Card>

  <Card title="Runtime selection" icon="layer-group" href="/stateset-sandbox/stateset-sandbox-runtime-selection">
    Container, gVisor or MicroVM for the `isolation` field above.
  </Card>

  <Card title="Security guide" icon="shield-halved" href="/stateset-sandbox/stateset-sandbox-security-guide">
    What a session can reach, and how to narrow it.
  </Card>
</CardGroup>
