> ## Documentation Index
> Fetch the complete documentation index at: https://hud-f5fd7c15-mintlify-83a8014e.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Runtimes: LocalRuntime, SubprocessRuntime, HUDRuntime

> Reference for HUD runtimes that place each rollout's environment: LocalRuntime in-process, SubprocessRuntime in a child, and HUDRuntime on the platform.

A **runtime** chooses where each rollout's environment runs. You pass it to `task.run` /
`taskset.run` at execution time, and the same task and the same `env.py` run anywhere - only the
runtime changes.

```python theme={null}
from hud import LocalRuntime

await taskset.run(agent, runtime=LocalRuntime("env.py"))   # serve env.py locally, run here
```

## Built-in runtimes

| Runtime                       | Where the env runs                                             | When to reach for it                                   |
| ----------------------------- | -------------------------------------------------------------- | ------------------------------------------------------ |
| `LocalRuntime("env.py")`      | This process, loaded fresh from your source per rollout        | Fastest iteration; local development                   |
| `SubprocessRuntime("env.py")` | A child process from your source                               | Local runs isolated from your process                  |
| `DockerRuntime("my-env")`     | A fresh local container per rollout                            | Reproducibility and parity with production             |
| `ModalRuntime("my-env")`      | A fresh [Modal](https://modal.com/) sandbox per rollout        | Cloud scale, no infra to manage                        |
| `DaytonaRuntime("my-env")`    | A fresh [Daytona](https://www.daytona.io/) sandbox per rollout | Cloud scale on Daytona                                 |
| `Runtime("tcp://host:8765")`  | A substrate you already started                                | Attaching to a long-lived container or sandbox you own |
| `HUDRuntime()`                | A HUD-hosted env, leased by name and tunneled                  | Local agent loop against a deployed env                |
| `HostedRuntime()`             | The whole rollout on a HUD-leased box                          | Agent and env run together off your machine            |

Most runtimes are on the top-level package (`from hud import LocalRuntime, DockerRuntime, HUDRuntime,
HostedRuntime, Runtime`); `ModalRuntime` and `DaytonaRuntime` import from `hud.eval`.

<Note>
  **You can usually omit `runtime=`.** A run without one uses what HUD already knows:

  * a taskset loaded from Python source (`Taskset.from_file` / `from_module`) runs against that source
  * a platform taskset (`Taskset.from_api`) runs on the platform
  * otherwise, if the envs your tasks name are defined in files you've imported, each rollout gets a
    fresh env served from its file — so `my_task().run(agent)` just works in the project that defines
    the env

  When none of these apply, `run` raises and lists the runtimes you can pass — it never silently picks
  one. If two imported files define an env with the same name, that's also an error; disambiguate by
  passing the instance you mean: `runtime=LocalRuntime(env)`.
</Note>

To deploy an environment to the platform and run against it, see
[running an eval](/v6/guides/running-an-eval) and
[deploying to the platform](/v6/guides/creating-an-environment#deploying-to-the-platform).

## RuntimeConfig

`RuntimeConfig` carries the construction hints a container-based runtime needs: which image, how much
hardware, and what timeouts. Set it on the runtime (`runtime_config=`) or per row on
[`Task.runtime_config`](/v6/reference/tasks#task); the runtime merges the two and applies what it
supports.

```python theme={null}
from hud.eval import RuntimeConfig, RuntimeResources, RuntimeGPU, RuntimeLimits

RuntimeConfig(
    image="my-env",
    resources=RuntimeResources(cpu=4, memory_mb=8192, gpu=RuntimeGPU(type="A100", count=1)),
    limits=RuntimeLimits(startup_timeout_s=300, run_timeout_s=1800),
)
```

| Field       | Description                                                      |
| ----------- | ---------------------------------------------------------------- |
| `image`     | Image to run.                                                    |
| `resources` | `RuntimeResources(cpu, memory_mb, gpu=RuntimeGPU(type, count))`. |
| `limits`    | `RuntimeLimits(startup_timeout_s, run_timeout_s)`.               |

Support differs per runtime: `DockerRuntime`, `ModalRuntime`, and `DaytonaRuntime` accept it (Docker
ignores `limits`; Daytona ignores `run_timeout_s` and resource overrides when booting from a snapshot).
`LocalRuntime` and `HUDRuntime` reject a per-task `runtime_config`.

## Runtime directory

The constructor for each built-in runtime:

### `LocalRuntime`

```python theme={null}
LocalRuntime(source, *, env=None, ready_timeout=120.0)
```

Serves a fresh env per rollout, in this process, over the same control channel as every placement.
`source` is any pointer to the env:

* **a `.py` file or directory** that declares it, imported fresh per rollout (sibling imports
  resolve). **`env`** pins one name when the source declares several; it defaults to the placed
  task's env.
* **a live `Environment`** declared at module level - its declaring module's file is the recipe;
  the instance itself is never served, so every rollout is still fresh.
* **a `(task) -> Environment` constructor** for envs built in code (integrations, parameterized
  envs), called fresh per rollout with the placed row.

`ready_timeout` bounds `@env.initialize` startup. The freshness boundary is the env's own source:
it is re-imported per rollout, while modules it imports follow normal Python import caching and are
shared process-wide - state kept in helper modules persists across rollouts. Env hooks run in this
process and share its event loop - keep envs async, or use `SubprocessRuntime` / `DockerRuntime`
when rollouts need whole-process isolation.

### `SubprocessRuntime`

```python theme={null}
SubprocessRuntime(path, *, env=None, ready_timeout=120.0)
```

* **`path`** - `.py` file (or directory) that declares the env. The child's working directory is the source's directory, so sibling imports and relative data paths resolve.
* **`env`** - pin a specific env name when the source declares more than one. Defaults to the placed task's env.
* **`ready_timeout`** - seconds to wait for the child to start serving.

### `DockerRuntime`

```python theme={null}
DockerRuntime(image=None, *, port=8765, run_args=(), runtime_config=None)
```

* **`image`** - image name to run; shorthand for `runtime_config.image`.
* **`port`** - port the image's CMD serves inside the container (the scaffolded `Dockerfile.hud` serves `8765`).
* **`run_args`** - extra `docker run` flags, e.g. `["--gpus", "all"]` or `["-e", "KEY=VAL"]`.
* **`runtime_config`** - a `RuntimeConfig` (image, resources) for finer control.

### `ModalRuntime`

```python theme={null}
ModalRuntime(image_name=None, *, image=None, command=None, app_name="hud-envs", workdir=None, port=8765, runtime_config=None, env_vars=None)
```

* **`image_name`** - published Modal image name (the preferred durable handle), e.g. `ModalRuntime("hud-libero-env")`.
* **`image`** - an `Image` to build lazily on first use, as an escape hatch.
* **`command`** - override the serving command (defaults to the scaffolded `hud serve` entrypoint).
* **`workdir`** - working directory inside the sandbox. Left unset, Modal keeps the image's `WORKDIR`.
* **`app_name`** / **`port`** / **`env_vars`** - Modal app name, in-sandbox serving port, and extra environment variables.

Requires the `modal` extra and a configured token.

### `DaytonaRuntime`

```python theme={null}
DaytonaRuntime(snapshot_name=None, *, image=None, command=None, workdir="/app", port=8765, ssh_host="ssh.app.daytona.io", ssh_expires_minutes=1440, runtime_config=None)
```

* **`snapshot_name`** - Daytona snapshot to boot from (the durable handle).
* **`image`** - Dockerfile/registry ref to build the snapshot once if it's missing. Resources (cpu/memory/gpu) live on the snapshot.
* **`workdir`** / **`port`** - guest working directory and in-sandbox serving port.
* **`ssh_host`** / **`ssh_expires_minutes`** - SSH tunnel settings (Daytona exposes services over an SSH local-forward).

### `HUDRuntime`

```python theme={null}
HUDRuntime(*, run_timeout=3600.0, runtime_url=None)
```

* **`run_timeout`** - bound on one rollout end to end, including instance startup.
* **`runtime_url`** - override the runtime endpoint the tunnel connects to.

The SDK leases your deployed env by name and tunnels to its control channel; the agent loop runs local.

### `HostedRuntime`

```python theme={null}
HostedRuntime(*, poll_interval=5.0, run_timeout=3600.0)
```

* **`poll_interval`** - seconds between trace-status polls while the rollout runs remotely.
* **`run_timeout`** - bound on one rollout end to end, including instance provisioning and queueing.

Where `HUDRuntime` runs the agent loop locally against a tunneled env, `HostedRuntime` runs the
**whole rollout** off-box: the platform leases an instance, brings the env's container up on it, and
runs the agent right next to it. This process only submits the rollout and polls its trace to
completion. It requires a gateway agent that can serialize its identity (Claude/OpenAI/Gemini).

### `Runtime`

```python theme={null}
Runtime(url, params=..., config=...)
```

* **`url`** - control-channel address of an already-running substrate (e.g. `tcp://host:8765`).
* **`params`** - connection-time data a transport may need (auth token, sandbox id).

## Run on your own infra

A **runtime is just a function**: given a task, start a container somewhere and yield its
control-channel URL. That one function is the whole integration surface for any provider - Modal, E2B,
Runloop, your own Kubernetes:

```python run.py theme={null}
from contextlib import asynccontextmanager
from hud import Runtime

@asynccontextmanager
async def my_runtime(task):
    sandbox = await start_my_sandbox(image="my-env")   # your infra brings it up
    try:
        yield Runtime(f"tcp://{sandbox.host}:{sandbox.port}")
    finally:
        await sandbox.terminate()                       # ...and tears it down

await taskset.run(agent, runtime=my_runtime)
```

`DockerRuntime` and the rest are just built-in versions of this. Anything that starts your image and
hands back a URL plugs in with no change to the environment or the task - that's what "run anywhere"
means concretely. Constructed directly, `Runtime(url)` yields itself with a no-op lifecycle, since
whoever provisioned the substrate owns teardown.

Placement can also vary per task: a runtime is called once per rollout with the task row being placed,
so one callable can route heavier rows to heavier substrates.
