> ## 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.

# Model Anatomy

> How a model is structured: one class, the inputs it accepts, the outputs it sends, and the method that generates.

A model is one Python class that extends `ReactorApp`. It declares what a client can change and
what a client receives, loads its weights once, and produces the next output whenever the runtime
asks. This page takes a working model apart so you can see where each of those lives and build
your own the same way. The example is the starter from
[Test locally](/deploy/development/local-testing), trimmed to the parts that matter.

<Note>
  Porting a model written against an earlier runtime? `ReactorModel` is the previous name for this
  class. It still imports, with a deprecation warning, and everything on this page applies to it.
</Note>

## The ReactorApp class

```python starter.py theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
class StarterState(InputState):
    spin_speed: float = InputField(
        default=0.2, ge=0.05, le=5.0, description="Spin speed in turns per second."
    )
    paused: bool = InputField(
        default=False, description="Hold the logo at its current angle."
    )


class StarterOutput(Output):
    main_video: Video


class Starter(ReactorApp):
    state: StarterState
    fps = 30

    def load(self, config_path: Path | None) -> None:
        self.background = render_gradient(800, 500)
        self.logo = load_sprite(get_weights_path() / "logo.png", box=(400, 350))
        self.reset()

    def generate(self, input: StarterState) -> StarterOutput:
        frame = self.background.copy()
        draw_logo(frame, self.logo, math.cos(self.angle))
        if not input.paused:
            self.angle += 2 * math.pi * input.spin_speed / self.fps
        return StarterOutput(main_video=frame)

    @session_ended
    def on_session_ended(self) -> None:
        self.reset()
```

The file defines three classes.

**`StarterState` is what the client controls.** Think of it as a set of knobs. When a client turns
one, whether that is a web app, a script, or a robot, the value changes inside your model.

**`StarterOutput` is what the model sends back.** It lists the tracks your model sends to clients.
Here there is one, a video track called `main_video`.

**`Starter` is the model.** It has two methods the runtime calls for you:

<Steps>
  <Step title="`load()` runs once, when the container starts" icon="download">
    Load your weights here.
  </Step>

  <Step title="`generate()` runs once per step, while a client is connected" icon="repeat">
    It receives the current input values and returns a `StarterOutput`. Here each call produces one
    video frame.
  </Step>
</Steps>

That is all it takes to define a model that reads client inputs and generates output in real time.
`fps` sets the frame rate, and `@session_ended` resets the model when a session ends. [Sessions & Clients](/deploy/development/reactor-app/sessions-and-clients) covers hooks like
that one.

## Processing inputs and outputs

Client inputs often need converting into the form the model expects. In the example below, the
state holds an action name and two mouse coordinates, and the engine wants them as one
`WorldInput`. In the other direction, the model returns a raw array, not an `Output`. Two optional
hooks around `generate()` cover this: `process_input()` prepares the input before generation, and
`process_output()` turns the result into what the client receives.

<Frame caption="One frame, clockwise: prepare the input, generate, shape the output, then back through the application, where client input has landed in the state.">
  <img src="https://mintcdn.com/reactortechnologiesinc/dCQX8iFwCidKaSoT/diagrams/step-hooks.svg?fit=max&auto=format&n=dCQX8iFwCidKaSoT&q=85&s=039bc8a86e70ba1c330c86457b8f0db6" alt="A diamond-shaped loop: process_input at the top, generate on the right, process_output at the bottom, and the application on the left, with arrows running clockwise. A second arrow leaves process_output downward, labeled output to clients." width="670" height="360" data-path="diagrams/step-hooks.svg" />
</Frame>

**`process_input()`** runs before `generate()`. It reads the state and decides what the model
gets. It can also decide that this is not the moment to call the model at all, for example while
the client has paused, or before a required upload has arrived. Raising `ApplicationError` skips
the frame, and the runtime asks again a moment later.

**`process_output()`** runs after `generate()`. It receives what `generate()` returned and turns it
into the `Output` to send, which is also where a status message to the client would be sent.

```python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
@dataclass
class WorldInput:
    action: str
    mouse: tuple[float, float]


class World(ReactorApp):
    state: WorldState

    async def process_input(self) -> WorldInput:
        if self.state.paused:
            raise ApplicationError("paused")
        return WorldInput(
            action=self.state.action,
            mouse=(self.state.mouse_x, self.state.mouse_y),
        )

    def generate(self, input: WorldInput) -> np.ndarray:
        return self.engine.step(input.action, input.mouse)

    async def process_output(self, outcome: StepOutcome) -> WorldOutput | None:
        if outcome.error is not None:
            raise outcome.error
        return WorldOutput(main_video=outcome.result)
```

Both hooks have defaults. Without them, `generate()` receives the whole state and its return
value must already be an `Output`, which is what the starter relies on. With them, `generate()`
receives exactly the input you built, and it can stay a thin call into whatever runs your weights.

This separation is optional and not enforced. It pays off as the model grows past a few lines:
input preparation, generation, and output handling each live in one place, so each is easier to
read and change. [The Step Loop](/deploy/development/reactor-app/step-loop)
covers the three calls in detail, and [Application and Model](/deploy/development/reactor-app/application-and-model)
covers how far to take the separation.

## The manifest

`reactor.yaml` sits in the folder beside your code and tells the runtime which class to load.
Three of its fields describe the model:

```yaml reactor.yaml theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
model:
  name: starter
  version: v0.1.0

runtime:
  import: starter:Starter
  config: config.yaml
```

`runtime.import` is the Python import path to your class, in `module:ClassName` form.
`runtime.config` is the file whose path `load()` receives; leave it out and `load()` gets `None`.
`model.name` and `model.version` identify the release when you deploy. The scaffolded file has
more than this, including the build settings and the deployment plan, and the
[`reactor.yaml` reference](/deploy/platform/reactor-yaml) documents every key.

## Next

<CardGroup cols={2}>
  <Card title="The Step Loop" icon="play" href="/deploy/development/reactor-app/step-loop">
    How the runtime drives the three calls, when to skip a step, and how playout is paced.
  </Card>

  <Card title="Managing State" icon="sliders-horizontal" href="/deploy/development/reactor-app/state">
    Declare what a client can set, and the validation each field carries.
  </Card>
</CardGroup>
