# New Theory SDK
URL: /docs/newt-sdk

Python SDK for calling the New Theory API.



The New Theory SDK is a Python library for the New Theory inference API. You write two callbacks — one that returns what your robot is sensing, one that moves your robot — and call `robot.run(prompt)`. The SDK handles the network connection, the message format, and the inference loop.

Source repo: [`new-theory-research/newt-python`](https://github.com/new-theory-research/newt-python). Import name: `newt`.

<Callout type="warn" title="Pre-alpha (v0.0.3)">
  Install today is from the `newt-python` repo, not PyPI. A public PyPI release is eventually planned; this page will update when it lands.
</Callout>

## What it does [#what-it-does]

* **The loop.** Calls your `read_state()` to get an observation, sends it to the model, receives the plan, calls your `execute(chunk)` with it. Repeats until the model emits a terminal frame or `max_duration` elapses.
* **Connection.** Opens one authenticated WebSocket per `robot.run()` call, holds it for the duration of the task, closes it cleanly when the run ends.
* **Framing.** Encodes observation dicts as msgpack (with the msgpack-numpy ndarray extension) on the way out; decodes action chunks back into NumPy arrays on the way in. You never touch the wire format.
* **Bootstrap.** On `Robot()` construction, the SDK contacts the always-on registry to resolve the model tag and fetch its contract. A bad or revoked key fails here, before any WebSocket is opened, with `AuthError` and a hint that valid keys start with `nt_`. If the registry itself is unreachable, `RegistryUnavailable` is raised (check `NT_BOOTSTRAP_URL` or retry). Two environment overrides exist for infra testing: `NT_BOOTSTRAP_URL` replaces the registry URL and `NT_INFERENCE_URL` pins the inference endpoint. Leave both unset for production runs.
* **Auth.** Resolves your API key — `NT_API_KEY` env var first, then `~/.nt/credentials` — and presents it as a Bearer token on the WebSocket handshake. A key revoked mid-session raises `AuthError` at the next connection.
* **Error mapping.** Translates WebSocket close codes into typed Python exceptions, all subclasses of `newt.NewTheoryError`: `AuthError` (4001), `ForbiddenError` (4403), `ProtocolError` (4400), `ModelNotFoundError` (4404), `ContractMismatchError` (4422), `ServerError` (4500), and `VerifierError` (4503) — plus a codeless `ConnectionDroppedError` for abnormal drops. The full close-code table lives in the [API reference](/docs/api).

## What it doesn't do [#what-it-doesnt-do]

The SDK does not run inference — the model lives on New Theory's hosted endpoint, and the SDK is the client that calls it. It does not drive the robot; the SDK hands action chunks to your `execute` callback, which decides how to apply them. It does not bundle a simulator, a viewer, or a training stack.

## Call shape [#call-shape]

```python
import os
import newt

robot = newt.Robot(
    api_key=os.environ["NT_API_KEY"],
    read_state=read_my_robot,    # you write this
    execute=move_my_robot,       # you write this too
    model="so101",
)

robot.run("pick up the cup", max_duration=30.0)
```

`newt.Robot(...)` takes your API key, two callbacks, and the model name. `robot.run(prompt, max_duration=...)` opens the connection, drives the loop, and returns when the model signals the task is complete or `max_duration` elapses. The return value names how the run ended.

See [Set up your embodiment](/docs/set-up-your-embodiment) for wiring the callbacks into real hardware.

## Embodiment [#embodiment]

An embodiment is any object that drives your hardware. It implements two methods, and you pass it to `Robot(embodiment=obj)` instead of the two callbacks.

```python
class Embodiment(Protocol):
    def read_state(self) -> dict: ...
    def execute(self, action_chunk: np.ndarray) -> None: ...
```

`read_state()` returns one observation dict — the same shape `read_state=` returns. `execute(action_chunk)` applies one action chunk, an ndarray of shape `(action_horizon, action_dim)`. The protocol is `runtime_checkable`, so any object with both methods qualifies; no inheritance or registration is required.

`Robot(embodiment=obj)` is equivalent to passing `read_state=obj.read_state` and `execute=obj.execute` separately. The two paths produce the same run. Passing both `embodiment=` and either callback raises `EmbodimentError` with `type` `embodiment.conflict` — they are mutually exclusive. Passing a string raises `EmbodimentError` with `type` `embodiment.string_not_object`, because the SDK consumes objects and never resolves names. See [Embodiment errors](/docs/api/errors#embodiment) for all three failure types.

Bare callbacks remain supported and undeprecated. `embodiment=` is shorthand for the object you already build; if your style is two closures, two closures still work.

### Construction [#construction]

Starter kits stamp an embodiment class into your repo as `embodiment.py`. You construct it and pass the instance:

```python
from newt import Robot
from embodiment import SO101

robot = Robot(embodiment=SO101.from_config())
robot.run("Pick up the red cup")
```

`from embodiment import SO101` resolves because the file lives in your repo — the [starter kit](/docs/starters/so101) put it there. `from_config()` reads the hardware addresses from `~/.config/nt/nt.toml`. The class is yours to rename and edit. To write your own for any hardware, see [Set up your embodiment](/docs/set-up-your-embodiment).

## Install [#install]

The `newt` SDK lives in the public [`new-theory-research/newt-python`](https://github.com/new-theory-research/newt-python) repo. The HTTPS command clones anonymously — no GitHub account or access setup needed:

```bash
pip install git+https://github.com/new-theory-research/newt-python.git
```

On a machine set up for SSH-based GitHub auth instead, use the SSH URL:

```bash
pip install git+ssh://git@github.com/new-theory-research/newt-python.git
```

Then `import newt` works.

<Callout type="info">
  PyPI publication is on the roadmap; when it lands, this becomes a single `pip install newt` line.
</Callout>

## Try it [#try-it]

For the full walk-through — issuing a key, running the script, reading what comes back — see [Getting started](/docs/getting-started).
