---
title: "Multi-turn sessions"
description: "This page covers live multi-turn sessions: one sandbox kept alive for a whole episode, actions applied step by step, and a final verifier that scores the state the agent left. It covers the Python API, writing a multi-turn environment, step rewards, and running sessions over HTTP."
slug: environments/sessions
---

## The session API

```python
from deepgym import Action, DeepGym, MultiTurnEnvironment

env = MultiTurnEnvironment(
    task='Write the word hello into notes.txt, then send done.',
    final_verifier_code=(
        'import json, pathlib\n'
        'p = pathlib.Path("notes.txt")\n'
        'ok = p.is_file() and p.read_text().strip() == "hello"\n'
        'print(json.dumps({"score": float(ok), "passed": ok}))\n'
    ),
)

dg = DeepGym(mode='docker')   # or 'apple', 'modal', 'e2b', 'daytona'; 'local' isn't isolated
with dg.session(env) as session:
    obs = session.start()
    obs, reward, done = session.step(Action(action_type='bash', content='echo hello > notes.txt'))
    obs, reward, done = session.step(Action(action_type='done'))
    result = session.score()
print(result.score, result.has_verdict)
```

- `start()` writes the environment's files, runs its setup, and returns the first
  observation. `step(action)` returns the new observation, the step reward and whether
  the episode is done. `score()` runs the final verifier.
- Or in one call: `trajectory, result = dg.run_episode(env, agent)`, where `agent` maps an
  observation to the next action.
- Shell actions are `code` (Python), `bash` and `write_file` (`path` relative to the work
  directory), plus `done`. Commands run under bash and report `exit_code` in the
  observation's metadata.
- The episode ends on `done`, after `max_steps` (default 10), or when an action's output
  has a line `DONE` (or a JSON line with `"done": true`). Each action has
  `timeout_per_step` seconds (default 30); setup has `setup_timeout` (default 120).
- `trajectory` holds every (observation, action) pair and the step rewards.
- One session runs one episode; open a new one per episode. Concurrent calls on a session
  queue up.

Computer-use environments use the same API with GUI actions; see
[computer use](/docs/environments/computer-use). To keep an episode as training data, record it with
[`dg.trace()`](/docs/traces).

## Backends and isolation

Every provider runs sessions. In a sandbox, agent commands run as an unprivileged
`dgagent` user in a loopback-only network namespace. Verifiers run offline as a separate
`dgverifier` user, with `python -I`, from a file only that user can read, so the agent
can't read, swap or hijack them, and leftover agent processes can't read or forge the
score.

`mode='local'` runs the agent's actions on your machine without isolation; use it only
for agents you trust.

## Writing a multi-turn environment

`deepgym new NAME --kind multi-turn` scaffolds a working one (see
[authoring](/docs/authoring/new-check-qa)). The fields:

| Field | What it is |
|---|---|
| `task` | The prompt the agent sees. |
| `files` | Text files written into the work directory before setup. |
| `setup_script`, `setup_code` | Bash, then Python, run after the files are written; the setup output is the first observation. A setup step that exits non-zero raises `SandboxError`. |
| `step_verifier_code` | Optional; runs after every step and returns its reward. |
| `final_verifier_code` | Scores the final state. |
| `max_steps`, `timeout_per_step`, `setup_timeout` | Limits. |
| `allowed_actions` | The action types the agent may use; anything else raises `ActionError`. |
| `image` | The image the sandbox boots from. |

Verifiers print protocol JSON, as single-turn ones do (see
[verifiers](/docs/authoring/verifiers)). A score only counts if the verifier exits
cleanly (0, or 1 for a failed run), and a score printed before a crash is never kept. A
verifier that crashes before it opens a file in the agent's work directory or starts the
agent's code has no verdict; after that the crash came from handling the agent's work, so
it scores 0 (`solution_error`). One killed by a signal has no verdict. Only stdout counts,
so a JSON line on stderr can't replace the verdict; stderr is kept in `RunResult.stderr`.

Session verifiers that grade the agent's code load it with `load_solution` too, so it runs
isolated from the verifier: locally in the same sandbox as single runs, and in sandbox
images with sudo (Daytona's default has it) as a third user, `dgsolver`, with its groups
cleared, no-new-privs and an empty capability bounding set. Images without sudo, such as
the default `python:3.12-slim`, have no `deepgym_solution` in sessions, and neither do
Docker sessions: Docker runs them with no-new-privileges, which blocks sudo.

## Step rewards and verdicts

- A step verifier gets the step's output in the file named by `STEP_OUTPUT_PATH` and the
  step number in `STEP_NUMBER`, and prints a float or protocol JSON. `session.step_verdict`
  holds its last output and exit code.
- A step that fails after its action may have run ends the episode: `step()` raises, the
  action stays on the trajectory with no step reward (so `step_rewards` is one shorter
  than `steps`), and `score()` returns the result that failure fixed instead of running
  the final verifier. Only a failure that is ours for certain has no verdict
  (`deepgym.session.failure_result`): a provider, network or auth failure, a step
  verifier that couldn't run or failed (bad output, exit 2, an `error_type` that isn't the
  model's fault, or a timeout before it read the agent's output), or the provider retiring
  the sandbox at the end of its lifetime, unless the agent's own actions took at least half
  the session's time. A failed step verifier reports `verifier_error`, or `invalid_score`
  for a NaN or out-of-range score. Anything else follows from the agent's actions (its
  sandbox died, a write failed, even with a disk or I/O error, since the agent can fill
  the disk or plant a link, its files blocked the step's output) and scores 0 with
  `error_type='policy_sandbox_failure'`.
- A step or final verifier that times out after reading the agent's output (it opened a
  file, listed a folder or started a program in the work directory or on a file there, or
  started the agent's code) is a judged 0 with `error_type='solution_timeout'`; for a step
  verifier that is the step's reward, and the episode goes on. One that is killed, or
  gives no verdict, after reading it is a judged 0 with `solution_error` (unless the
  agent's code failed to launch), and so is a timeout whose output was lost.
- A rejected action (not allowed, or the agent's own files in the way of a `write_file`)
  raises `ActionError` and the episode goes on.
- `run_episode` returns the result a failed step fixed instead of raising.
- A sandbox that dies under the final verifier scores 0 (`policy_sandbox_failure`) once the
  agent has acted.
- An environment with no `final_verifier_code` has no verdict: `score()` returns
  `error_type='no_verifier'`, so trainers mask the episode.

See [the reward contract](/docs/training/reward-contract).

## Sessions over HTTP

`deepgym serve` exposes sessions to trainers in any language:

```
POST   /v1/sessions                {"environment": "todo_list", "variant": 3}  -> session_id, observation
POST   /v1/sessions/{id}/step      {"action_type": "type", "content": "Pay rent"}
POST   /v1/sessions/{id}/score     -> RunResult
DELETE /v1/sessions/{id}
```

- `environment` can also be an inline `MultiTurnEnvironment` spec; the API refuses one
  that sets an `image` or a `snapshot`.
- At most 100 sessions run at once (429 above). Sessions idle for 30 minutes are closed
  by a sweep that runs every 60 seconds, and the server tries to close every session on
  shutdown.
- If deleting a session's sandbox fails, `DELETE` answers 502 and the session keeps its
  id and slot, so the delete can be retried; the sweep retries it too, up to 10 attempts.
  After that its sandbox id is logged as an error, the slot is freed, and `/health`
  counts it in `leaked_sandboxes`.

Server setup and the other endpoints are in the [API server](/docs/api-server) docs.

