Reference
Python
Author robotics controls, record observations, and replay evidence with the tinyrobot Python SDK.
View as MarkdownThe Python SDK supplies author controls, bounded execution, recording, and replay. External libraries still own inference, planning, and robot dynamics. This reference tracks the repository's main branch, not a particular PyPI release.
Install
uv add tinyrobot
Native Rerun recording requires the optional recording extra:
uv add 'tinyrobot[recording]'
Author an app
from tinyrobot import App, Number
app = App("example", title="Example", source="simulation")
@app.command(inputs={"steps": Number(default=10, min=1, max=100, integer=True)})
def run(recording, steps):
for step in range(steps):
recording.checkpoint()
An App declares controls and streams; a Run is supplied to each callback by the runner. Declaring an app does not start a web server or authorize hosted execution.
API reference
- Authoring: App, Stream, Limits, and Run.
- Controls: Number, Choice, MultiChoice, and Toggle.
- Recorded data: Capture, replay, replay_evidence, and evidence_request. Start with the recorded-data guide for reproduction instructions.
- Visualization helpers: depth_points and meters.
- LeRobot data: import_lerobot reads a
bounded numeric subset, not a full dataset or robot-control integration. This
requires
tinyrobot[lerobot,recording].
Signatures and descriptions below are extracted from SDK source with Griffe. They are rendered by the same TypeScript pipeline as these guides and are available through View as Markdown, site search, and /llms.txt.
tinyrobot.App
from tinyrobot import App
tinyrobot.App(id: str, *, title: str, source: str, description: str = '', limits: Limits | None = None, blueprint: Any = None)
A trusted Python author with typed commands, not a script-rerun web server.
Callbacks receive Run then keyword arguments matching their controls. Each
invocation must initialize its own simulator/model state. External libraries
own inference, IK, planning and dynamics. No callback or import path is sent
to clients; hosted execution needs a separately reviewed, pinned recipe.
tinyrobot.App.command
tinyrobot.App.command(*, inputs: Mapping[str, Control] | None = None, id: str | None = None, label: str = '', description: str = '') -> Callable[[Callable[..., None]], Callable[..., None]]
Register a button and its bounded controls. Missing values use defaults.
tinyrobot.App.descriptor
tinyrobot.App.descriptor() -> dict[str, Any]
tinyrobot.App.descriptor_bytes
tinyrobot.App.descriptor_bytes() -> bytes
Freeze declarations and return exact bytes used for descriptorSha256.
tinyrobot.App.digest
tinyrobot.App.digest: str
tinyrobot.App.invoke
tinyrobot.App.invoke(request: object, *, output: str | Path, cancel: threading.Event | None = None) -> dict[str, Any]
Single-flight execution; reject concurrent calls, never queue/retry.
Cancellation is cooperative inside Python. Hosts must terminate hung processes; hosted jobs additionally require provider-confirmed cleanup. Use requestId to ignore stale UI responses after reset/cancel/navigation.
tinyrobot.App.request
tinyrobot.App.request(command: str, *, request_id: str | None = None, **values: Value) -> dict[str, Any]
Make an invocation with validated defaults (no execution).
tinyrobot.App.run
tinyrobot.App.run(command: str, *, output: str | Path, **values: Value) -> dict[str, Any]
Run locally into a NEW directory; never upload or publish anything.
tinyrobot.App.stream
tinyrobot.App.stream(path: str, *, kind: Literal['scalar', 'visual'] = 'visual', role: Role = 'diagnostic', source: str | None = None, unit: str = '', timeline: str = 'step', time_kind: Literal['sequence', 'duration'] = 'sequence') -> Stream
Declare one output; chart-only apps need no robot, camera or geometry.
tinyrobot.app.Role
from tinyrobot.app import Role
type tinyrobot.app.Role = Literal['observed', 'simulated', 'predicted', 'diagnostic', 'action']
tinyrobot.Capture
from tinyrobot import Capture
tinyrobot.Capture(output: str | Path, *, title: str, source: str, target: str = 'unknown', question: str = '', limits: Limits | None = None, kind: str = 'capture', source_format: str = 'native-rerun', source_version: str | None = None, adapter: str | None = None, source_files: list[dict[str, Any]] | None = None, notes: str = '', config: dict[str, Any] | None = None)
Local capture only. Closing stops recording, NEVER the external hardware.
Use as a context manager. Declare clocks/streams before the first log. Native archetypes own data; metadata describes clocks/semantics, not a scene graph. Bounds are cooperative: one native log can allocate before a checkpoint. No App, executable author, inferred action interval, or execution attestation.
tinyrobot.Capture.enter
tinyrobot.Capture.__enter__() -> Capture
tinyrobot.Capture.exit
tinyrobot.Capture.__exit__(exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None
tinyrobot.Capture.checkpoint
tinyrobot.Capture.checkpoint() -> None
tinyrobot.Capture.clock
tinyrobot.Capture.clock(id: str, *, kind: str = 'duration', unit: str = 's', origin: str = 'unknown', source: str | None = None, derivation: str | None = None, synchronization: str = 'unknown', uncertainty_seconds: float | None = None) -> str
tinyrobot.Capture.diagnostic
tinyrobot.Capture.diagnostic(code: str, message: str, *, severity: str = 'warning') -> None
tinyrobot.Capture.finish
tinyrobot.Capture.finish() -> dict[str, Any]
Commit evidence.json last; failed captures are removed, not replayable.
tinyrobot.Capture.log
tinyrobot.Capture.log(stream: str, *archetypes: Any, times: dict[str, float]) -> None
tinyrobot.Capture.metric
tinyrobot.Capture.metric(id: str, value: float | None, *, unit: str | None, method: str, source: str, reason: str | None = None) -> None
tinyrobot.Capture.scalar
tinyrobot.Capture.scalar(stream: str, value: float | None, *, times: dict[str, float]) -> None
None is a native Clear (never zero or a forward-filled measurement).
tinyrobot.Capture.stream
tinyrobot.Capture.stream(path: str, *, role: str = 'unknown', meaning: str = 'unknown', unit: str | None = None, clock: str | None = None, source: str | None = None) -> str
tinyrobot.Choice
from tinyrobot import Choice
tinyrobot.Choice(*, options: tuple[str, ...], default: str, label: str = '', description: str = '')
One value from a fixed allowlist, never a path, model URL or code selector.
tinyrobot.Choice.default
tinyrobot.Choice.default: str
tinyrobot.Choice.description
tinyrobot.Choice.description: str = ''
tinyrobot.Choice.label
tinyrobot.Choice.label: str = ''
tinyrobot.Choice.options
tinyrobot.Choice.options: tuple[str, ...]
tinyrobot.controls.Control
from tinyrobot.controls import Control
type tinyrobot.controls.Control = Number | Choice | MultiChoice | Toggle
tinyrobot.controls.Value
from tinyrobot.controls import Value
type tinyrobot.controls.Value = float | int | str | bool | list[str]
tinyrobot.evidence_request
from tinyrobot import evidence_request
tinyrobot.evidence_request(directory: str | Path, namespace_id: str) -> dict[str, Any]
Build a bounded HTTP import body locally; never upload or claim server trust.
tinyrobot.geometry.depth_points
from tinyrobot.geometry import depth_points
tinyrobot.geometry.depth_points(depth: npt.ArrayLike, image_from_camera: npt.ArrayLike, *, units_per_meter: float = 1000, stride: int = 1) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.int64]]
Project calibrated depth to camera-frame meters and matching (row, col).
Uses integer pixel centers and a 3x3 pinhole intrinsic matrix. Invalid/zero depth is omitted. Coordinates are right/down/forward (RDF); log native Rerun ViewCoordinates/Pinhole/Transform3D when relating this to a world frame. This does not infer calibration, undistort a lens or estimate a camera pose. Native rr.DepthImage(meter=units_per_meter) + rr.Pinhole is preferable when callers only need depth visualization, rather than explicit point arrays.
tinyrobot.geometry.meters
from tinyrobot.geometry import meters
tinyrobot.geometry.meters(value: Any, *, unit: Literal['m', 'cm', 'mm']) -> Any
Convert scalar/array visualization positions once at the boundary.
tinyrobot.lerobot.import_lerobot
from tinyrobot.lerobot import import_lerobot
tinyrobot.lerobot.import_lerobot(directory: str | Path, *, episode: int, output: str | Path, timestamp_policy: str = 'source', title: str = 'LeRobot numeric episode') -> dict[str, Any]
Import one explicit offline episode, never a new hardware trial.
source preserves the source-reported display timeline; nominal_fps
uses a separate derived display clock. Both retain source timestamps/indices
and nulls in native RRD. Neither proves timing, execution or policy quality.
tinyrobot.Limits
from tinyrobot import Limits
tinyrobot.Limits(*, seconds: float = 60, max_samples: int = 10000, max_bytes: int = 16 * 1024 * 1024)
Cooperative bounds; runner process/provider deadlines are the hard backstop.
tinyrobot.Limits.descriptor
tinyrobot.Limits.descriptor() -> dict[str, Any]
tinyrobot.Limits.max_bytes
tinyrobot.Limits.max_bytes: int = 16 * 1024 * 1024
tinyrobot.Limits.max_samples
tinyrobot.Limits.max_samples: int = 10000
tinyrobot.Limits.seconds
tinyrobot.Limits.seconds: float = 60
tinyrobot.MultiChoice
from tinyrobot import MultiChoice
tinyrobot.MultiChoice(*, options: tuple[str, ...], default: tuple[str, ...], min: int = 1, max: int = 64, label: str = '', description: str = '')
A bounded subset of a fixed allowlist; selection order is meaningful.
tinyrobot.MultiChoice.default
tinyrobot.MultiChoice.default: tuple[str, ...]
tinyrobot.MultiChoice.description
tinyrobot.MultiChoice.description: str = ''
tinyrobot.MultiChoice.label
tinyrobot.MultiChoice.label: str = ''
tinyrobot.MultiChoice.max
tinyrobot.MultiChoice.max: int = 64
tinyrobot.MultiChoice.min
tinyrobot.MultiChoice.min: int = 1
tinyrobot.MultiChoice.options
tinyrobot.MultiChoice.options: tuple[str, ...]
tinyrobot.Number
from tinyrobot import Number
tinyrobot.Number(*, default: float, min: float, max: float, step: float = 1, integer: bool = False, unit: str = '', label: str = '', description: str = '')
A finite bounded slider/number input; integer=True preserves Python ints.
tinyrobot.Number.default
tinyrobot.Number.default: float
tinyrobot.Number.description
tinyrobot.Number.description: str = ''
tinyrobot.Number.integer
tinyrobot.Number.integer: bool = False
tinyrobot.Number.label
tinyrobot.Number.label: str = ''
tinyrobot.Number.max
tinyrobot.Number.max: float
tinyrobot.Number.min
tinyrobot.Number.min: float
tinyrobot.Number.step
tinyrobot.Number.step: float = 1
tinyrobot.Number.unit
tinyrobot.Number.unit: str = ''
tinyrobot.replay
from tinyrobot import replay
tinyrobot.replay(directory: str | Path) -> dict[str, Any]
Verify an immutable local bundle without importing the author or Rerun.
Return data only. No callbacks, compute, publication or network access. The returned recording filename is relative to the already trusted directory.
tinyrobot.replay_evidence
from tinyrobot import replay_evidence
tinyrobot.replay_evidence(directory: str | Path) -> dict[str, Any]
Verify a headless bundle, without loading Rerun, an author or a reader.
tinyrobot.Run
from tinyrobot import Run
tinyrobot.Run(app: App, request: dict[str, Any], output: Path, cancel: threading.Event)
One independent command/recording, owned by its callback thread.
with run.at(step): synchronizes outputs without repeating clocks. Stream
declarations supply roles/units/source. Out-of-order samples are valid. Every
native log resets clocks so independent sensors/predictions never inherit time.
checkpoint() should also be called inside long inference/simulation loops.
tinyrobot.Run.at
tinyrobot.Run.at(step: float | None = None, **times: float) -> Iterator[None]
Use step by default, or named sensor clocks: at(elapsed=0.1).
Nested contexts replace rather than inherit clocks. Sequence streams require integers; duration streams use seconds. No wall-clock inference.
tinyrobot.Run.checkpoint
tinyrobot.Run.checkpoint() -> None
Check cancellation, timeout, sample and flushed RRD byte budgets.
tinyrobot.Run.clear
tinyrobot.Run.clear(stream: Stream, *, at: float | None = None) -> None
Explicitly clear stale geometry at a sample time (recursive native Clear).
tinyrobot.Run.log
tinyrobot.Run.log(stream: Stream, *archetypes: Any, at: float | None = None, static: bool = False, interval: tuple[float, float] | None = None) -> None
Log native Rerun geometry/images/transforms/plots. No scene graph DSL.
Actions require the application interval, in the stream's time units.
Predicted streams require a prediction context and target time;
geometry is never silently relabeled as observed or simulated.
tinyrobot.Run.prediction
tinyrobot.Run.prediction(*, origin: float, candidate: str) -> Iterator[None]
Retain forecast origin and horizon separately from target/sample time.
tinyrobot.Run.scalar
tinyrobot.Run.scalar(stream: Stream, value: float, *, at: float | None = None, interval: tuple[float, float] | None = None) -> None
Convenient finite scalar; units, role and source live on the stream.
tinyrobot.Stream
from tinyrobot import Stream
tinyrobot.Stream(path: str, kind: Literal['scalar', 'visual'], role: Role, source: str, unit: str, timeline: str, time_kind: Literal['sequence', 'duration'])
Semantics declared once. Native Rerun archetypes still own visual data.
tinyrobot.Stream.descriptor
tinyrobot.Stream.descriptor() -> dict[str, Any]
tinyrobot.Stream.kind
tinyrobot.Stream.kind: Literal['scalar', 'visual']
tinyrobot.Stream.path
tinyrobot.Stream.path: str
tinyrobot.Stream.role
tinyrobot.Stream.role: Role
tinyrobot.Stream.source
tinyrobot.Stream.source: str
tinyrobot.Stream.time_kind
tinyrobot.Stream.time_kind: Literal['sequence', 'duration']
tinyrobot.Stream.timeline
tinyrobot.Stream.timeline: str
tinyrobot.Stream.unit
tinyrobot.Stream.unit: str
tinyrobot.Toggle
from tinyrobot import Toggle
tinyrobot.Toggle(*, default: bool = False, label: str = '', description: str = '')
tinyrobot.Toggle.default
tinyrobot.Toggle.default: bool = False
tinyrobot.Toggle.description
tinyrobot.Toggle.description: str = ''
tinyrobot.Toggle.label
tinyrobot.Toggle.label: str = ''