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

> ## Agent Instructions
> To build and serve your own model, start at /deploy/development/quickstart and /deploy/development/overview. Deploying is the default path: reactor init scaffolds a workspace, reactor auth login authenticates, and reactor model deploy registers the model, publishes the release with the weights/ folder, and activates it on Reactor's GPUs, in one command from that workspace. Docker must be running, because the publish step builds the image locally. Bump model.version in reactor.yaml before redeploying a change, because a release that already has an image is reactivated as it is. Deployment access is granted per account, so contact team@reactor.inc if a deploy is refused. Every key in reactor.yaml is documented at /deploy/platform/reactor-yaml. Model code imports reactor_runtime; Python client code imports reactor_sdk. The runtime overview explains the model interface. Running the model on your own machine with reactor run is optional and needs a GPU you attach with --gpus; /deploy/development/local-testing covers that loop and pairs a complete brightness model with a Python client test in a separate brightness-test workspace.
> Reactor hosts multiple models, each with its own connect slug (modelName) and command/event schema. The catalog of every model — slug, typed SDK package, and links to its schema — is at /model-api-reference/overview. Some models expose one slug per experience (e.g. HappyOyster); always take the slug from the model's own pages, never guess it.
> Fastest path to a working app: `npx create-reactor-app my-app --model=<slug>` scaffolds a complete app with secure auth wired up. Typed TypeScript SDKs are published as @reactor-models/<model>; Python uses the base reactor-sdk package.
> Auth: exchange an API key (rk_...) for a JWT via POST https://api.reactor.inc/tokens from your server. Never put the API key in client-side code.
> Append .md to any docs URL for clean Markdown. Search these docs via the MCP server at https://docs.reactor.inc/mcp.

# reactor.yaml reference

> Every key in reactor.yaml, with its type, its default, and the commands that read it.

`reactor.yaml` is the model spec. One file declares what the model is called, how the runtime loads
it, how its image is built, and where it runs. `reactor init` writes it, and every other `reactor`
command reads it.

The file sits at the root of your workspace. A **workspace** is the directory tree rooted at a
`reactor.yaml`, so you can run commands from a subdirectory. The CLI searches upward for the file
and stops at the first directory that holds a `.git` entry, so the search never leaves your
repository.

The name is exactly `reactor.yaml`. There is no `.yml` spelling. `reactor model register` and
`reactor model update` take `--model-file <path>`, which points at a different path rather than a
different name, and reads a `.json` file as JSON.

Check a file without contacting the server:

```sh theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
reactor validate
```

## `$schema`

```yaml reactor.yaml theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
$schema: reactor/v2
```

`$schema` declares which generation of the format you wrote. It is a marker, not a URL. Nothing is
published at that address, so do not open it and do not point an editor at it for completion.

| Value         | Behavior                                                         |
| ------------- | ---------------------------------------------------------------- |
| `reactor/v2`  | The current format. `reactor init` writes this.                  |
| `reactor/v1`  | An older generation. It loads, and it warns.                     |
| Absent        | Read as the first generation and upgraded as it loads. It warns. |
| Anything else | A hard error. The CLI refuses the file.                          |

