# tinyrobot > A tiny start for something useful. Documentation, project notes, and changelog updates for tinyrobot. These files contain published, repository-authored content. Append .md to a documentation, blog, or changelog entry URL to read its Markdown source. ## Documentation - [Introduction](http://tinyro.bot/docs/introduction.md): A quick look at tinyrobot and how the pieces fit together. - [Quickstart](http://tinyro.bot/docs/quickstart.md): Get the development server running on your machine. - [Use cases](http://tinyro.bot/docs/use-cases.md): Inspect a recording, compare a change, or try a model. Start with the question you are trying to answer. - [Project structure](http://tinyro.bot/docs/project-structure.md): Know where routes, shared UI, and content belong. - [Writing content](http://tinyro.bot/docs/writing-content.md): Add a guide, blog post, or changelog update without creating a new route. - [Printer monitoring](http://tinyro.bot/docs/printer.md): Connect a local Bambu LAN bridge for private, read-only telemetry. Setup, TLS trust, namespace access, and honest freshness states. - [Inspect and compare recorded data](http://tinyro.bot/docs/recorded-data.md): A small offline example for finding a timing problem without changing a model. - [TypeScript](http://tinyro.bot/docs/reference/ts.md): JavaScript and TypeScript SDK public API reference, generated with TypeDoc. - [Python](http://tinyro.bot/docs/reference/py.md): Author robotics controls, record observations, and replay evidence with the tinyrobot Python SDK. --- # Introduction > A quick look at tinyrobot and how the pieces fit together. Source: http://tinyro.bot/docs/introduction tinyrobot is a small starting point for building something useful. The cloud website brings together public content, community experiments, and an application scaffold. Public pages share navigation, typography, and layout; the self-hosted deployment includes only the application. ## Find your way around - **Home** introduces the project and points you to the next step. - **Docs** explain the setup and the conventions behind it. - **Blog** is a place for project notes and updates. - **Changelog** records project changes. - **Community** hosts curated browser experiments, with login still disabled. - **App** is the shared client-rendered application scaffold. ## The foundation Both deployments use **Node.js** and **TanStack Start** for server rendering and file-based routing, with **Vite** for development and builds. The shared backend uses **Effect**, **PostgreSQL**, and **Better Auth**. Website hosting migration and live deployment remain deferred; analytics and GitHub integrations are separate Cloudflare Workers, not website hosting. Shared UI lives in its own workspace. Documentation and blog posts are Markdown files, validated and compiled by **Content Collections** during development and builds. There is no separate content server to run. ## Start building [Run the project locally](/docs/quickstart), then take a look at the [project structure](/docs/project-structure). --- # Quickstart > Get the development server running on your machine. Source: http://tinyro.bot/docs/quickstart ## Install the tools Use the versions pinned in `.tool-versions`. The repository also includes `.nvmrc` and `.python-version` for Node and Python version managers. You will need Node.js, pnpm, and PostgreSQL for the web app. Docker Compose can supply the local database; an existing PostgreSQL server also works. Python and uv are only needed for the Python SDK and robotics checks. With nvm, load `~/.nvm/nvm.sh` and run `nvm install && nvm use`; enable the pinned pnpm with `corepack enable`. ## Install dependencies From the repository root, install the web workspace: ```sh ./scripts/setup.sh web pnpm test:install ``` To install both the web and Python workspaces, omit `web`. ## Migrate and start the app ```sh docker compose up --detach --wait postgres export DATABASE_URL=postgresql://tinyrobot:tinyrobot@127.0.0.1:5432/tinyrobot pnpm --filter @tinyrobot/server-cloud db:migrate pnpm --filter @tinyrobot/server-cloud catalog:publish pnpm dev ``` Open **http://localhost:3000**. Vite watches the app and Content Collections watches the Markdown files. The Node/Start server handles the backend too; there is no separate Python API process or frontend proxy to start. Migration is explicit, not an automatic side effect of startup. The app-only self-hosted web deployment is removed for now. An unset development `BETTER_AUTH_URL` defaults to `http://localhost:3000`; if you use a different listener port, set the matching auth origin explicitly. The Compose credentials above are public, local-only development values. Signup and authentication methods are disabled by default; community login stays an unconfigured scaffold even when backend auth is configured. Production requires an explicit PostgreSQL URL, stable secret, and HTTPS auth origin. See the [backend configuration and enrollment guide](https://github.com/yujonglee/tinyrobot/blob/main/server/core/README.md) before enabling authentication or exposing an instance. ## Check your changes ```sh ./scripts/format.sh web ./scripts/check.sh web ``` Keep PostgreSQL running for the backend, HTTP and browser suites. Tests create and drop uniquely named disposable databases, not the application database. For a separate test server, set `TEST_DATABASE_URL` with a `CREATEDB` role; never use production. The check script runs generation/drift checks, lint, formatting checks, Node tests, Vitest browser tests, Playwright E2E tests, Node website builds, and TypeScript checks. Only standalone Workers such as analytics and GitHub use Wrangler dry-runs; nothing deploys. Run `pnpm test:install` again after Playwright version changes; Linux machines may also need `pnpm exec playwright install --with-deps chromium` for browser system libraries. Use `make check` for full local validation of both ecosystems, MinIO storage, the cloud Compose app/auth/migration/catalog/restart smoke, and HTTP/Query/SSE browser integration against isolated fixtures. It requires Docker and owns a disposable PostgreSQL service unless `TEST_DATABASE_URL` is set. Pass `python` to the setup/check scripts for Node-free Python checks; web checks do not require uv. There are no automatic validation or preview workflows. Railway deploys main automatically without waiting for CI, so run the full check before merging. Next, learn [where things live](/docs/project-structure). --- # Use cases > Inspect a recording, compare a change, or try a model. Start with the question you are trying to answer. Source: http://tinyro.bot/docs/use-cases ## Inspect — Why did that run look wrong? Look through recorded states, actions, and timing. Find a data problem before changing your model. [Follow a small example](/docs/recorded-data): an offline walkthrough using synthetic numeric data. It keeps the source timestamps, missing samples, and limitations visible. A cleaner display is not proof that a robot task succeeded. ## Compare — What did my change do? Put two recordings side by side. Compare the evidence and keep a note of what to try next. [Compare recordings](/evidence) in the private workspace. Sign in and select a namespace, import your records, and choose an explicit alignment. Inspect the evidence before interpreting a delta; incompatible alignment suppresses metric comparisons. Imported recordings are not attested tinyrobot executions. ## Explore — How does this model work? Try a small robotics demo. Inspect its behavior and the code behind it, without setting up a robot. [Explore experiments](/community): browser demos and recorded runs, not robot control. Each experiment explains its own execution mode and limits. Illustrations are not screenshots, recordings, or proof of physical hardware performance. ## Monitor — What is my printer reporting? [Read the printer setup guide](/docs/printer) for private, read-only Bambu LAN monitoring. A local connector reports supported status and temperature fields; missing values stay unknown. Hardware support is unverified. This does not add camera access, printer controls, or automatic intervention. Keep your tools. Bring the recordings and results here. [Read the docs](/docs) for setup and the boundaries of each workflow. --- # Project structure > Know where routes, shared UI, and content belong. Source: http://tinyro.bot/docs/project-structure ## A small monorepo ```text api/ models/ Authored JSON Schema wire models generated/ Generated self/cloud OpenAPI snapshots server/ core/ Shared Effect services, PostgreSQL and Better Auth cloud/ Cloud-only backend extensions web/ web-deploy-cloud/ Node/Start: shared app + cloud-only features src/document.tsx Cloud HTML document and theme src/public-shell.tsx Public navigation, search, account and footer composition src/routes/_site.tsx Public shell route src/routeTree.gen.ts Cloud-generated route tree src/server.ts Backend-first Start server entry src/routeTree.gen.ts Self-generated route tree Dockerfile Multi-stage app-only image web-router/ src/vite.ts Shared virtual-route/Vite composition src/backend.server.ts Shared backend lifecycle adapter web-app/ src/routes/ Application routes (starts at /app) web-community/ src/experiments.ts Static gallery metadata; no model imports src/demo/ Shared checked, cancellable browser runtime src/routes/ Cloud-only gallery, experiments and disabled login web-og/ src/routes/ Cloud-only OG image route web-status/ src/routes/ Cloud-only status page scaffold web-shell/ src/site-shell.tsx Shared runtime shell for cloud public pages and /app web-landing/ src/routes/ Cloud-only homepage route and metadata src/home/ Session-aware SSR homepage web-content/ src/routes/ Docs/blog/changelog metadata and Markdown endpoints src/docs/ Documentation screens src/blog/ Blog screens src/changelog/ Changelog screens and styles src/llms.ts LLM indexes over published content src/markdown.ts Shared Markdown renderer src/content.ts Typed content selectors content/docs/ Documentation in Markdown content/blog/ Blog posts in Markdown content/changelog/ Changelog updates in Markdown content-collections.ts Content schemas and transforms api/ Generated fetch client; public @tinyrobot/api entry ui/ src/components/ Shared UI and public proxies src/generated/ Upstream-owned components analytics/ Standalone Cloudflare analytics Worker github/ Standalone Cloudflare GitHub App Worker sdk/py/ Python SDK (PyPI: tinyrobot) sdk/js/ JavaScript/TypeScript SDK (npm: tinyrobot) experiments/ Isolated tinygrad training/export projects and demo kit ``` ## Routes and layouts Features own route files, loaders, not-found decisions and metadata. `web/web-router` uses virtual routes to compose physical feature directories. Each deployment owns its root route, router factory and generated `src/routeTree.gen.ts`. `web/web-content` owns docs (including printer monitoring and use cases), blog, changelog, and their shared Markdown/LLM pipeline. `web/web-landing` owns the cloud-only homepage. Public pages nest under the cloud deployment's `_site` route, which supplies navigation, search, account controls, and footer to `web/web-shell`; `/app` and `/printers` use that same shell in compact mode in cloud. `/llms.txt` and `/llms-full.txt` use the published content pipeline. `web/web-app` contains the client-rendered application at `/app` and the private `/printers` monitor. `web/web-community` owns the trusted `/community/` gallery and experiments; `/demos/cartpole` permanently redirects to `/community/cartpole`. `web/web-og` owns the `/og` PNG endpoint, and `web/web-status` owns the `/status` scaffold. Features use TanStack Router directly. Cloud mounts public and shared features; the app-only self-hosted web deployment is removed for now. Landing/public content/community/analytics/OG/status/invite and the cloud backend are excluded from the shared shell and self-hosting, not merely hidden in navigation. Each deployment has a separate TypeScript program and router registration. Never import both generated route trees into one program. Feature-only checks use generated type-only route declarations. Run the owning deployment's `generate` command after route changes; `pnpm routes:check` checks both trees and declarations without rewriting sources or outputs, before full/affected checks can regenerate them. ## Browser experiment library Visit [Community](/community) for CartPole, two-link reaching, action-chunk cube pushing, trajectory denoising, pendulum control, handwritten digits and visual target following, plus a curated Hugging Face Space. Local models run only after Start, using WebGPU or explicitly selected JavaScript CPU. The Space is external and can process inputs remotely. The [contributor experiment index](https://github.com/yujonglee/tinyrobot/blob/main/experiments/README.md) links every method, dataset, release gate and reproducible training/export command. Robotics scenes are synthetic teaching tasks, not hardware-ready controllers; MNIST results do not guarantee arbitrary handwriting recognition. Training is isolated Python, never a dependency of web builds or a server inference service. Splat routes support nested content paths, so `web/web-content/content/docs/guides/example.md` becomes `/docs/guides/example`, with its Markdown counterpart at `/docs/guides/example.md`. ## Backend and generated contracts The cloud Node deployment sends `/health`, `/openapi.json`, `/api` and `/api/*` to the backend before TanStack Start, including JSON 404s for unknown API paths. Core owns shared auth and Effect resource lifetimes; cloud composes extensions without leaking them into self. Better Auth's supported Promise/pg adapter and native Effect PostgreSQL queries share the database and generated schema, but use separate pools and transaction scopes. Author wire models in `api/models`, then run `pnpm api:generate` in core → cloud → web client order. Import web clients only through `@tinyrobot/api`; never edit generated models, OpenAPI or client files. See the [backend guide](https://github.com/yujonglee/tinyrobot/blob/main/server/core/README.md) and [API contract guide](https://github.com/yujonglee/tinyrobot/blob/main/api/README.md). Python serves SDK and robotics work, not HTTP. The SDKs release independently through `py-vX.Y.Z` and `js-vX.Y.Z` tags; web checks need no uv and Python checks need no Node. Nx orchestrates local tasks only, with no remote cache. Website hosting migration and community login activation remain deferred; standalone Workers are not the website host. ## Shared UI Generic slot-based layout components and UI proxies belong in `web/ui/src/components`. Runtime shell behavior lives in `web/web-shell`; navigation and optional footer/search/account controls are composed by callers. Cloud owns its public composition without adding public routes or dependencies to self-hosting. Use public imports such as `@tinyrobot/ui/components/button`. Generated shadcn components use Base UI and are wrapped by public proxies. Never edit upstream-owned files in `web/ui/src/generated` directly. ## Framework conventions This follows [TanStack Start's routing conventions](https://tanstack.com/start/latest/docs/framework/react/guide/routing): file-based routes, nested layouts, typed links, route loaders, and route-level metadata. TanStack Router's ESLint plugin runs inside **oxlint** through JavaScript plugins for route property ordering and route parameter names. TanStack Start's server/client rules are deferred because they require type-aware parser services that Oxlint's JavaScript plugin API does not provide. There is no separate ESLint command. --- # Writing content > Add a guide, blog post, or changelog update without creating a new route. Source: http://tinyro.bot/docs/writing-content Content Collections turns local Markdown into typed data. Its Vite adapter handles generation during development and production builds. ## Add a documentation page Create a Markdown file under `web/web-content/content/docs`: ```yaml --- title: Your guide description: A short explanation of what this guide covers. section: Guides order: 3 --- ``` Write the body below the frontmatter. The file path becomes the URL, and the page appears automatically in the sidebar and docs overview. Use `Getting started` or `Guides` for the section. The `order` field controls ordering within that section. ## Add a blog post Create a file under `web/web-content/content/blog`: ```yaml --- title: A new note description: A short summary for the blog index. date: '2026-10-16' author: tinyrobot draft: false category: Field notes artwork: recognition sample: false --- ``` Quote dates and use the YYYY-MM-DD format. Posts are listed newest first. Set `draft: true` to hide a post from both the index and its public route. Draft content is still generated locally; it is not a place to store secrets. `artwork` selects a reviewed illustration from `@tinyrobot/artwork`, used on the index and article page. It defaults to `small-steps`; examples include `recognition`, `denoising`, and `robot-companion`. These are illustrations, not experiment outputs. `category` defaults to `Notebook`. Set `sample: true` for visible mock posts: cards and articles label them as samples. Also identify sample content in the Markdown body so that Markdown and LLM readers see the disclosure. ## Add a changelog update Create a file under `web/web-content/content/changelog`: ```yaml --- title: A small improvement description: A short summary of what changed. date: '2026-10-16' draft: false --- ``` Write the changes below the frontmatter using headings and lists. The file path becomes the URL: `a-small-improvement.md` is served at `/changelog/a-small-improvement`. Nested paths work too. Updates are listed newest first, with file paths breaking ties on the same date. `draft` defaults to `false`; set it to `true` to hide an update from both the index and its public route. As with blog drafts, generated content is not a place to store secrets. Changelog routes and screens live alongside home, docs, blog, and the Markdown pipeline in `web/web-content`, included only in the cloud deployment. Adding an entry does not require adding a route file. ## Write for the reader The title is rendered by the page layout, so start body sections with second-level headings. Standard Markdown supports links, lists, quotes, images, and fenced code blocks. Use site paths for internal links, such as `/docs/quickstart`. Raw HTML is disabled during Markdown compilation. Only repository-authored Markdown is supported; this is not an upload or user-content pipeline. ### Code highlighting Add a language after a code fence, such as `sh`, `ts`, `python`, `json`, or `yaml`. [Shiki](https://shiki.style/) highlights fenced blocks during content generation for docs, blog posts, and changelog entries; no highlighter runs in the reader's browser. Unlabeled blocks, `text`, and unknown languages render as plain text. Inline code keeps its existing styling. Syntax colors follow the site's light/dark mode through shared UI theme tokens. Highlighting changes only the HTML; Markdown and LLM endpoints keep the original code fences and source text. ## Markdown for agents Every published doc, blog post, and changelog entry also has a Markdown URL: append `.md` to its page URL, for example `/docs/quickstart.md`. Nested paths work the same way. The response includes the title, description, source URL, publication metadata when available, and the original Markdown body without frontmatter or the site shell. Use the **View as Markdown** link on each entry, or start at [/llms.txt](/llms.txt) for a grouped index. [/llms-full.txt](/llms-full.txt) includes the full text of every published entry. Both update from the same Content Collections data as the website; there is no second copy to maintain. Blog and changelog drafts are excluded from all three forms, and missing Markdown URLs return 404. These endpoints belong to the cloud website, not the app-only self-hosted deployment. Marketing, index, application, and status pages are not authored content entries and do not have `.md` counterparts. ## Generated content ### Python API reference Edit Python SDK signatures and Markdown docstrings in `sdk/py/src/tinyrobot`, then run: ```sh uv run --locked python scripts/generate_python_docs.py pnpm --filter @tinyrobot/web-content generate ``` Griffe statically extracts the package's `__all__` exports and the explicit additional public symbols in `scripts/generate_python_docs.py`. It does not import the SDK or require optional robotics dependencies. Generated `content/generated/python-api/*.json` files are committed and must never be edited by hand. Removed exports are removed from this directory on regeneration. Content Collections validates those JSON files, converts them to reference Markdown in TypeScript, and compiles them with Shiki. All symbols are appended to the single Python reference page, with in-page heading links. That complete page joins navigation, search, Markdown responses, and LLM exports. The cloud build needs only Node; Python validation needs no Node. SDK checks include `uv run --locked python scripts/generate_python_docs.py --check` to reject missing, changed, or obsolete metadata. Nx exposes the same tasks as `tinyrobot-py:docs:generate` and `tinyrobot-py:docs:check`. Use ordinary Markdown docstrings (including fenced Python examples). This small adapter does not interpret Sphinx roles or generate versioned release docs. ### TypeScript API reference The [TypeScript SDK reference](/docs/reference/ts) is generated from public exports in `sdk/js/src/index.ts` with TypeDoc and its Markdown plugin. The [Python SDK reference](/docs/reference/py) each occupy one page and one item under the sidebar's Reference section. Symbols use in-page anchors, not separate pages. Both complete pages appear in search, Markdown responses, and LLM exports. After editing TypeScript exports or JSDoc, run: ```sh pnpm --filter tinyrobot docs:generate pnpm --filter @tinyrobot/web-content generate ``` TypeDoc's Markdown plugin handles overloads, generics, types, and JSDoc. The exporter uses the module router to store a single normalized reference page in `content/generated/typescript-api/*.json`. These are committed build inputs, not rendered HTML. Generated metadata for both languages lives under `content/generated/`; regenerate it rather than editing it manually. The compiled `.content-collections` output remains ignored. `pnpm --filter tinyrobot docs:check` rejects missing, changed, or obsolete metadata and also runs during SDK tests and Nx `generate:check` validation. The library currently exports no public symbols, so its landing page says so; CLI internals are not treated as library APIs. Future exports automatically appear on that same page. Web builds consume the committed metadata and do not run Griffe or TypeDoc. ### Build artifacts Do not edit `web/web-content/.content-collections` or deployment `src/routeTree.gen.ts` files and `web/web-router/src/*-routeTypes.gen.d.ts`. Content Collections and the TanStack Start plugin regenerate these files. Run `pnpm --filter @tinyrobot/web-content generate` before a standalone typecheck when generated data is absent. For more options, see the [Content Collections documentation](https://www.content-collections.dev/docs). --- # Printer monitoring > Connect a local Bambu LAN bridge for private, read-only telemetry. Setup, TLS trust, namespace access, and honest freshness states. Source: http://tinyro.bot/docs/printer tinyrobot can retain the **current** normalized printer state in a private namespace. Open [Printers](/printers) from [your account](/app) to connect and inspect it. A computer or NAS on the printer's LAN runs an outbound connector. No inbound port forwarding is needed. **Hardware support is unverified.** The exact printer model and firmware have not been established or physically tested. This is not a hardware compatibility certification. Confirm LAN status availability and authenticated TLS trust for your printer before relying on the connector. Never use this page as a safety monitor or leave a printer unattended because a status appears healthy. ## Current capabilities | Capability | This slice | | -------------------------------------- | ---------------------------------------------------------------------------------------- | | Status | Idle, preparing, printing, paused, completed, or failed when reported; otherwise unknown | | Progress | Reported percentage only; never inferred from status | | ETA | Reported remaining duration only; not a guaranteed finish time | | Temperatures | Nozzle and bed when valid readings are present | | Freshness | Separate connector heartbeat and printer observation ages | | Camera | Unsupported; no images, streaming, or camera credentials | | Printer controls / G-code | Unsupported; subscribe-only MQTT, no command publication | | History / notifications / AI detection | Not implemented | | Automatic intervention | Not implemented; no pause, cancel, or safety action | Values can be partial. Each supported MQTT update replaces the current sample; fields absent from that update become **unknown**, not silently carried forward. Unknown status names are not guessed. Reconnects and detected print changes clear the previous sample. There is no print history or raw MQTT payload archive. ## Hosted and self-hosted topology ```text Printer on LAN -- authenticated TLS / status subscription --> LAN computer or NAS | outbound HTTPS only | tinyrobot server | private /printers page ``` This diagram is illustrative, not live telemetry or evidence of tested hardware. Hosted tinyro.bot cannot connect directly to a private LAN. Keep the connector running on a trusted computer or NAS on that LAN. A local self-hosted tinyrobot uses the **same connector**; it may run on the same machine as the server, but is still a separate process. The public documentation lives on the cloud site and is not bundled into the self-hosted application. ## Before you start 1. Install the repository's pinned Node.js and pnpm versions and run `./scripts/setup.sh web` from a checked-out repository. The connector is a source workspace package, not a promised published SDK or hosted relay. With [nvm](https://github.com/nvm-sh/nvm) already installed, run `nvm install`, `nvm use`, and `corepack enable` from the repository root before setup. No Python, Docker, or local PostgreSQL is needed for the connector alone. Run the `pnpm --filter` commands below from that same repository root. 2. Know your own printer's LAN address, serial number, and LAN access code. Read them from your printer's supported local settings or vendor documentation for its model and firmware. Do not use or scrape Bambu Cloud account credentials. 3. Establish certificate trust as described below. If you cannot authenticate the printer, **stop**. Do not bypass TLS checks to complete setup. 4. Sign into your tinyrobot deployment and select the intended namespace on [Printers](/printers). Members can read; owners and admins can create, re-pair, and revoke. The server checks current membership on every read and checks owner/admin rights on every mutation. ## Configure the local connector Create a private directory on the connector machine, owned by the account that will run it. On Linux/macOS, use `chmod 700` for the directory and `chmod 600` for the config file. Windows and network filesystems are unverified and unsupported in this slice; do not bypass the connector's permission checks. Keep config, certificate material, and state out of source control, shared folders, backups accessible to others, and support logs. Example local config (replace every placeholder locally; these are not working credentials). All file paths must be absolute: ```json { "origin": "https://tinyro.bot", "stateDirectory": "/absolute/private/state", "printer": { "host": "192.168.1.50", "port": 8883, "serial": "PRINTER_SERIAL", "accessCode": "LAN_CODE", "tls": { "certificateFile": "/absolute/private/printer.pem", "fingerprint256": "REPLACE_WITH_64_LOWERCASE_HEX_SHA256_DIGEST" } } } ``` The LAN access code is the printer's eight-character alphanumeric code. It and the serial stay on the LAN machine; tinyrobot receives neither. Use a plain server origin, with no path, query string, userinfo, or token. Non-loopback connections require HTTPS. HTTP is accepted only for `localhost`, `127.0.0.1`, or `[::1]` on the connector machine, not an arbitrary LAN server. For a self-hosted server on another machine, configure HTTPS. Credential-bearing requests reject redirects rather than forwarding credentials to another URL. ### Authenticate the printer with TLS Choose one of the two supported trust configurations: - **Exact certificate pin:** `certificateFile` is a PEM certificate from a trusted source; `fingerprint256` is its independently verified SHA-256 digest of the DER certificate, exactly 64 lowercase hexadecimal characters without colons. The connector checks the live peer against that pin before sending MQTT credentials. Certificate validity and chain checks remain enabled. - **Trusted CA:** replace the entire `tls` object with the following. Supply a trusted CA PEM and the printer certificate's DNS identity, not an arbitrary display name: ```json { "caFile": "/absolute/private/trusted-ca.pem", "serverName": "printer-certificate-dns-name" } ``` For a PEM already obtained from a trusted source, a local fingerprint can be computed without printing credentials: ```sh openssl x509 -in /absolute/private/printer.pem -outform DER | openssl dgst -sha256 ``` Use only the final hexadecimal digest, lowercased. Computing a hash does **not** establish trust. A certificate retrieved from the network, even on an isolated or direct LAN, is only a candidate until compared with an independent trusted reference. There is no automatic trust-on-first-use, no silent acceptance of a self-signed certificate, and no global TLS-verification disable switch. We do not yet have model-specific validated certificate provisioning instructions. If your hardware cannot provide a trusted reference or valid certificate, setup is blocked rather than weakened. Firmware or certificate changes may require re-establishing trust; do not blindly replace a mismatched pin. ## Create, claim, and start 1. On [Printers](/printers), explicitly select your namespace. An owner/admin enters a human-readable printer name and chooses **Create pairing code**. Do not enter the printer password, access code, serial, or IP in the label. 2. The one-use pairing code is shown only at issuance and expires in ten minutes. It is a tinyrobot pairing secret, **not** the printer access code. Keep it out of URLs, command arguments, shell history, screenshots, logs, and chats. 3. From the repository root on the LAN machine, claim it: ```sh pnpm --filter @tinyrobot/printer-bridge claim --config /absolute/private/config.json ``` Paste the code into the **hidden interactive prompt**. A successful claim consumes it and stores a narrow device credential bound to that printer and namespace, never a user's login session. The server retains secret hashes; the credential and next sequence are stored atomically in the private local state directory. Only one process may own that state directory. Do not copy the state to a second running connector or reset its sequence to replay old observations. 4. Start the connector: ```sh pnpm --filter @tinyrobot/printer-bridge start --config /absolute/private/config.json ``` Keep the process running. It subscribes to printer status, never publishes printer control/request messages, and opens no inbound service. It sends bounded normalized samples no more frequently than every five seconds and independent heartbeats every fifteen seconds. Shutdown releases the local connection and state lock; stopping the process does not itself revoke the device credential. Transient HTTP failures stop after five attempts. MQTT permits the initial session plus four reconnects; trust failures stop immediately. Investigate the cause before restarting rather than configuring an infinite restart loop. 5. Return to the selected namespace in the app. It refreshes every fifteen seconds while visible. Requests and timers stop when the page is left or hidden; private state and pairing codes are cleared on namespace changes, sign-out, and access loss. Reopen or refresh to check current authorization. ### Re-pair or revoke **New pairing code** requires confirmation: it immediately revokes the previous device credential, replaces any pending code, and clears retained readings. Stop the old local connector and claim the new code using the same commands. An expired or consumed code cannot be recovered; generate a new one. **Revoke connector** invalidates both the active device credential and pending pairing code, and clears current readings. Stop the process on the LAN computer too. Revocation does not change your printer's LAN access code or account settings. Repeated revocation is safe. Printer records are retained, with limits of 25 per namespace and 1,000 per deployment; this slice has no deletion UI. ## Read the two clocks separately | State | Meaning | | ------- | ----------------------------------------------------------------------------------- | | Unknown | Not paired, no heartbeat yet, or a live connector without a retained printer sample | | Fresh | Live connector with valid printer telemetry no older than 90 seconds | | Stale | Live connector, but retained printer data older than 90 seconds | | Offline | Revoked, explicitly disconnected, or heartbeat older than 90 seconds | **Bridge heartbeat** measures server acceptance of connector messages. **Printer telemetry** measures when the connector actually received a supported printer update. A heartbeat, reconnect, or disconnect cannot make old printer data fresh. Ages use the server's time reference, not the browser's wall clock. Stale/offline readings, if retained, are explicitly last known values. Unknown is not zero, idle, complete, or safe. ETA is the last reported remaining duration, not a countdown inferred by tinyrobot. ## Troubleshooting and limits - **No namespaces or access denied:** select a namespace you currently belong to. Only an owner/admin can manage connectors. Membership removal hides telemetry; a device credential cannot log into the browser app. - **Pairing expired/used or interrupted claim:** request a new pairing code. The old code cannot be reused. Do not paste either secret into an issue. - **State directory locked:** stop the existing connector before claiming or re-pairing. After an unclean crash, verify that no process uses the state directory, then remove only its empty `lock` directory. Never restore old sequence snapshots, delete credential state to force a retry, or clone it to another process. - **Diagnostics refused:** the CLI refuses `DEBUG`, `NODE_DEBUG`, and `NODE_DEBUG_NATIVE` diagnostics to avoid leaking credentials. Do not enable verbose protocol logging or send private config/state files to support. - **TLS rejected:** verify the trusted reference, hostname (CA mode), expiry, certificate chain, and independently verified pin. Never disable verification. - **Connected but unknown:** status pushes may omit supported fields or have not arrived. The bridge does not request a full snapshot or invent values. - **Stale with recent heartbeats:** the LAN process is alive, but no fresh supported printer observation has arrived. Check the printer and LAN path. - **Offline:** check process power, network reachability, MQTT connection, and whether the connector was revoked. Cloud reachability does not prove LAN health. - **Rejected observations:** keep the connector machine's clock synchronized. Samples older than 30 seconds or more than five seconds in the future are rejected; retries do not re-date old data. Replay/regressing sequences fail. - **Too many requests:** respect the displayed retry delay. Repeatedly creating codes or refreshing does not speed up telemetry. Storage/unavailable responses hide data rather than presenting a successful update. - **Changed print or reconnect:** previous values are cleared. Missing fields remain unknown until a supported update includes them. ## Bambu integration facts and protocol references [Bambu's official third-party integration explanation](https://wiki.bambulab.com/en/software/third-party-integration) states that MQTT **status pushes** are exempt from its authorization changes. Camera access is separate and is not part of this connector. Developer Mode is **not a blanket prerequisite** for status monitoring; it disables Bambu Cloud, so do not turn it on as a default setup step. Availability still depends on the actual model, firmware, and network settings. Bambu Handy is free. Paid OctoEverywhere service is optional and is not required for this local connector. tinyrobot makes no promise of access to, or rights to redistribute, Bambu's SDK. Undocumented MQTT mapping was checked against the established ha-bambulab integration at commit `0e027ff135a6d9265cb756d3e246747954c76722` as a protocol reference, not copied implementation code: - [bambu_client.py](https://github.com/greghesp/ha-bambulab/blob/0e027ff135a6d9265cb756d3e246747954c76722/custom_components/bambu_lab/pybambu/bambu_client.py): MQTT user `bblp`, port 8883, and report subscription. - [models.py](https://github.com/greghesp/ha-bambulab/blob/0e027ff135a6d9265cb756d3e246747954c76722/custom_components/bambu_lab/pybambu/models.py): `mc_percent`, `mc_remaining_time` in minutes, `nozzle_temper`, `bed_temper`, `gcode_state`, and local print identity handling. - [const.py](https://github.com/greghesp/ha-bambulab/blob/0e027ff135a6d9265cb756d3e246747954c76722/custom_components/bambu_lab/pybambu/const.py): reported status strings. These references are not physical testing or a supported-model list. Camera, controls, history, alerts, and AI detection are outside this implementation, not advertised as working or promised future features. --- # Inspect and compare recorded data > A small offline example for finding a timing problem without changing a model. Source: http://tinyro.bot/docs/recorded-data A recording looks mistimed. Is the display hiding a problem in the data? This example changes one display setting, then compares the results. It uses four synthetic frames, not a physical robot trial. No retraining is needed. ## Before you start Use a repository checkout with Python and uv from the [quickstart](/docs/quickstart). The offline command below writes local files; it does not upload data or contact a robot. Private review requires sign-in, a namespace, and a deployment with evidence storage configured. The LeRobot adapter supports a bounded numeric subset of local v2.1 episodes: states, actions, frame indices, and timestamps. It does not import camera/video files or live streams. See the [SDK guide](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/README.md) for capture, supported inputs, and limits. ## Generate two recordings From the repository root, use a new output directory under an existing private parent: ```sh PYTHONPATH=sdk/py uv run --locked --package tinyrobot \ --extra recording --extra lerobot \ python -m examples.reprocess_evidence ./timing-review ``` This writes the synthetic source, two evidence bundles, and two import requests. Protect the request files like the recordings: they contain the evidence data. [Read the pinned example source](https://github.com/yujonglee/tinyrobot/blob/df0bd77f965e0fdea42ecdc955ecdc82cbbe815c/sdk/py/examples/reprocess_evidence.py). The baseline displays frames at nominal 10 FPS. The candidate uses `timestamp_policy="source"` instead of `"nominal_fps"`. Source bytes, frame IDs, state/action values, and missing samples stay the same. No policy, checkpoint, or controller changes. ## Inspect the timing Both recordings refer to the same four frames. Rounded timestamps in seconds: | Frame | Source | Baseline: nominal FPS | Candidate: source time | | ----- | ------ | --------------------- | ---------------------- | | 0 | 0.00 | 0.00 | 0.00 | | 1 | 0.10 | 0.10 | 0.10 | | 2 | 0.35 | 0.20 | 0.35 | | 3 | 0.30 | 0.30 | 0.30 | Frame 2 appears at 0.20 s in the baseline, but its source timestamp is 0.35 s. The source also goes backward at frame 3. The candidate preserves that defect rather than smoothing it away. ## Compare privately 1. Open the [evidence workspace](/evidence), sign in, and select your namespace. 2. Import `baseline.request.json`, then `candidate.request.json`. The workspace assigns your selected namespace; the request's namespace ID is a placeholder. 3. Select the two records and describe the intended display-timing change. 4. Choose `frame_index` alignment, using each record's `frame_index` clock. 5. Save the comparison. Its URL can be reopened for review. Both records remain imported evidence, not attested tinyrobot executions. Matching frame IDs are an assertion for this same-source example, not proof of sensor synchronization. Without compatible alignment, metric deltas are suppressed. Native Rerun recordings open independently; they are not synchronized playback. ## What the result means The maximum absolute display/source timestamp error falls from approximately 0.15 s to 0 s. Keep the source-time display for inspection and investigate the nonmonotonic source clock separately. Source hashes and missing samples are retained. Zero display error does not repair the source clock or prove sensor synchronization, command execution, or robot/task success. Generic action values do not prove which commands reached a robot. New actions still require a simulator rollout or a controlled physical trial; replay is not a test of what different actions would do. A useful decision note: **Keep the source-time display. Investigate the source clock next. Task outcome remains unknown.** --- # TypeScript > JavaScript and TypeScript SDK public API reference, generated with TypeDoc. Source: http://tinyro.bot/docs/reference/ts Generated from the TypeScript SDK source. This reference tracks the repository’s main branch; it is not a versioned release reference. [View public entry point](https://github.com/yujonglee/tinyrobot/blob/main/sdk/js/src/index.ts) The library currently exports no public API symbols. Its entry point is an empty scaffold: ```ts export {} ``` The `tinyrobot` CLI is separate from the library API. Its demo commands are offline mocks, not hosted execution. Run `npx tinyrobot --help` after a release containing the CLI is published. Public library exports will appear here automatically when added to the SDK. --- # Python > Author robotics controls, record observations, and replay evidence with the tinyrobot Python SDK. Source: http://tinyro.bot/docs/reference/py The 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 ```sh uv add tinyrobot ``` Native Rerun recording requires the optional recording extra: ```sh uv add 'tinyrobot[recording]' ``` ## Author an app ```python 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](#tinyrobotapp) declares controls and streams; a [Run](#tinyrobotrun) 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](#tinyrobotapp), [Stream](#tinyrobotstream), [Limits](#tinyrobotlimits), and [Run](#tinyrobotrun). - **Controls:** [Number](#tinyrobotnumber), [Choice](#tinyrobotchoice), [MultiChoice](#tinyrobotmultichoice), and [Toggle](#tinyrobottoggle). - **Recorded data:** [Capture](#tinyrobotcapture), [replay](#tinyrobotreplay), [replay_evidence](#tinyrobotreplay_evidence), and [evidence_request](#tinyrobotevidence_request). Start with the [recorded-data guide](/docs/recorded-data) for reproduction instructions. - **Visualization helpers:** [depth_points](#tinyrobotgeometrydepth_points) and [meters](#tinyrobotgeometrymeters). - **LeRobot data:** [import_lerobot](#tinyrobotlerobotimport_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](https://mkdocstrings.github.io/griffe/). They are rendered by the same TypeScript pipeline as these guides and are available through **View as Markdown**, site search, and [/llms.txt](/llms.txt). ## tinyrobot.App ```python from tinyrobot import App ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L56) ### tinyrobot.App.command ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L128) ### tinyrobot.App.descriptor ```python tinyrobot.App.descriptor() -> dict[str, Any] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L195) ### tinyrobot.App.descriptor_bytes ```python tinyrobot.App.descriptor_bytes() -> bytes ``` Freeze declarations and return exact bytes used for descriptorSha256. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L174) ### tinyrobot.App.digest ```python tinyrobot.App.digest: str ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L201) ### tinyrobot.App.invoke ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L238) ### tinyrobot.App.request ```python tinyrobot.App.request(command: str, *, request_id: str | None = None, **values: Value) -> dict[str, Any] ``` Make an invocation with validated defaults (no execution). [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L204) ### tinyrobot.App.run ```python tinyrobot.App.run(command: str, *, output: str | Path, **values: Value) -> dict[str, Any] ``` Run locally into a NEW directory; never upload or publish anything. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L225) ### tinyrobot.App.stream ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L97) ## tinyrobot.app.Role ```python from tinyrobot.app import Role ``` ```python type tinyrobot.app.Role = Literal['observed', 'simulated', 'predicted', 'diagnostic', 'action'] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L16) ## tinyrobot.Capture ```python from tinyrobot import Capture ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L22) ### tinyrobot.Capture.__enter__ ```python tinyrobot.Capture.__enter__() -> Capture ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L220) ### tinyrobot.Capture.__exit__ ```python tinyrobot.Capture.__exit__(exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L398) ### tinyrobot.Capture.checkpoint ```python tinyrobot.Capture.checkpoint() -> None ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L267) ### tinyrobot.Capture.clock ```python 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 ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L119) ### tinyrobot.Capture.diagnostic ```python tinyrobot.Capture.diagnostic(code: str, message: str, *, severity: str = 'warning') -> None ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L198) ### tinyrobot.Capture.finish ```python tinyrobot.Capture.finish() -> dict[str, Any] ``` Commit evidence.json last; failed captures are removed, not replayable. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L323) ### tinyrobot.Capture.log ```python tinyrobot.Capture.log(stream: str, *archetypes: Any, times: dict[str, float]) -> None ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L279) ### tinyrobot.Capture.metric ```python tinyrobot.Capture.metric(id: str, value: float | None, *, unit: str | None, method: str, source: str, reason: str | None = None) -> None ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L175) ### tinyrobot.Capture.scalar ```python 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). [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L306) ### tinyrobot.Capture.stream ```python tinyrobot.Capture.stream(path: str, *, role: str = 'unknown', meaning: str = 'unknown', unit: str | None = None, clock: str | None = None, source: str | None = None) -> str ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/capture.py#L151) ## tinyrobot.Choice ```python from tinyrobot import Choice ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L25) ### tinyrobot.Choice.default ```python tinyrobot.Choice.default: str ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L30) ### tinyrobot.Choice.description ```python tinyrobot.Choice.description: str = '' ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L32) ### tinyrobot.Choice.label ```python tinyrobot.Choice.label: str = '' ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L31) ### tinyrobot.Choice.options ```python tinyrobot.Choice.options: tuple[str, ...] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L29) ## tinyrobot.controls.Control ```python from tinyrobot.controls import Control ``` ```python type tinyrobot.controls.Control = Number | Choice | MultiChoice | Toggle ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L54) ## tinyrobot.controls.Value ```python from tinyrobot.controls import Value ``` ```python type tinyrobot.controls.Value = float | int | str | bool | list[str] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L55) ## tinyrobot.evidence_request ```python from tinyrobot import evidence_request ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/evidence.py#L193) ## tinyrobot.geometry.depth_points ```python from tinyrobot.geometry import depth_points ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/geometry.py#L12) ## tinyrobot.geometry.meters ```python from tinyrobot.geometry import meters ``` ```python tinyrobot.geometry.meters(value: Any, *, unit: Literal['m', 'cm', 'mm']) -> Any ``` Convert scalar/array visualization positions once at the boundary. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/geometry.py#L56) ## tinyrobot.lerobot.import_lerobot ```python from tinyrobot.lerobot import import_lerobot ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/lerobot.py#L169) ## tinyrobot.Limits ```python from tinyrobot import Limits ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L37) ### tinyrobot.Limits.descriptor ```python tinyrobot.Limits.descriptor() -> dict[str, Any] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L45) ### tinyrobot.Limits.max_bytes ```python tinyrobot.Limits.max_bytes: int = 16 * 1024 * 1024 ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L43) ### tinyrobot.Limits.max_samples ```python tinyrobot.Limits.max_samples: int = 10000 ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L42) ### tinyrobot.Limits.seconds ```python tinyrobot.Limits.seconds: float = 60 ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L41) ## tinyrobot.MultiChoice ```python from tinyrobot import MultiChoice ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L35) ### tinyrobot.MultiChoice.default ```python tinyrobot.MultiChoice.default: tuple[str, ...] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L40) ### tinyrobot.MultiChoice.description ```python tinyrobot.MultiChoice.description: str = '' ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L44) ### tinyrobot.MultiChoice.label ```python tinyrobot.MultiChoice.label: str = '' ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L43) ### tinyrobot.MultiChoice.max ```python tinyrobot.MultiChoice.max: int = 64 ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L42) ### tinyrobot.MultiChoice.min ```python tinyrobot.MultiChoice.min: int = 1 ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L41) ### tinyrobot.MultiChoice.options ```python tinyrobot.MultiChoice.options: tuple[str, ...] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L39) ## tinyrobot.Number ```python from tinyrobot import Number ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L11) ### tinyrobot.Number.default ```python tinyrobot.Number.default: float ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L15) ### tinyrobot.Number.description ```python tinyrobot.Number.description: str = '' ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L22) ### tinyrobot.Number.integer ```python tinyrobot.Number.integer: bool = False ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L19) ### tinyrobot.Number.label ```python tinyrobot.Number.label: str = '' ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L21) ### tinyrobot.Number.max ```python tinyrobot.Number.max: float ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L17) ### tinyrobot.Number.min ```python tinyrobot.Number.min: float ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L16) ### tinyrobot.Number.step ```python tinyrobot.Number.step: float = 1 ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L18) ### tinyrobot.Number.unit ```python tinyrobot.Number.unit: str = '' ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L20) ## tinyrobot.replay ```python from tinyrobot import replay ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/recording.py#L318) ## tinyrobot.replay_evidence ```python from tinyrobot import replay_evidence ``` ```python tinyrobot.replay_evidence(directory: str | Path) -> dict[str, Any] ``` Verify a headless bundle, without loading Rerun, an author or a reader. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/evidence.py#L188) ## tinyrobot.Run ```python from tinyrobot import Run ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/recording.py#L46) ### tinyrobot.Run.at ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/recording.py#L99) ### tinyrobot.Run.checkpoint ```python tinyrobot.Run.checkpoint() -> None ``` Check cancellation, timeout, sample and flushed RRD byte budgets. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/recording.py#L85) ### tinyrobot.Run.clear ```python tinyrobot.Run.clear(stream: Stream, *, at: float | None = None) -> None ``` Explicitly clear stale geometry at a sample time (recursive native Clear). [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/recording.py#L229) ### tinyrobot.Run.log ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/recording.py#L147) ### tinyrobot.Run.prediction ```python tinyrobot.Run.prediction(*, origin: float, candidate: str) -> Iterator[None] ``` Retain forecast origin and horizon separately from target/sample time. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/recording.py#L114) ### tinyrobot.Run.scalar ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/recording.py#L212) ## tinyrobot.Stream ```python from tinyrobot import Stream ``` ```python 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. [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L19) ### tinyrobot.Stream.descriptor ```python tinyrobot.Stream.descriptor() -> dict[str, Any] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L31) ### tinyrobot.Stream.kind ```python tinyrobot.Stream.kind: Literal['scalar', 'visual'] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L24) ### tinyrobot.Stream.path ```python tinyrobot.Stream.path: str ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L23) ### tinyrobot.Stream.role ```python tinyrobot.Stream.role: Role ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L25) ### tinyrobot.Stream.source ```python tinyrobot.Stream.source: str ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L26) ### tinyrobot.Stream.time_kind ```python tinyrobot.Stream.time_kind: Literal['sequence', 'duration'] ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L29) ### tinyrobot.Stream.timeline ```python tinyrobot.Stream.timeline: str ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L28) ### tinyrobot.Stream.unit ```python tinyrobot.Stream.unit: str ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/app.py#L27) ## tinyrobot.Toggle ```python from tinyrobot import Toggle ``` ```python tinyrobot.Toggle(*, default: bool = False, label: str = '', description: str = '') ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L47) ### tinyrobot.Toggle.default ```python tinyrobot.Toggle.default: bool = False ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L49) ### tinyrobot.Toggle.description ```python tinyrobot.Toggle.description: str = '' ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L51) ### tinyrobot.Toggle.label ```python tinyrobot.Toggle.label: str = '' ``` [View source](https://github.com/yujonglee/tinyrobot/blob/main/sdk/py/src/tinyrobot/controls.py#L50) ## Blog - [What a little network sees](http://tinyro.bot/blog/what-a-little-network-sees.md): A handwritten digit, 784 pixels, and the useful gap between what we draw and what a model understands. - [A path from noise](http://tinyro.bot/blog/a-path-from-noise.md): Watching a route take shape, one small correction at a time. A visual note on learning to denoise. - [A small beginning](http://tinyro.bot/blog/a-small-beginning.md): A home for the project, a place for the docs, and a notebook for what comes next. - [Small robots, useful questions](http://tinyro.bot/blog/small-robots-useful-questions.md): Why a two-joint arm and a movable target can teach more than a polished demo that never goes wrong. --- # What a little network sees > A handwritten digit, 784 pixels, and the useful gap between what we draw and what a model understands. Source: http://tinyro.bot/blog/what-a-little-network-sees Published: 2026-10-18 Author: tinyrobot > Sample notebook entry for the blog layout. This is an illustrative explanation, not a new research result or product announcement. Draw a seven and you see a seven. A small classifier sees something less familiar: a grid of numbers, each describing how bright one pixel is. That difference is a good place to start an experiment. ## Make the invisible part visible In [Digit lab](/community/tinyrobot/digit-lab), the drawing is only half the story. The input preview shows the smaller image that reaches the network. A sweeping stroke can become a handful of bright squares; a subtle hook can disappear. Putting that preview beside the prediction makes mistakes more interesting. Instead of asking only “did it get the answer right?”, we can ask what information survived the trip from our hand to the model. ## A score is not a promise Accuracy on held-out MNIST images describes performance on that test set. It does not guarantee the same result for a finger drawing, a messy canvas, or a digit with an unfamiliar shape. Those are different inputs. Try a real held-out example, then draw your own version. Compare the model input, not just the winning label. The difference is often more useful than the score. ## Keep the loop small A canvas, a preview, and ten probabilities are enough for a surprisingly rich conversation with a tiny network. Change one thing, look closely, and try again. The point is not to make the model seem magical. It is to make its limits easier to see. --- # A path from noise > Watching a route take shape, one small correction at a time. A visual note on learning to denoise. Source: http://tinyro.bot/blog/a-path-from-noise Published: 2026-10-17 Author: tinyrobot > Sample notebook entry for the blog layout. This is an illustrative explanation, not a new research result or product announcement. A finished path hides all the decisions that came before it. Start with noise instead, and the process becomes the interesting part. The [trajectory experiment](/community/tinyrobot/trajectory-denoiser) turns that idea into something you can inspect: two endpoints, a noisy set of waypoints, and a sequence of denoising steps. ## Watch the middle The first frame looks unhelpful. The last looks intentional. Between them, a small conditional network repeatedly adjusts the path. Watching those intermediate states makes it easier to distinguish a learned process from a line drawn directly between two points. Keep the endpoints fixed and change the seed. Then keep the seed fixed and move the goal. Each comparison asks a different question about what the model learned. ## Change one thing at a time Experiments become easier to read when the controls tell a simple story. A checkpoint changes the learned weights. A seed changes the starting noise. An endpoint changes the condition. Mixing all three at once makes a dramatic animation, but a poor comparison. ## A sketch, not a robot plan A smooth two-dimensional trajectory is not proof of collision avoidance, physical feasibility, or safe robot control. This is a small synthetic demonstration of denoising. Its value is in making one idea tangible without pretending to solve the whole problem. Sometimes the most useful experiment is the one whose boundaries are easy to explain. --- # A small beginning > A home for the project, a place for the docs, and a notebook for what comes next. Source: http://tinyro.bot/blog/a-small-beginning Published: 2026-10-16 Author: tinyrobot Every project needs somewhere to start. For tinyrobot, that means keeping the foundation small and giving each kind of information a clear place to live. ## Three places, one home The landing page is the front door. It should help you understand where you are and where to go next, without asking you to read a manual. The documentation is the practical part: how to get running, where the code belongs, and how to add to it. The blog is a little less structured. It's a place to share decisions, things learned along the way, and updates as the project takes shape. ## Content that lives with the code Guides and posts are Markdown files in the repository. Content Collections validates their metadata and compiles the Markdown at build time. Adding a page means adding a file, not wiring up another route. > Start with a clear structure. Leave room for the useful things to come. ## Make it your own This is a first note, not a finished story. The [documentation](/docs) is the best place to start exploring the foundation. --- # Small robots, useful questions > Why a two-joint arm and a movable target can teach more than a polished demo that never goes wrong. Source: http://tinyro.bot/blog/small-robots-useful-questions Published: 2026-10-16 Author: tinyrobot > Sample notebook entry for the blog layout. This is an illustrative explanation, not a new research result or product announcement. An arm reaches for a dot. There is no complicated scene, no cinematic camera, and no long list of tasks. Just two joints and somewhere to go. That simplicity is useful. It leaves room to notice what changes when a learned policy replaces an untrained one. ## Let the reader disturb the demo A good experiment gives you something to change. In [Robot reacher](/community/tinyrobot/robot-reacher), moving the target lets you ask whether the behavior holds up somewhere new. Switching checkpoints makes the comparison immediate. The interesting moment is not always a successful reach. It might be a hesitant movement, a target the arm cannot quite reach, or a difference between the route you expected and the one the policy takes. ## Name the source of the behavior This example learns from synthetic demonstrations. That matters: it is not evidence of a policy trained on a physical robot, and a browser simulation cannot establish real-world safety. Keeping those facts next to the interaction gives the reader a fair starting point. Clear labels are part of the experiment, not fine print beneath it. ## Leave with a better question What happens near the edge of reach? What changes with the starting pose? Where does the trained policy still struggle? A small demo does not need to answer every question. It should make the next one easier to ask. ## Changelog - [A small foundation](http://tinyro.bot/changelog/a-small-foundation.md): A home for the docs, an application scaffold, and a self-hosting option. --- # A small foundation > A home for the docs, an application scaffold, and a self-hosting option. Source: http://tinyro.bot/changelog/a-small-foundation Published: 2026-10-16 The first pieces of tinyrobot are in place. This is a foundation to build on, not a finished product. ## What's here - A website with [documentation](/docs) and a [blog](/blog), backed by Markdown. - An [application scaffold](/app), available in the cloud deployment or on its own with Node.js and Docker. - Light, dark, and system themes across the site and app. - Separate Python and JavaScript SDK scaffolds, plus a FastAPI server with a health endpoint. The SDKs do not have a public API yet. Start with the [quickstart](/docs/quickstart) to explore the project locally. ## Optional - [All content in one file](http://tinyro.bot/llms-full.txt): Full text of all published entries.