> ## 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 video model catalog — slug, typed SDK package, and links to its schema — is at /model-api-reference/overview. Robotics policy documentation starts at /robotics/overview; X-WAM observations, actions, and client integration are under /robotics/xwam/; Cosmos3 Nano Policy DROID is under /robotics/cosmos/nano-policy-droid/. 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

> Connect to models, send commands, upload files, and download recordings with Android

`inc.reactor.sdk.android.Reactor` represents a client connection and its session. It implements
`AutoCloseable`. Calls that wait are `suspend` functions, and state is exposed as flows. See
[Installation](https://docs.reactor.inc/sdk-reference/android/installation.md) for the dependency and a complete example, and
the [API reference](https://javadoc.io/doc/inc.reactor/reactor-sdk-android/1.1.0) for every
signature.

The snippets below run inside a coroutine with a `Reactor reactor`; command and media operations
need a connected session. Import SDK types from `inc.reactor.sdk.android`.

## Create a client

Build `ReactorOptions` and pass it to the `Reactor` constructor. Creating a client does not connect
it.

| Option | Meaning |
| - | - |
| `apiUrl` | API URL. Defaults to `https://api.reactor.inc` |
| `jwt` | JWT obtained from your backend |
| `apiKey` | API key the client exchanges for a token itself, for development only |
| `local` | Enable local development, including accepting a local self-signed certificate |
| `generation` | `Generation.STREAMED` (default) or `Generation.STEPPED`; see [Stepped generation](#stepped-generation) |
| `stepTimeoutSeconds` | Upper bound for one step, in a stepped session. Defaults to 300 |

The constructor's second parameter, `dispatcher`, sets where control events are delivered. It
defaults to `Dispatchers.Main.immediate`, so status and error collectors can update UI directly.
Frame handlers run separately; see [Track](https://docs.reactor.inc/sdk-reference/android/track.md).

```kotlin theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
val reactor = Reactor(ReactorOptions(jwt = jwt))
```

For a local model, set `apiUrl = "http://localhost:8080"` and `local = true`. From an emulator, the
host machine is `10.0.2.2` rather than `localhost`.

## Authentication

Use `jwt` in apps you distribute. During development, `apiKey` lets the client exchange the key for
a token on each `connect()`, scoped to the model it creates a session for, or to the session it
joins. A `jwt` you set takes priority, and the SDK never replaces it.

A trusted backend or tool can exchange its API key with `Reactor.fetchJwt(apiUrl, apiKey)`. Without
`options`, the token grants everything the key allows. Pass a JSON scope to limit it:

```kotlin theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
val token = Reactor.fetchJwt(
    apiUrl = "https://api.reactor.inc",
    apiKey = apiKey,
    options = """{"models":["reactor/helios"],"max_sessions":1,"expires_after":3600}""",
)
```

An unknown option key fails the call rather than being ignored. To bind a token to an existing
session, put its ID in a `sessions` array instead of `models`. See [Authentication](https://docs.reactor.inc/authentication.md)
for token scope and expiry.

## Session lifecycle

| Member | Behavior |
| - | - |
| `connect(modelName)` | Create a session on `modelName`, such as `reactor/helios`, and connect |
| `connect(modelName, sessionId)` | Join an existing session |
| `reconnect()` | Re-establish the connection on the same session |
| `disconnect()` | Leave the session, ending it if this client created it |
| `close()` | Release the client and settle pending operations; safe to call more than once |
| `status` | `StateFlow<ConnectionStatus>` |
| `sessionId` | `StateFlow<String?>`, `null` without a session |

Model names are `owner/name`. `connect()` returns once the connection is set up; wait for `READY`
before sending commands:

```kotlin theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
reactor.connect("reactor/helios")
reactor.status.first { it == ConnectionStatus.READY }
```

Call `disconnect()` before `close()` to end a session you created. A session joined by ID remains
available to its owner after you disconnect. To restore a connection on the same session, call
`reconnect()` without first calling `disconnect()`. Receiving tracks resume after connect and
reconnect. Sending tracks must be published again before pushing frames.

Cancelling a coroutine stops waiting for a call. It does not undo an operation already sent. See
[Errors](https://docs.reactor.inc/sdk-reference/android/types.md#errors) for handling failures.

## Commands and schema

Use command names and arguments from the model's [API reference](https://docs.reactor.inc/model-api-reference/overview.md).
Arguments are a JSON object as a string. `requestSchema()` returns the running model's schema as a
`Map<String, Any?>`.

```kotlin theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
val reply = reactor.sendCommand("set_prompt", """{"prompt":"a mountain landscape"}""")
if (!reply.isEmpty) {
    Log.i(TAG, "${reply.type}: ${reply.data}")
}
```

`sendCommand(name)` sends a command with no arguments. The call returns a
[`CommandReply`](https://docs.reactor.inc/sdk-reference/android/types#commandreply). An empty reply means the model
acknowledged the command without returning a message; it is a successful result.

## Upload files

| Method | Source |
| - | - |
| `uploadFile(file)` | A `java.io.File` |
| `uploadBytes(data, name, mimeType)` | A direct `ByteBuffer` already in memory |
| `uploadContent(context, uri, name)` | A `content://` URI, such as a picker result |
| `uploadStream(name, cacheDirectory) { … }` | Any `InputStream` |

Each returns a [`FileRef`](https://docs.reactor.inc/sdk-reference/android/types#fileref). `uploadContent` and `uploadStream`
copy the stream to a cache file first, up to 64 MiB by default; pass `maxBytes` to change the limit.
Pass the uploaded file in `uploads`, keyed by the command's parameter name:

```kotlin theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
val image = reactor.uploadContent(context, pickedUri, name = "photo.jpg")
reactor.sendCommand(
    "set_image",
    uploads = """{"image":{"upload_id":"${image.uploadId}","name":"${image.name}",
        "mime_type":"${image.mimeType}","size":${image.size}}}""",
)
```

This example requires a model with a `set_image` command accepting an `image` parameter. Give the
upload a name with the right extension: the MIME type is inferred from it, and a content URI's own
name often has none. See [File uploads](https://docs.reactor.inc/concepts/file-uploads.md).

## Recordings

`requestClip(durationSeconds)` requests a recent window; `requestRecording()` requests the session
recording. Each returns a [`Clip`](https://docs.reactor.inc/sdk-reference/android/types#clip-and-downloadedclip) once the
request is accepted. The media may not be ready yet. `downloadClip` waits for it and assembles a
playable file:

```kotlin theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
val clip = reactor.requestClip(10.0)
val saved = reactor.downloadClip(clip, File(context.cacheDir, "clip.mp4")) { progress ->
    Log.i(TAG, "${progress.done}/${progress.total}")
}
Log.i(TAG, saved.path)
```

The progress callback is optional. `readyTimeoutSeconds` bounds the wait beyond the clip's predicted
ready time; by default it waits while the session can still produce the clip. Keep the session open
until the download completes. Closing the client while a download runs fails the call but does not
cancel the download; the file may still arrive. See [Recordings](https://docs.reactor.inc/concepts/recordings.md).

## Events

| Flow | Payload |
| - | - |
| `status` | `StateFlow<ConnectionStatus>` |
| `errors` | `SharedFlow<ReactorException>` |
| `messages` | `SharedFlow<Map<String, Any?>>` |
| `runtimeMessages` | `SharedFlow<Map<String, Any?>>` |
| `sessionId` | `StateFlow<String?>` |

Collect them from a lifecycle-aware scope, for example
`lifecycleScope.launch { reactor.errors.collect { … } }`. Start collecting `errors` and `messages`
before connecting to see every one. `messages` carries each message the model sends as a
`{"type", "data"}` object, including the ones no call returns, such as state updates. A command's
reply also arrives as the return value of `sendCommand`. `runtimeMessages` carries what the platform
sends about the session, such as recording lifecycle notices. Media is delivered on individual
[tracks](https://docs.reactor.inc/sdk-reference/android/track.md).

## Stepped generation

A model that supports it can generate one step at a time instead of streaming. Set
`generation = Generation.STEPPED`, then call `step()`:

```kotlin theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
val reactor = Reactor(ReactorOptions(jwt = jwt, generation = Generation.STEPPED))
reactor.connect("owner/model")

val result = reactor.step(
    state = """{"prompt":"a red fox"}""",
    commands = listOf(StepCommand("advance", """{"frames":4}""")),
)
for (frame in result.track("main_video").frames) {
    val rgb = frame.data() // (height, width, 3) uint8 RGB
}
```

The runtime applies the state, then the commands, then the input frames pushed since the previous
step, generates, and returns a [`StepResult`](https://docs.reactor.inc/sdk-reference/android/types#stepresult). Every
argument is optional: `step()` runs on the model's current state. `connect()` fails when the model
cannot run a stepped session.

In a stepped session, `Track.pushFrame` buffers a frame for the next step instead of sending it.
`clearStepInputs()` drops what is buffered. Frame handlers are refused, because nothing streams.

## Connection statistics

`stats()` returns a [`Stats`](https://docs.reactor.inc/sdk-reference/android/types#stats) snapshot of the connection.
`droppedFrames` counts received frames that arrived on a track with no handler.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.