> ## 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. Model code imports reactor_runtime; Python client code imports reactor_sdk. The runtime overview explains the model interface. /deploy/development/local-testing pairs a complete brightness model with a Python client test in a separate brightness-test workspace, without weights, a GPU, or a browser. Rebuild after edits: reactor run reuses the existing image. Check deployment access and capacity before deploying.
> 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.

# Local testing

> Run a sample model and verify its video and commands with Python, without a browser.

This guide pairs a brightness model with a Python script that checks its output.
The model sends gray video frames and accepts a command that changes their brightness.
It runs on a CPU and needs no model weights or API key.

The script tests `set_brightness` and `main_video` from the model below.
The spinning-logo model from the [quickstart](/deploy/development/quickstart) has different commands.
Use the separate workspace below to keep both examples available.

## Create a test workspace

[Install the CLI](/deploy/platform/installation) and start Docker.
You also need Python 3.10 or newer for the client script.
Run the model and client on the same machine.
Stop any model already using port 8080 before starting this one.

```sh theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
reactor init brightness-test
cd brightness-test
```

Keep the generated `reactor.yaml`, including its `build.runtime_version` and
`runtime.import: model:MyModel`. Keep `numpy` in `requirements.txt`.
Replace `model.py` in this workspace with the following:

```python model.py theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
from pathlib import Path

import numpy as np

from reactor_runtime import InputField, Output, ReactorModel, Video, event, session_started


class MyOutput(Output):
    main_video: Video


class MyModel(ReactorModel):
    fps = 24

    def load(self, config_path: Path | None) -> None:
        self.frame = np.empty((360, 640, 3), dtype=np.uint8)

    @session_started
    async def on_session_start(self) -> None:
        self.brightness = 128

    @event(name="set_brightness", description="Change the brightness of the video")
    async def set_brightness(
        self, value: int = InputField(default=128, ge=0, le=255)
    ) -> None:
        self.brightness = value

    async def run(self) -> None:
        while True:
            await self.connected.wait()
            while self.connected.is_set():
                self.frame.fill(self.brightness)
                await self.emit(MyOutput(main_video=self.frame.copy()))
```

Each frame is a NumPy array with shape `(height, width, 3)` and dtype `uint8`.
The three channels are red, green, and blue. The runtime paces output at `fps`.
A new session starts at brightness `128`. The command accepts values from `0` to `255`.

## Build and run the model

From the `brightness-test` workspace, run:

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

Leave the model running in this terminal. It waits for a client on port 8080.
After editing `model.py` or its dependencies, stop the model and run both commands again.
`reactor run` reuses an existing image, so rebuild to include your changes.

## Check video and commands

Open a second terminal in the `brightness-test` workspace.
Install the Python client SDK and NumPy in a virtual environment:

```sh theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
python3 -m venv .venv
source .venv/bin/activate
python -m pip install reactor-sdk numpy
```

Save the following as `check_model.py` in that workspace:

```python check_model.py theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
import asyncio

from reactor_sdk import Reactor


async def main():
    brightness = asyncio.Queue(maxsize=1)

    async with Reactor(model_name="brightness-test", local=True) as reactor:
        # Async frame handlers run on the client's asyncio loop.
        @reactor.track("main_video").on_frame
        async def on_frame(frame):
            if brightness.full():
                brightness.get_nowait()
            brightness.put_nowait(float(frame.mean()))

        async def wait_for_brightness(target):
            while True:
                measured = await brightness.get()
                if abs(measured - target) < 5:
                    return

        await asyncio.wait_for(reactor.connect(), timeout=30)
        for value in (128, 220):
            await asyncio.wait_for(
                reactor.send_command("set_brightness", {"value": value}), timeout=10
            )
            await asyncio.wait_for(wait_for_brightness(value), timeout=15)

        print("PASS: received video at both commanded brightness levels")


asyncio.run(main())
```

Run the check while the model runs:

```sh theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
python check_model.py
```

The script sends `set_brightness` with values `128` and `220`.
For each command, it waits for a video frame with the expected brightness.
The tolerance allows small differences from video compression.
On success, it prints:

```text theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
PASS: received video at both commanded brightness levels
```

The script exits with an error if connection, command handling, or the expected video change times out.
The context manager closes the client connection when the test ends.

If the command fails, check that you replaced `model.py` in `brightness-test` and rebuilt the image.
The spinning-logo model from the quickstart does not accept `set_brightness`.

## Test your own model

Replace the sample with your model, then rebuild and run it.
Adapt the command, payload, track name, and frame check in the script to your model.
Also test invalid inputs and state reset in a new session.
For this sample, brightness values outside `0` to `255` produce a validation error.
A new session restores brightness to `128`.

A health response or command reply alone does not prove that media reached the client.
This check verifies video delivery and command effects. It does not test GPU inference or model quality.
See [Using the SDK](/sdk-reference/using-the-sdk) for other client operations.