Write `$schema: reactor/v2` at the top of every new file. [Older formats](#older-formats) covers
what happens to a file that predates it.

## `model:`

What the model is called on the platform, and the hardware it asks for.

| Key            | Type            | Required                    | Default                      | Controls                                                                                                                                                                                                                         |
| -------------- | --------------- | --------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`         | string          | Yes, for publish and deploy | none                         | The model slug. Either a short per-account name or a canonical `org/name`. Lowercase letters and digits in runs separated by `.`, `_`, or `-`, up to 64 characters.                                                              |
| `version`      | string          | Yes, for publish and deploy | none                         | The release tag. Semver, with an optional leading `v`. A `+` is rejected, because OCI tags do not allow it: use `-` for build metadata. A published release is immutable, so bump this whenever the image or the weights change. |
| `description`  | string          | No                          | empty                        | A short description stored with the model.                                                                                                                                                                                       |
| `public`       | boolean         | No                          | absent, which is not `false` | Whether every Reactor account can discover the model. See [Gotchas](#gotchas).                                                                                                                                                   |
| `display-name` | string          | No                          | none                         | Nothing. The key parses and is then ignored with a warning. See [Gotchas](#gotchas).                                                                                                                                             |
| `resources`    | mapping         | No                          | none                         | The GPU, CPU, and memory request. See below.                                                                                                                                                                                     |
| `gpu`          | mapping         | No                          | none                         | An older sibling of `resources.gpu`. Still read. `resources.gpu` wins when both are set.                                                                                                                                         |
| `extra-args`   | list of strings | No                          | empty                        | Arguments appended to the runtime container's command line, both locally and when deployed.                                                                                                                                      |

### `model.resources:`

| Key      | Type    | Required | Default                       | Controls                      |
| -------- | ------- | -------- | ----------------------------- | ----------------------------- |
| `gpu`    | mapping | No       | none, which means a CPU model | The accelerator request.      |
| `cpu`    | mapping | No       | set by the Reactor API        | The CPU request and limit.    |
| `memory` | mapping | No       | set by the Reactor API        | The memory request and limit. |

`cpu` and `memory` each take a `request` and a `limit`, both Kubernetes quantity strings such as
`500m` or `4Gi`. The limit must be greater than or equal to the request. `reactor.yaml` is the only
place to set them, because there are no equivalent CLI flags. Leave either key out and the Reactor
API fills in a value. See [Gotchas](#gotchas).

### `model.resources.gpu:`

| Key     | Type    | Required | Default             | Controls                                                 |
| ------- | ------- | -------- | ------------------- | -------------------------------------------------------- |
| `type`  | string  | No       | none                | The accelerator type, written as one of the codes below. |
| `count` | integer | No       | `1` for a GPU model | How many accelerators each instance gets.                |

The scaffold requests one `NVIDIA_B200`.

### Accelerator type codes

These are the codes `model.resources.gpu.type` accepts. The older `model.gpu.type` takes the same
set. Write a code exactly as it appears here, because the Reactor API rejects any other string.

| Code                 | Family         |
| -------------------- | -------------- |
| `NVIDIA_A100`        | NVIDIA GPU     |
| `NVIDIA_H100`        | NVIDIA GPU     |
| `NVIDIA_H200`        | NVIDIA GPU     |
| `NVIDIA_B200`        | NVIDIA GPU     |
| `NVIDIA_GB200`       | NVIDIA GPU     |
| `NVIDIA_B300`        | NVIDIA GPU     |
| `NVIDIA_RTX6000`     | NVIDIA GPU     |
| `NVIDIA_RTX6000_PRO` | NVIDIA GPU     |
| `AWS_TRAINIUM_2`     | AWS Trainium   |
| `AWS_TRAINIUM_3`     | AWS Trainium   |
| `AWS_INF_2`          | AWS Inferentia |
| `AMD_MI300X`         | AMD GPU        |
| `AMD_MI350X`         | AMD GPU        |
| `AMD_MI400`          | AMD GPU        |

The family is the only part of a code the CLI reads, and it comes from the prefix. A code that
starts with `AWS_TRAINIUM_` or `AWS_INF_` is AWS Neuron hardware. Every other code is a GPU.

This table copies a list the platform owns, so a platform release can add to it. Print the set your
platform accepts today:

```sh theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
reactor gpu ls --refresh
```

That command lists the accepted codes. It does not report free capacity. An accepted code is not a
promise of hardware either, because the Reactor API checks region membership and per-region quota
when you deploy.

A code places the model at deploy time. `reactor model register`, `reactor model update`, and
`reactor model deploy` send it to the Reactor API, which schedules the model on that hardware.

<Warning>
  A code never gets you hardware locally. `reactor run` attaches no accelerator unless you pass
  `--gpus` yourself, and it never checks the code against your host. A workspace that declares
  `NVIDIA_B200` runs on the CPU, with no warning.
</Warning>

A Neuron code is the one code that changes a local run, and it takes a flag away rather than adding
one. Docker's `--gpus` does not reach Neuron devices, so an `AWS_TRAINIUM_` or `AWS_INF_` code makes
`reactor run` drop that flag and print a note. It drops the flag even when you passed `--gpus all`.
Expose the device files your host has instead:

```sh theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
reactor run --device /dev/neuron0
```

## `runtime:`

How the runtime inside the container finds and loads your model code.

| Key            | Type    | Required                     | Default                                       | Controls                                                                                                                                                |
| -------------- | ------- | ---------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `import`       | string  | Yes, enforced by the runtime | none                                          | The entry point, written `module:ClassName`. Without it the runtime stops and reports that `runtime.import` is required.                                |
| `config`       | string  | No                           | none, and the model receives an empty mapping | A path to the model's own settings file. See [How it relates to the other files](#how-it-relates-to-the-other-files).                                   |
| `weights_path` | string  | No                           | see below                                     | The weights root. Either a directory or a public `hf://org/repo[@rev]` reference.                                                                       |
| `recording`    | mapping | No                           | recording off                                 | The recorder's settings. `enabled: true` is the whole opt-in; [Recording](/deploy/development/recording#configuration-reference) documents every field. |

The Go parser does not require `import`, so a file without it passes `reactor validate` and then
fails when the container starts. Treat it as required.

`weights_path` is read by three callers, each with its own precedence:

* `reactor run` takes the bind mount from `--weights`, then `$REACTOR_WEIGHTS_PATH`, then
  `runtime.weights_path`. With none of the three set, nothing is mounted.
* `reactor model publish` and `reactor weights upload` take the upload source from `--weights`, then
  `runtime.weights_path`, then `./weights`.
* Inside the container, the runtime reads `$REACTOR_WEIGHTS_PATH`, then `runtime.weights_path`, then
  falls back to its own cache directory.

A relative path resolves against the workspace root. See [Weights](/deploy/development/weights).

## `build:`

The image. `reactor build` renders a Dockerfile from this block in memory, so a scaffolded workspace
ships no Dockerfile to maintain. [Build the image](/deploy/platform/build) walks through the
workflow and the build secrets. The table below is the full field list.

| Key                   | Type                    | Required                   | Default                           | Controls                                                                                                                      |
| --------------------- | ----------------------- | -------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `runtime_version`     | string                  | Yes, for a generated build | none in the parser                | The `reactor-runtime` release the image installs. Major version 3 or later. See [Gotchas](#gotchas).                          |
| `platform`            | string                  | No                         | `linux/amd64`                     | The target build platform. `--platform` overrides it. Publishing needs `linux/amd64`.                                         |
| `base_image`          | string                  | No                         | none, derived from `cuda_version` | An explicit `FROM` ref, used verbatim. See [Gotchas](#gotchas).                                                               |
| `cuda_version`        | string                  | No                         | none, which builds a CPU image    | The CUDA toolkit version, written `12.9.1` or `12.9`. It picks the CUDA base image and the matching PyTorch wheel index.      |
| `python_version`      | string                  | No                         | `3.12`                            | The interpreter version the image installs.                                                                                   |
| `system_packages`     | list of strings         | No                         | empty                             | Debian package names, installed in one layer. Names only: no versions and no apt flags.                                       |
| `python_requirements` | string                  | No                         | `requirements.txt`                | The dependency file to install from, either a requirements file or a `pyproject.toml`. It cannot point outside the workspace. |
| `python_indexes`      | list of strings         | No                         | empty                             | Extra package index URLs, highest priority first. Each must be `http` or `https`, and must not embed credentials.             |
| `build_env`           | map of string to string | No                         | empty                             | Environment variables for the build only. They are absent from the finished image.                                            |
| `runtime_env`         | map of string to string | No                         | empty                             | Environment variables baked into the image, which the model process sees.                                                     |
| `run`                 | list                    | No                         | empty                             | Build commands, in order, after the dependency install and before the model code is copied.                                   |

Both environment blocks reject six names the generated image sets for itself: `HOST`,
`LD_LIBRARY_PATH`, `PATH`, `PORT`, `PYTHONUNBUFFERED`, and `VIRTUAL_ENV`.

### `build.run[]`

Each step is either a plain string, which is the command, or a mapping:

| Key       | Type   | Required | Default | Controls                                     |
| --------- | ------ | -------- | ------- | -------------------------------------------- |
| `command` | string | Yes      | none    | The command to run. One line, and not empty. |
| `mounts`  | list   | No       | empty   | Build secrets mounted for this step only.    |

A mount takes three keys. `type` must be `secret`. `id` is required, and it matches the id you pass
to `reactor build --build-secret`. `target` is the optional mount path.

## `deployment:`

Where the model runs.

| Key         | Type | Required | Default            | Controls                                                |
| ----------- | ---- | -------- | ------------------ | ------------------------------------------------------- |
| `instances` | list | No       | none in the parser | How many model instances to run in each Reactor Region. |

Each entry takes two keys, and both are required:

| Key      | Type    | Required | Default | Controls                                                  |
| -------- | ------- | -------- | ------- | --------------------------------------------------------- |
| `region` | string  | Yes      | none    | A canonical Reactor Region. `reactor regions` lists them. |
| `count`  | integer | Yes      | none    | The number of instances. Zero or more.                    |

`count: 0` on a named region is a deliberate scale to zero, and it is applied. An empty `instances:`
list is different: it declares no plan, so the deploy warns and leaves per-region capacity
unchanged. The scaffold requests one instance in `us-west`. The Reactor API checks quota and
availability, and a deploy either applies the whole plan or changes nothing.

## Key casing

The casing is not uniform across blocks, and the CLI accepts only one spelling. Copy the exact key:

* `model:` uses hyphens for its multi-word keys: `display-name`, `extra-args`.
* `runtime:`, `build:`, and `deployment:` use underscores: `weights_path`, `runtime_version`,
  `base_image`, `cuda_version`, `python_version`, `system_packages`, `python_requirements`,
  `python_indexes`, `build_env`, `runtime_env`.

`build.runtime_version` and `model.display-name` are the pair that catches people. Both are
multi-word keys in the same file, spelled two different ways.

<Warning>
  The parser drops keys it does not recognize instead of rejecting them. A misspelled key is ignored
  in silence, so a typo reads as "the field had no effect" rather than as an error. Run
  `reactor validate` and compare against the tables above.
</Warning>

## Which commands read which blocks

| Command                                                                  | Reads                                                                                                        |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| [`reactor validate`](/deploy/cli-reference/reactor_validate)             | Every block, offline.                                                                                        |
| [`reactor build`](/deploy/cli-reference/reactor_build)                   | `build:`                                                                                                     |
| [`reactor run`](/deploy/cli-reference/reactor_run)                       | `build:` for the automatic build, `model.extra-args`, `model.resources.gpu.type`, and `runtime.weights_path` |
| [`reactor model register`](/deploy/cli-reference/reactor_model_register) | `model:`                                                                                                     |
| [`reactor model update`](/deploy/cli-reference/reactor_model_update)     | `model:`                                                                                                     |
| [`reactor model publish`](/deploy/cli-reference/reactor_model_publish)   | `model.name`, `model.version`, `build:`, and `runtime.weights_path`                                          |
| [`reactor model deploy`](/deploy/cli-reference/reactor_model_deploy)     | `model.name`, `model.version`, `deployment:`, and everything publish reads                                   |
| [`reactor weights upload`](/deploy/cli-reference/reactor_weights_upload) | `model.name`, `model.version`, and `runtime.weights_path`                                                    |
| The runtime, in the container                                            | `runtime.import`, `runtime.config`, `runtime.weights_path`, `runtime.recording`, and `model.name`            |

`model.description`, `model.public`, `model.resources.cpu`, `model.resources.memory`, and
`model.resources.gpu.count` reach the Reactor API through `register` and `update`. They have no
effect on a local run.

`reactor run` is the one command that does not search upward. It reads the `reactor.yaml` in the
current directory only, so run it from the workspace root.

## How it relates to the other files

`reactor init` scaffolds four files that work together, plus a `weights` directory:

| File               | Purpose                                                 |
| ------------------ | ------------------------------------------------------- |
| `reactor.yaml`     | The model spec. Everything on this page.                |
| `model.py`         | The model code. `runtime.import` names the class in it. |
| `config.yaml`      | The model's own settings. `runtime.config` names it.    |
| `requirements.txt` | The dependencies. `build.python_requirements` names it. |
| `weights/`         | The weights. `runtime.weights_path` names it.           |

### `config.yaml`

`config.yaml` holds nothing Reactor reads. The runtime loads the file that `runtime.config` names
and hands the parsed mapping to the model's `load()` method. The contents and the format are yours.
A relative path resolves against the workspace root. With no `runtime.config`, the model receives an
empty mapping.

`reactor run -- --model.<key>=<value>` merges overrides on top of the loaded file. Those overrides
need `runtime.config` to be set, and fail with an error when it is not.

### `deployment.yaml`

`deployment.yaml` is a standalone deployment plan, and it is **not deprecated**. One thing changed:
`reactor init` no longer writes it. A fresh workspace's plan is the `deployment:` block inside
`reactor.yaml`, and `reactor deploy init` still writes a `deployment.yaml` for the CI path.

When both exist, `deployment.yaml` **wins**:

| State                     | What `reactor model deploy` applies                                                                                            |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `deployment.yaml` present | The plan in `deployment.yaml`. The `deployment:` block in `reactor.yaml` is ignored, and the deploy warns that it shadowed it. |
| No `deployment.yaml`      | The `deployment:` block in `reactor.yaml`.                                                                                     |
| Neither                   | No plan. The deploy warns and leaves per-region capacity unchanged.                                                            |

The scaffolded `deployment.yaml` ships an empty `instances:` list, which declares no plan. Add the
file to a workspace that already carries a plan, and that plan stops being applied. Fill in the list
to restore it.

## A minimal file

The smallest file that both parsers accept, and that supports `reactor build` and `reactor run`:

```yaml reactor.yaml theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
$schema: reactor/v2

model:
  name: my-model

runtime:
  import: model:MyModel

build:
  runtime_version: "<runtime-version>"
```

Replace `<runtime-version>` with the release `reactor init` pinned in your workspace, or pick one
from the [runtime changelog](/deploy/development/changelog).

`reactor model publish` and `reactor model deploy` also need `model.version`. With no `deployment:`
block, `reactor model deploy` warns and provisions nothing.

## A fully populated file

Every key the format models, at its correct nesting.

```yaml reactor.yaml theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
$schema: reactor/v2

model:
  name: acme/vision-transformer
  version: v1.4.2
  description: A vision transformer for partner integration.
  public: false
  resources:
    gpu:
      type: NVIDIA_H100
      count: 2
    cpu:
      request: "2000m"
      limit: "8000m"
    memory:
      request: 16Gi
      limit: 64Gi
  extra-args: ["--batch-size", "4"]

runtime:
  import: model:VisionTransformer
  config: config.yaml
  weights_path: ./weights
  recording:
    enabled: true

build:
  runtime_version: "<runtime-version>"
  platform: linux/amd64
  cuda_version: "12.9.1"
  python_version: "3.12"
  system_packages: [ffmpeg, git, libgl1]
  python_requirements: requirements.txt
  python_indexes:
    - https://pypi.acme.example/simple
  build_env:
    MAX_JOBS: "32"
  runtime_env:
    MODEL_PRECISION: bf16
  run:
    - python -m compileall -q .
    - command: pip install --no-deps ./vendor/acme_kernels
      mounts:
        - type: secret
          id: acme_pypi_token
          target: /run/secrets/acme_pypi_token

deployment:
  instances:
    - region: us-west
      count: 2
    - region: us-east
      count: 1
```

`base_image` is left out on purpose. Setting it replaces the base that `cuda_version` picks, which
makes `cuda_version` cosmetic.

## Gotchas

### A `Dockerfile` on disk switches most of `build:` off

When the workspace has a `Dockerfile`, `reactor build` builds that file verbatim. Nine fields then
stop applying: `base_image`, `cuda_version`, `python_version`, `system_packages`,
`python_requirements`, `python_indexes`, `build_env`, `runtime_env`, and `run`. The build prints a
note naming the fields it dropped, so read it. `platform` and `runtime_version` still apply. Use
`reactor build --no-dockerfile` to build from `reactor.yaml` instead.

### `base_image` is not checked, but it is still constrained

The CLI uses the ref verbatim and validates nothing. But the generated image installs system
packages with `apt-get`. A base that is not Debian- or Ubuntu-derived fails the build instead of
the spec check.

### `model.display-name` does nothing

The key parses, and then `register` and `update` both print that it is ignored and safe to remove.
Set `model.description` instead.

### An absent `model.public` is not `public: false`

The key has three states. On `register`, leaving it out registers the model private. On
`reactor model update`, leaving it out keeps whatever the platform already holds, while an explicit
`true` or `false` overwrites it. Write the value you mean.

### `build.runtime_version` has no default

The scaffold pins a version, which makes the field look defaulted, but the parser has no fallback. A
generated build with the key missing fails with an error naming it. Every workspace must carry it.

### The CPU and memory defaults come from the Reactor API

They are not in this file. Leave `cpu` or `memory` out and the Reactor API fills in the request and
the limit at deploy time. Those values can change, so treat them as a platform behavior rather than
as a schema default. Set the keys yourself when a model needs a specific size.

## Older formats

The format is versioned, and older files still load. Three shapes qualify: `$schema: reactor/v1`,
the older `apiVersion` and `kind` header, and no header at all. Each is upgraded as it loads, and
each earns a deprecation warning. The first generation also accepted a flat shape. There `model` was
a string rather than a mapping, beside the scalar keys `name`, `config`, and `weights_path`.

Two keys moved when the second generation landed. A deployment plan's `instances:` now sits under
`deployment:`, and the recorder's `recording:` block sits under `runtime:`. Under
`$schema: reactor/v2` either key at the top level is a hard error rather than a silent drop, which
is what catches a half-finished migration. Move the block down one level, or drop `$schema` and let
the upgrade handle it.

Write new files as `reactor/v2`.
