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

# Writing a good model

> Keep the code that talks to clients apart from the code that runs the weights. Optional, and worth it past a few hundred lines.

Nothing on this page is enforced. A `ReactorApp` with the weights loaded in `load()` and the
inference written straight into `generate()` is a complete model, and for a small one that is
the right shape. This page is about what to do when the model grows: the pattern the runtime's own
examples follow, and the reasons behind it.

The idea is a split into two halves. The **model** is a black box: give it an input, it produces a
result. The **application** is everything around it that a client can see or touch.

<Frame caption="The application owns everything a client can observe. The model owns the weights and produces a result from an input.">
  <img src="https://mintcdn.com/reactortechnologiesinc/dCQX8iFwCidKaSoT/diagrams/two-halves.svg?fit=max&auto=format&n=dCQX8iFwCidKaSoT&q=85&s=d7d10abcc350a81fb289c66b4e6e0605" alt="An Application box holding state and commands, tracks and messages, process_input() and process_output(). Inside it sits a Model box with load(), generate(), and reset(), described as owning the weights, cache, and its own step count. Client input enters the application, an input goes into the model, a result comes back, and the output leaves to the clients." width="680" height="260" data-path="diagrams/two-halves.svg" />
</Frame>

Kept apart, the model half can be tested on its own, with no runtime and no client, by
constructing it and calling `generate()` in a loop. The application half can be read without
opening the model, since everything a client can observe is in one place. And because the two
share nothing but two dataclasses, either can change without touching the other.

The examples are the runtime's Waypoint: `waypoint_model.py` is the model half, `waypoint.py` is
the application half.

## The model half

A plain Python class with three methods. It imports nothing from `reactor_runtime`, knows nothing
about clients, tracks, or commands, and runs in a notebook with Reactor uninstalled.

```python waypoint_model.py theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
# imports omitted


class WaypointModel:
    def load(self, config_path: Path | None) -> None:
        # ✅ Weights and warmup, once
        self.engine = WorldEngine(...)
        self.reset()

    def generate(self, input: WaypointInput) -> WaypointResult:
        # ✅ A step it cannot take from here is a raise
        if input.seed_id != self.seed_id:
            if input.seed is None:
                raise NotSeeded("no seed frame to start from")
            self.engine.reset()
            self.engine.append_frame(input.seed)
            self.seed_id = input.seed_id
            self.index = 0

        # ✅ One step, and its own count of it
        frames = self.engine.gen_frame(ctrl=input.controls)
        self.index += 1
        index = self.index - 1
        return WaypointResult(frames, index, input.seed_id)

    def reset(self) -> None:
        # ✅ Back to the default state, called by the application
        self.engine.reset()
        self.seed_id = None
        self.index = 0
```

The model keeps its own state, the world and the step count, and nobody
else writes it. It never invents input: with no seed to start from, it raises rather than making
one up. And it never resets itself: when it cannot continue, it raises, and the application decides
what to do about it.

## The application half

The `ReactorApp` constructs the model in `load()`, holds it under an attribute, and its
`generate()` is one line that forwards to it. Everything else on the class is about the client.

```python waypoint.py theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
class Waypoint(ReactorApp):
    state: WaypointState

    def load(self, config_path: Path | None) -> None:
        # ✅ The model half lives here
        self.engine = WaypointModel()
        self.engine.load(config_path)

    async def process_input(self) -> WaypointInput:
        # ✅ Client facts decide whether to run at all
        if self.state.paused:
            raise ApplicationError("paused")
        if self.state._seed is None:
            raise ApplicationError("no seed image")

        # ✅ Only what this step needs crosses over
        state = self.state
        new_seed = state._seed_id != state._applied_seed_id
        return WaypointInput(
            controls=state.controls(),
            seed=state._seed if new_seed else None,
            seed_id=state._seed_id,
        )

    def generate(self, input: WaypointInput) -> WaypointResult:
        return self.engine.generate(input)

    async def process_output(
        self, outcome: StepOutcome
    ) -> WaypointOutput | None:
        if outcome.error is not None:
            raise outcome.error

        # ✅ What the model knows, read off the result
        result: WaypointResult = outcome.result
        self.state._applied_seed_id = result.seed_id
        return WaypointOutput(main_video=result.frames)

    @event(name="reset", description="Restart from the seed.")
    def reset(self) -> None:
        # ✅ Handlers run between steps: safe to call the model
        self.engine.reset()
        self.state._applied_seed_id = None
        self.output.flush()

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

## Where the halves meet

The two halves talk through two dataclasses you define: the input `process_input()` builds and
the result `generate()` returns. [The Step Loop](/deploy/development/reactor-app/step-loop) covers
how they travel.

<Frame caption="The only things that cross between the halves are the two dataclasses.">
  <img src="https://mintcdn.com/reactortechnologiesinc/dCQX8iFwCidKaSoT/diagrams/inner-contract.svg?fit=max&auto=format&n=dCQX8iFwCidKaSoT&q=85&s=8424366e636159064a8496668f349662" alt="An Application box with process_input() and process_output() on the left, a Model box with generate() on the right. A WaypointInput crosses from process_input() into the model, and a WaypointResult crosses from the model back into process_output()." width="680" height="210" data-path="diagrams/inner-contract.svg" />
</Frame>

Going in, everything the model needs to know rides in the input: a control, a prompt, a seed frame.
The application never writes a model attribute. When a handler needs the model to change, it calls
a method the model wrote, such as `reset()`, and since handlers run between steps that call is
safe.

Coming back, everything the application needs to know rides in the result. Waypoint reads
`result.seed_id` to know which seed the world holds and `result.index` to tag frames, and never
touches `self.engine.index`. Design the result as the model's public face.

## Refusing is the application's, failing is the model's

`process_input()` refuses a step with `ApplicationError` for a fact about the client: paused, no
prompt yet, waiting for frames. The model is not called and the loop asks again shortly.

`generate()` fails a step by raising the model's own exception for a fact about the model: it
cannot continue from the state it holds. The runtime delivers it to `process_output()` as
`outcome.error`, and the application decides whether to recover or let the session end.

Never raise `ApplicationError` from the model half. It does not import the runtime, and whether to
refuse is not its call.

## Next

<CardGroup cols={2}>
  <Card title="The Step Loop" icon="play" href="/deploy/development/reactor-app/step-loop">
    How the input and the result travel between the three calls.
  </Card>

  <Card title="Session Recording" icon="circle-dot" href="/deploy/development/recording">
    Record sessions and let clients capture clips.
  </Card>
</CardGroup>
