# menlo-robot-sdk

> Python SDK for the Menlo robot platform: create robots, manage sessions,
> mint viewer room keys, and drive robots live over LiveKit. This file is the
> complete agent-readable reference (llms-full.txt convention) — generated by
> `scripts/gen_api_reference.py`, do not edit by hand.

Install: `pip install "menlo-robot-sdk[livekit]"` · Auth: `MENLO_API_KEY`
(`sk_live_…` from the console at https://console.menlo.ai) + `MENLO_RCS_URL`
(prod: `https://platform-auth.menlo.ai/rcs`). All SDK-defined exceptions derive from
`MenloSDKError`; runtime/SFU timeouts raise the stdlib `TimeoutError`; low-level
network failures from control-plane HTTP pass through as `httpx` exceptions
(`httpx.TransportError`, incl. `httpx.TimeoutException`).

Imports: every symbol below is importable from the top-level package —
`from menlo_robot_sdk import AsyncClient, connect, MenloSession, ...` — except
the room-key helpers (`from menlo_robot_sdk.experimental import ...`) and
`connect_session` (`from menlo_robot_sdk.livekit import ...`).

# Concepts (read this first)

- **Two planes.** The *control plane* (HTTP via the platform edge) manages
  records: robots, sessions, tokens. The *runtime plane* (LiveKit room, no HTTP)
  is where the robot is actually driven: `invoke`, `state.get`, `get_vision` are
  RPCs to a participant in the room — RCS is not involved in them.
- **rcw = the runtime worker**: whichever room participant answers the
  `runtime.*` RPCs. It executes the robot's actions. For SimpleSim, **the Chrome
  viewer tab is the rcw** — the simulator runs in the browser, so the robot only
  "exists" while that tab is open. A server-side worker (e.g. `sim-worker`) can
  be the rcw instead.
- **`worker_names` vs `rcw_identity_prefix`** — different jobs:
  `create_session(worker_names=[...])` *dispatches* server-side workers into the
  room (`[]` = dispatch nothing — the browser will fill the role; omitted = the
  platform's default dispatch). `connect(rcw_identity_prefix="simplesim")` tells
  the SDK how to *find* the rcw among participants: identities match a name
  exactly, or as `name-...` / `name:...` (the SimpleSim tab joins as
  `simplesim:<robot_id>`, hence prefix `"simplesim"`).

# Usage pattern (the canonical flow)

```python
import asyncio
from menlo_robot_sdk import AsyncClient, connect
from menlo_robot_sdk.experimental import generate_room_key

async def main() -> None:
    async with AsyncClient() as client:          # MENLO_RCS_URL + MENLO_API_KEY env
        created = await client.robots.create(name="bot", model="asimov-v0")
        robot_id = created.robot.id

        session = await connect(
            client, robot_id,
            worker_names=[],                     # browser viewer is the runtime
            rcw_identity_prefix="simplesim",
        )
        key = await generate_room_key(client, robot_id)
        print(f"open in Chrome: https://sim.menlo.ai/?key={key}")

        skills = await session.discover_skills() # poll until the viewer joins
        result = await session.invoke("go_to",
            {"target": {"kind": "entity", "entity_id": "pad_B"}}, timeout_s=300)
        assert result.status == "done", result.error
        jpeg = await session.get_vision("pov")   # JPEG bytes

        await session.disconnect()
        await client.robots.delete(robot_id)

asyncio.run(main())
```

# Troubleshooting

- `discover_skills()` raises `RuntimeError` ("no rcw answering") → the viewer
  tab isn't open / hasn't finished loading. Open the room-key URL in Chrome,
  give the scene a few seconds, retry in a poll loop.
- `invoke()` raises `TimeoutError` → the reply didn't arrive in time; the
  action **may still be running** on the robot. Use a larger `timeout_s` for
  long actions (a `go_to` walk can take minutes) and
  `cancel_action("cancel-active")` to stop the active action.
- `RcwRuntimeError` with code `NOT_READY` → the runtime is still initializing
  (browser WASM load); wait and retry.
- `result.status == "failed"` is a normal terminal outcome, not an exception —
  inspect `result.error.code` (e.g. `NAVIGATION_STUCK`, `CANCELLED`).
- Don't reload the viewer tab (its credential bakes a fixed identity) — mint a
  fresh room key instead.

Version: `0.2.2`

# Clients

## `AsyncClient`

Asynchronous RCS client (preferred in docs).

Same configuration rules as :class:`Client`.

```python
AsyncClient(*, settings: 'MenloSettings | None' = None, rcs_url: 'str | None' = None, identity: 'str | None' = None, api_key: 'str | None' = None, timeout: 'float | None' = None, max_retries: 'int | None' = None, http_client: 'httpx.AsyncClient | None' = None) -> 'None'
```

### `AsyncClient.aclose`

```python
async aclose() -> 'None'
```

*(no docstring)*

### `AsyncClient.settings` *(property)*

*(no docstring)*

## `Client`

Synchronous RCS client.

Pass ``settings=``, or pass ``rcs_url`` plus ONE of ``api_key`` (platform-auth
edge — recommended) / ``identity`` (direct RCS), or omit all three to load from
env (``MENLO_RCS_URL`` + ``MENLO_API_KEY``, or ``MENLO_IDENTITY``). ``timeout``
and ``max_retries`` override ``MENLO_ROBOT_SDK_*`` env values.

```python
Client(*, settings: 'MenloSettings | None' = None, rcs_url: 'str | None' = None, identity: 'str | None' = None, api_key: 'str | None' = None, timeout: 'float | None' = None, max_retries: 'int | None' = None, http_client: 'httpx.Client | None' = None) -> 'None'
```

### `Client.close`

```python
close() -> 'None'
```

*(no docstring)*

### `Client.settings` *(property)*

*(no docstring)*

Both clients expose the Robots API as `client.robots` (methods below are `async` on `AsyncClient`, sync with identical signatures on `Client`).

## `client.robots`

Async ``/v1/robots`` API — exposed as ``client.robots`` on :class:`AsyncClient`.

### `client.robots.create`

```python
async create(*, name: 'str', model: 'str', language: 'Language' = <Language.EN: 'en'>, voice: 'Voice' = <Voice.KOKORO: 'kokoro'>, description: 'str' = '') -> 'RobotCreateResponse'
```

Create a virtual robot. ``pin_code`` is returned only once — save it securely.

### `client.robots.create_participant_token`

```python
async create_participant_token(robot_id: 'str', *, participant_identity: 'str', participant_name: 'str' = 'Viewer', ttl_seconds: 'int | None' = None) -> 'ParticipantTokenResponse'
```

*(no docstring)*

### `client.robots.create_session`

```python
async create_session(robot_id: 'str', *, workers: 'WorkerConfig | None' = None, worker_names: 'Sequence[str] | None' = None) -> 'SessionCreateResponse'
```

Create the robot's session (LiveKit room + optional worker dispatch).

``worker_names`` semantics: a list dispatches one LiveKit agent per name
(e.g. ``["sim-worker"]``); an EMPTY list ``[]`` means no dispatch — used
when a browser runtime (SimpleSim viewer) fills the room by presence;
OMITTING the parameter lets the platform apply its configured default
dispatch. ``workers=`` is deprecated and ignored for dispatch.

### `client.robots.delete`

```python
async delete(robot_id: 'str') -> 'None'
```

*(no docstring)*

### `client.robots.delete_session`

```python
async delete_session(robot_id: 'str') -> 'None'
```

*(no docstring)*

### `client.robots.get`

```python
async get(robot_id: 'str') -> 'RobotOut'
```

*(no docstring)*

### `client.robots.iter`

```python
async iter(*, limit: 'int' = 20) -> 'AsyncIterator[RobotOut]'
```

*(no docstring)*

### `client.robots.list`

```python
async list(*, limit: 'int' = 20, after_id: 'str | None' = None) -> 'RobotList'
```

*(no docstring)*

### `client.robots.update`

```python
async update(robot_id: 'str', *, name: 'str | None' = None, language: 'Language | None' = None, voice: 'Voice | None' = None, description: 'str | None' = None) -> 'RobotOut'
```

Partial PATCH — only keyword arguments you pass are sent to RCS.

# Connect & drive

## `connect`

```python
async connect(client: 'AsyncClient', robot_id: 'str', *, environment_id: 'str | None' = None, scene_id: 'str | None' = None, workers: 'WorkerConfig | None' = None, worker_names: 'list[str] | None' = None, join_livekit: 'bool' = True, livekit_url: 'str | None' = None, callbacks: 'ConnectCallbacks | None' = None, session_ready_timeout: 'float | None' = 120.0, wait_session_ready: 'bool' = False, rcw_identity_prefix: 'str | None' = None, runtime_call_timeout_s: 'float | None' = None) -> 'MenloSession'
```

Connect to a robot session (control plane + optional LiveKit).

This is the v0.2 entrypoint aligned with the rough Menlo Robot SDK spec. It wraps
``robots.create_session`` and optional :func:`menlo_robot_sdk.livekit.connect_session`.

**Available now:** session credentials, optional LiveKit room, ``on_connected`` /
``on_session_ready`` / ``on_disconnected`` when ``join_livekit=True``.
``on_session_ready`` fires when every ``room_metadata.required_participants`` slot
with ``min_count > 0`` has joined (same identity-prefix rules as RCS). Set
``wait_session_ready=True`` to block until ready (uses ``session_ready_timeout``).

**Runtime methods** (reads via ``session.state.get(...)`` / ``state.list()``, plus
``discover_skills`` / ``invoke`` / ``cancel_action`` / ``get_vision``) talk to the
per-room runtime worker (rcw) **directly over the LiveKit SFU** — RCS is not in the
runtime path (see ADR 0001). They require
``join_livekit=True`` and a runtime worker in the room. The rcw is resolved from the
room's participant list: by the session's runtime worker slots, or by an explicit
``rcw_identity_prefix`` (e.g. ``"simplesim"`` for a browser rcw). ``on_action_result``
fires with the terminal event from each ``invoke``. ``runtime_call_timeout_s`` sets
the default response timeout for ``invoke`` (a tool call blocks until the action
resolves, so a long ``go_to`` needs more than the read timeout); override per call
with ``invoke(..., timeout_s=...)``.

**Still blocked** (see ADR 0002): ``environment_id`` / ``scene_id`` at attach,
``get_logs``.

Use :class:`~menlo_robot_sdk.client.AsyncClient` with ``MENLO_RCS_URL`` + identity
(direct RCS), or ``MENLO_API_KEY`` with ``rcs_url`` pointing at the platform-auth
edge (the edge mints the signed identity — see #136).

## `MenloSession`

Handle for a robot session created via :func:`connect`.

Runtime calls — reads via the :attr:`state` namespace (``await
session.state.get("scene_state")``), plus :meth:`discover_skills`, :meth:`invoke`,
:meth:`cancel_action`, :meth:`get_vision` — go to the per-room runtime worker (rcw)
**directly over the
LiveKit SFU** — RCS is not in the runtime path. They require a joined room with a
resolved rcw (``connect(join_livekit=True)`` with a runtime worker in the room or
an explicit ``rcw_identity_prefix``). The first runtime call polls for the
runtime worker (up to the connect ``session_ready_timeout``) and raises the
stdlib ``RuntimeError`` if none can be resolved (no room, no worker hints, or
none answered before the timeout); an unanswered RPC raises ``TimeoutError``;
a runtime-side failure raises :class:`RcwRuntimeError`.

### `MenloSession.cancel_action`

```python
async cancel_action(action_id: 'str') -> 'None'
```

Cancel a running action via the ``runtime.cancel`` RPC on the rcw.

### `MenloSession.disconnect`

```python
async disconnect() -> 'None'
```

Delete the RCS session and close the LiveKit room if joined.

Safe to call more than once and from concurrent tasks: the room close and
``on_disconnected`` run at most once, and a failed ``DELETE /session`` (e.g. a
transient 502) is retried by the next call instead of being skipped forever.

Do NOT await ``disconnect()`` from inside an ``on_disconnected`` callback — the
callback fires while this method holds its (non-reentrant) lock, so that would
deadlock. The callback is a notification; cleanup is already in progress.

### `MenloSession.discover_skills`

```python
async discover_skills() -> 'list[RuntimeToolDescriptor]'
```

Discover the robot's available skills (the live runtime tool catalogue).

Calls ``runtime.tools_list`` on the rcw over the SFU. "Skills" are the runtime
tools (go_to, pick_entity, …); each scene/runtime exposes its own set.

### `MenloSession.get_logs`

```python
get_logs(*, level: 'str' = 'INFO', max_logs: 'int' = 10) -> 'Any'
```

Recent robot logs (blocked on RCS upstream work, #136 §5).

### `MenloSession.get_vision`

```python
async get_vision(camera_id: 'str', *, width: 'int' = 1280, height: 'int' = 720, quality: 'float' = 0.7) -> 'bytes'
```

Latest camera frame as JPEG bytes (on-demand capture over the SFU).

Triggers ``runtime.cameras_capture`` on the rcw for ``camera_id`` (e.g. ``pov``,
``topdown``); the frame is returned over a LiveKit byte stream, so full sizes
(up to the runtime's native 1280x720) work without the 15 KiB RPC payload cap.

Raises ``ValueError`` for non-positive ``width`` / ``height`` or ``quality``
outside ``(0, 1]``.

### `MenloSession.invoke`

```python
async invoke(skill_id: 'str', parameters: 'dict[str, Any] | None' = None, *, timeout_s: 'float | None' = None) -> 'ActionResult'
```

Run a skill and return its terminal :class:`ActionResult`.

Calls ``runtime.tools_call`` on the rcw over the SFU. The call blocks until the
action resolves and returns the final ActionResult in one RPC reply (no
intermediate accepted/running phases). Check ``result.status`` (``"done"`` |
``"failed"``) and ``result.error``; the action id is ``result.meta.action_id``.
The SDK also fires ``on_action_result`` (set on :func:`connect`) with the
terminal event.

``timeout_s`` overrides how long to wait for the action to resolve, for this
call only. Since the RPC blocks until the action completes, set this above the
action's expected duration (a long ``go_to`` can exceed the default read
timeout). When omitted, uses the session default
(``connect(runtime_call_timeout_s=...)``) or the SDK's long tool-call default.

### `MenloSession.state` *(property)*

Queryable runtime reads over the SFU.

``await session.state.get("scene_state")`` / ``get("robot_status")``; list the
available keys with ``session.state.list()``. See :class:`RuntimeStateReads`.

### `MenloSession.wait_until_ready`

```python
async wait_until_ready(*, timeout: 'float | None' = 120.0) -> 'None'
```

Block until RCS ``required_participants`` slots are satisfied in the room.

Requires ``join_livekit=True`` on :func:`connect`. Uses the same slot rules as
RCS webhooks (identity prefix match). Raises :class:`TimeoutError` on timeout.

## `ConnectCallbacks`

Optional hooks fired around the LiveKit join.

``on_connected`` runs after this client joins the room. ``on_session_ready`` runs
once every ``room_metadata.required_participants`` slot with ``min_count > 0`` has
a matching remote participant (RCS identity-prefix rules). ``on_action_result``
fires with the terminal runtime event each time :meth:`MenloSession.invoke`
completes over the SFU.

``on_disconnected`` may fire from a LiveKit room event (sync callback context).
If the hook is **async** and no asyncio event loop is running, the SDK logs a
warning and skips the callback — use a sync hook or call ``await session.disconnect()``
from async code instead.

```python
ConnectCallbacks(on_connected: 'Callback | None' = None, on_session_ready: 'Callback | None' = None, on_disconnected: 'Callback | None' = None, on_action_result: 'ActionResultCallback | None' = None) -> None
```

## `livekit.connect_session`

```python
async connect_session(session: 'SessionCreateResponse', *, livekit_url: 'str | None' = None) -> 'Any'
```

Connect to a session room using the participant token from RCS.

Async-first: ``await connect_session(session)``. In a sync-only script, use
``asyncio.run(connect_session(session))`` — do not call from an already-running
event loop without ``await``.

# Configuration

## `MenloSettings`

RCS client settings.

Two auth modes:
- **API key** (``api_key`` / ``MENLO_API_KEY``): point ``rcs_url`` at the
  menlo-platform-auth edge (e.g. ``http://localhost:8095/rcs``). The SDK
  sends ``Authorization: Bearer <key>``; the edge verifies it and mints the
  signed ``X-Menlo-Identity`` ([#136](https://github.com/menloresearch/mono/issues/136)).
- **Direct identity** (``identity`` / ``MENLO_IDENTITY``): talk straight to
  RCS with an ``X-Menlo-Identity`` header (dev/unsigned or pre-signed).

Exactly one is used; ``api_key`` wins if both are set.

```python
MenloSettings(rcs_url: 'str', identity: 'str' = '', timeout: 'float' = 30.0, max_retries: 'int' = 2, api_key: 'str | None' = None) -> None
```

### `MenloSettings.url` *(property)*

Alias for rough-spec ``url`` (same as ``rcs_url``).

### `MenloSettings.from_env`

```python
from_env() -> 'MenloSettings'
```

*(no docstring)*

## `DevIdentity`

Unsigned JSON identity for RCS dev mode (RCS_ALLOW_UNSIGNED_IDENTITY).

```python
DevIdentity(sub: 'str', organization_id: 'str', team_id: 'str', roles: 'tuple[str, ...]' = (), scopes: 'tuple[str, ...]' = ()) -> None
```

### `DevIdentity.to_header`

```python
to_header() -> 'str'
```

*(no docstring)*

### `DevIdentity.to_payload`

```python
to_payload() -> 'dict[str, Any]'
```

*(no docstring)*

# Room keys & viewer links (`menlo_robot_sdk.experimental`)

## `experimental.generate_room_key`

```python
async generate_room_key(client: 'AsyncClient', robot_id: 'str', *, viewer_livekit_url: 'str | None' = None, participant_identity: 'str | None' = None, participant_name: 'str | None' = None, ttl_seconds: 'int' = 14400) -> 'str'
```

Generate a compact "room key" for the SimpleSim browser demo.

The key is one opaque string the user pastes into the demo's gate (or appends
as ``?key=``) to open a robot's room — replacing the long
``?lk_url=&lk_token=&lk_room=`` URL. It bundles a short-lived LiveKit token
(identity ``simplesim:{robot_id}``) with the LiveKit URL and room name — the
same credential the legacy viewer URL carried, with **no API key and no
control-panel blob** in it. It is a publish-capable room token (the browser is
the rcw), so handle it like the viewer URL: short-lived, not for public posting.
To drive the robot via the control plane, use the SDK or the control-panel.

``viewer_livekit_url`` overrides only the embedded LiveKit URL for browser
reachability (e.g. Docker-internal ``ws://livekit:7880`` →
host ``ws://localhost:7880``). See :func:`generate_room_key_url` for a ready
``?key=`` link, or :func:`get_session_viewer_url` for the legacy long URL.

## `experimental.generate_room_key_sync`

```python
generate_room_key_sync(client: 'Client', robot_id: 'str', *, viewer_livekit_url: 'str | None' = None, participant_identity: 'str | None' = None, participant_name: 'str | None' = None, ttl_seconds: 'int' = 14400) -> 'str'
```

Sync variant of :func:`generate_room_key`.

## `experimental.generate_room_key_url`

```python
async generate_room_key_url(client: 'AsyncClient', robot_id: 'str', *, viewer_base_url: 'str | None' = None, viewer_livekit_url: 'str | None' = None, participant_identity: 'str | None' = None, participant_name: 'str | None' = None, ttl_seconds: 'int' = 14400) -> 'str'
```

Like :func:`generate_room_key`, but return a ready demo URL ``{base}/?key={key}``.

``viewer_base_url`` (or ``MENLO_ROBOT_VIEWER_URL``) is the SimpleSim app base.
Open the result in a browser to connect — no pasting.

## `experimental.generate_room_key_url_sync`

```python
generate_room_key_url_sync(client: 'Client', robot_id: 'str', *, viewer_base_url: 'str | None' = None, viewer_livekit_url: 'str | None' = None, participant_identity: 'str | None' = None, participant_name: 'str | None' = None, ttl_seconds: 'int' = 14400) -> 'str'
```

Sync variant of :func:`generate_room_key_url`.

## `experimental.get_session_viewer_url`

```python
async get_session_viewer_url(client: 'AsyncClient', robot_id: 'str', *, viewer_base_url: 'str | None' = None, viewer_livekit_url: 'str | None' = None, participant_identity: 'str | None' = None, participant_name: 'str | None' = None, ttl_seconds: 'int' = 14400) -> 'str'
```

Build a browser viewer URL for an existing robot session.

This helper is intentionally experimental and SimpleSim-specific: it contains
the current factory viewer's query-parameter conventions and, unless
overridden, uses the SimpleSim participant identity ``simplesim:{robot_id}``
and display name ``SimpleSim Viewer ({robot_id})``. Pass
``participant_identity`` and ``participant_name`` for other viewer programs.

RCS always returns its configured LiveKit URL unchanged. ``viewer_livekit_url``
only overrides the ``lk_url`` query parameter for browser reachability, for
example converting Docker-internal ``ws://livekit:7880`` to host-visible
``ws://localhost:7880``.

``ttl_seconds`` controls the minted LiveKit token lifetime. RCS caps extra
participant tokens at six hours to limit the lifetime of leaked browser URLs;
this helper defaults to four hours for local notebook exercises.

## `experimental.get_session_viewer_url_sync`

```python
get_session_viewer_url_sync(client: 'Client', robot_id: 'str', *, viewer_base_url: 'str | None' = None, viewer_livekit_url: 'str | None' = None, participant_identity: 'str | None' = None, participant_name: 'str | None' = None, ttl_seconds: 'int' = 14400) -> 'str'
```

Sync variant of :func:`get_session_viewer_url`.

The same SimpleSim-specific participant identity/name defaults apply.

# Workers

## `workers.for_rcs_stack`

```python
for_rcs_stack() -> 'WorkerConfig'
```

DEPRECATED — use ``create_session(..., worker_names=["sim-worker"])``.

RCS dispatches from ``worker_names`` only; the legacy ``workers`` config no
longer drives dispatch, and passing it emits a ``DeprecationWarning``.

# Errors

## `MenloSDKError`

Base class for all Menlo Robot SDK errors.

Catch this to handle any SDK-raised failure (API errors, runtime-worker
failures, blocked features) with one except clause.

## `MenloAPIError`

Raised when RCS returns HTTP status >= 400.

```python
MenloAPIError(*, status_code: 'int', code: 'str', message: 'str', details: 'dict[str, Any] | None' = None) -> 'None'
```

### `MenloAPIError.request_id` *(property)*

*(no docstring)*

### `MenloAPIError.trace_id` *(property)*

*(no docstring)*

## `RcwRuntimeError`

A ``runtime.*`` call failed at the rcw with a guard envelope.

The rcw bindings (SimpleSim bridge, runtime-tools) return ``{status:"failed",
error:{code,message}}`` — with no ActionResult ``meta`` — when a non-action method
(tools_list / state) fails or the runtime isn't ready. The SDK raises this rather
than crashing on pydantic validation of the non-conforming shape.

Attributes: ``code`` (e.g. ``NOT_READY``) and ``runtime_message``.

```python
RcwRuntimeError(code: 'str', message: 'str') -> 'None'
```

## `MenloNotAvailableError`

Raised when a product API is defined but blocked on upstream RCS/platform work.

```python
MenloNotAvailableError(feature: 'str', *, issue: 'int', detail: 'str' = '') -> 'None'
```

# Wire models

## `ActionResult` *(model)*

*(no docstring)*

| field | type | default |
|---|---|---|
| `status` | `Status` | *required* |
| `result` | `Any | None` | None |
| `error` | `RuntimeError | None` | None |
| `meta` | `ActionMeta` | *required* |
| `runtime_state` | `RuntimeState | None` | None |
| `scene_state` | `SceneState | None` | None |

## `RuntimeError` *(model)*

*(no docstring)*

| field | type | default |
|---|---|---|
| `code` | `str` | *required* |
| `message` | `str` | *required* |
| `details` | `Any | None` | None |

## `ActionMeta` *(model)*

*(no docstring)*

| field | type | default |
|---|---|---|
| `action_id` | `str` | *required* |
| `started_at_ms` | `int` | *required* |
| `finished_at_ms` | `int` | *required* |
| `duration_ms` | `int` | *required* |
| `degraded` | `bool | None` | None |
| `note` | `str | None` | None |

## `RuntimeEvent` *(model)*

*(no docstring)*

| field | type | default |
|---|---|---|
| `action_id` | `str` | *required* |
| `phase` | `Phase` | *required* |
| `at_ms` | `int` | *required* |
| `result` | `ActionResult | None` | None |

## `RuntimeToolDescriptor` *(model)*

*(no docstring)*

| field | type | default |
|---|---|---|
| `name` | `str` | *required* |
| `title` | `str | None` | None |
| `description` | `str` | *required* |
| `input_schema` | `dict[str, Any]` | *required* |
| `output_schema` | `dict[str, Any] | None` | None |
| `annotations` | `ToolAnnotations | None` | None |
| `cost` | `ActionCostEstimate | None` | None |

## `RobotOut` *(model)*

*(no docstring)*

| field | type | default |
|---|---|---|
| `id` | `str` | *required* |
| `name` | `str` | *required* |
| `model` | `str` | *required* |
| `type` | `RobotType` | *required* |
| `language` | `Language` | *required* |
| `voice` | `Voice` | *required* |
| `description` | `str` | *required* |
| `team_id` | `str` | *required* |
| `organization_id` | `str` | *required* |
| `created_by` | `str` | *required* |
| `created_at` | `datetime` | *required* |
| `updated_at` | `datetime` | *required* |

## `RobotCreateResponse` *(model)*

PIN is show-once; save ``pin_code`` when returned.

| field | type | default |
|---|---|---|
| `robot` | `RobotOut` | *required* |
| `pin_code` | `str` | *required* |

## `RobotList` *(model)*

*(no docstring)*

| field | type | default |
|---|---|---|
| `robots` | `list[RobotOut]` | *required* |
| `next_id` | `str | None` | None |

## `SessionCreateResponse` *(model)*

*(no docstring)*

| field | type | default |
|---|---|---|
| `room_name` | `str` | *required* |
| `livekit_url` | `str` | *required* |
| `participant_token` | `str` | *required* |
| `room_metadata` | `SessionMetadata` | *required* |

## `ParticipantTokenResponse` *(model)*

*(no docstring)*

| field | type | default |
|---|---|---|
| `room_name` | `str` | *required* |
| `livekit_url` | `str` | *required* |
| `participant_token` | `str` | *required* |
| `participant_identity` | `str` | *required* |
| `expires_at` | `datetime` | *required* |

# Enums

- **`ActionResult.status (Status)`**: `done`, `failed`
- **`RuntimeEvent.phase (Phase)`**: `accepted`, `running`, `done`, `failed`, `cancelled`
- **`Language`**: `en`, `vi`, `zh-tw`
- **`Voice`**: `kokoro`, `piper`, `breezy`, `mms`
- **`RobotType`**: `physical`, `virtual`
