Talk to your robot. It answers in motion.

OpenCastor is the open source runtime that connects AI brains to robot bodies. Speak or type one sentence, a local model drafts a bounded plan, you sign it, and the wheels turn.

You said: Do a 1 meter creep drive

The plan drafted from it: drive.set {"throttle": 0.2, "steering": 0, "duration_s": 2}

You said: signed

The receipt it left: drive.set -> ok envelope_signature
And on the robot's own screen: Creeping — 2.1 m ahead

One pass of the loop, set as type. Your sentence, the plan drafted from it, the receipt it left, and the line its screen showed while it moved. The robot has no line of dialogue here because its answer was the motion. The bench video is the proof.

pip install "opencastor>=3.1"
castor up

Python 3.10 to 3.13. The >=3.1 matters: a bare pip install opencastor resolves an older CalVer wheel with no castor up in it. castor up takes a bare host to a paired robot printing its pairing QR, on simulated wheels.

Get the iOS app on the App Store

Public beta, and we mean beta. Builds change most weeks, some things are rough, and the release notes tell you which.

Apache 2.0. Live on the bench this week, on a real RC car.

signed kid rover-gw-attest-2026
stop partial transcript, before the sentence ends
operator bench capture pending

Stop always wins.

Say stop and the word goes straight to the wheels. It does not wait for a model to finish thinking. It does not wait for you to finish the sentence. Motion needs a budget, a window, and a speed cap you approved. If commands stop arriving, the robot stops about a second later. Every action leaves a receipt you can verify offline.

You are not operating a machine. You are asking someone in the room to do one small thing, and being told what it did.

hold the button, or focus it and hold space

  • spoken stop bypasses every model in the stack
  • human-approved budget, window, speed cap
  • deadman stops the robot about 1 s after commands stop

A plan is inert until signed.

Your sentence becomes a plan, and the plan is a small, boring object: one tool, three arguments, and the limits it has to live inside. It cannot move anything. It moves when a signature lands on it. You sign it from your phone, or a standing owner grant you wrote in advance signs it for you, on terms you set.

drive.set {"throttle": 0.2, "steering": 0, "duration_s": 2}
drive.setthe one tool this plan may call
throttle0.2, under the speed cap you approved
steering0, straight ahead
duration_s2, the window in seconds it may move inside
budgetone motion, then the plan is spent
envelope_signature

alg Ed25519 · kid rover-gw-attest-2026 · sig

Anatomy of a receipt.

Every action leaves one of these behind. The tool name, the arguments it actually ran with, the result, and an Ed25519 signature over the whole envelope. You can check it on a laptop with no network, months from now. There is no account to log into and no server in the loop.

One command, no network, months later:

git clone https://github.com/RobotRegistryFoundation/robot-md-gateway
python robot-md-gateway/scripts/verify_receipt.py --receipt r.json --pubkey gw.pub

The verifier lives with the gateway, not with the runtime, and that is the point: it is stdlib and one crypto library, it imports neither the runtime nor the protocol package, and a third party checking your robot should not have to trust either one. It also flips one byte and proves the signature fails, so a run that reports success has asserted both directions.

{
  "v": "rcan-action-trace/1",
  "rrn": "RRN-000000000012",
  "ruri": "rcan://RRN-000000000012/drive.set",
  "invoke": {
    "type": "rcan/v1/invoke",
    "tool_name": "drive.set",
    "scope": "MANIPULATE",
    "tool_args": {
      "throttle": 0.2,
      "steering": 0,
      "duration_s": 2
    }
  },
  "outcome": {
    "status": "ok",
    "duration_ms": 1,
    "started_at": "2026-08-04T01:37:57.302051+00:00",
    "ended_at":   "2026-08-04T01:37:57.303164+00:00",
    "telemetry_sha256": ,
    "envelope_signature": {
      "alg": "Ed25519",
      "kid": "rover-gw-attest-2026",
      "sig": 
    }
  }
}

one real receipt from the RC car bench, 2026-08-04. field names, alg and kid verbatim. the digest and the signature are covered: this page prints no hex.

Ten minutes, on the clock.

It is the goal we design against, and the command times itself so the number is yours to check.

Ten minutes is the goal we design against. The timed run that fills the column on the right is the one the flashable Pi image produces when it clears hardware verification, and until that capture exists this page prints ink bars instead of numbers. It is not a certification and we will not dress it up as one.

One command does the work. castor up detects your hardware, writes and signs a ROBOT.md manifest, issues its tokens, starts the services, and prints a pairing QR. It timestamps every step as it goes, so you can check the arithmetic yourself.

castor/up.py:312  ·  print(f" [{time.monotonic() - started:5.1f}s] {step}")
castor/up.py:402  ·  _say(f"actuator: {actuator_name} — {actuator_note}", started)

[s]
detected:
[s]
archetype: rc-car
[s]
identity: (local — `castor register` upgrades it)
[s]
ROBOT.md written and signed (kid rover-manifest-2026)
[s]
bearers: tokens generated (actuate + read)
[s]
actuator: noop — the rc-car actuator is not installed — `pip install rc-car-actuator`, then rerun `castor up`
[s]
attestation: rover-gw-attest-2026
[s]
brain: ollama qwen3.5:2b
[s]
services written: rover-gateway.service, rover-castor.service, rover-console.service, rover-discovery.service
[s]
services started
[s]
pairing QR: /pair-qr.png
[s]
gaps: 1 noted in gaps.json (missing-package)

step text verbatim from castor/up.py, in the order it prints, with the kid names from the RC car bench. the actuator row and the gap row are the branch a host takes when the rc-car actuator package is not installed, which is what `pip install opencastor` on its own gives you, and they belong to each other. host-specific values and the elapsed column are ink bars until the operator's timed capture lands: this page will not print a number it did not measure.

operator bench capture pending

The scan.

Scan that QR with the app. Your robot has a face now, and on a vehicle, an eye.

operator bench capture pending

If you would rather never open a terminal.

There is a flashable Pi image: blank SD card to pairing QR. The first build is downloadable now, with hardware verification in progress, so early flashers are exactly that. It ships in two parts under GitHub's file limit; the release notes carry the one-line reassembly, the sha256, and the flashing steps.

Download the Pi image, v0.1.0

Local brains, long memory.

The brain runs on the robot. Ollama with qwen for language, gemma-vision for what the camera sees, and cloud brains available when you want them, optional by design. The robot also keeps a long term memory of its own bench history, and it recalls relevantly.

  • brain: ollama qwen3.5:2b
  • vision: gemma-vision, on the robot
  • cloud brains: optional, per robot, never required

You asked: why won't it move at low throttle?

The robot's recalled answer is not on this page yet: operator memory transcript pending

The question is verbatim. The answer is the robot's own recalled memory, and it goes here once the bench transcript is captured. We are not going to write a plausible one.

The face, and on vehicles the eye.

The iOS app is the robot's face, and on a vehicle it is also its eye. Wake word, per robot brain assignment, and an autonomy grant you hand over on purpose. Autonomy is tiered by what the camera can actually see: with LiDAR it runs full, with sparse depth it creeps only, and blind it refuses and tells you why.

Get the iOS app on the App Store

Public beta, and we mean beta. Builds change most weeks, some things are rough, and the release notes tell you which.

three strings, verbatim, from the mounted screen

operator app capture pending
operator app capture pending

Bodies.

Three bodies, three honest labels. A Raspberry Pi 5 RC car, driving today. An SO-ARM101 six degree of freedom arm, on the bench with its wrist camera unplugged. An ESP32-C6, groundwork toward six dollar class robots that show their own pairing QR on their own screen, and on the bench tonight it does exactly that.

Line drawing of the Raspberry Pi 5 RC car

Raspberry Pi 5 RC car

driving today Raspberry Pi 5 · PCA9685 PWM · brushed ESC and steering servo
operator bench capture pending
Line drawing of the SO-ARM101 arm

SO-ARM101 arm

on the bench SO-ARM101 six degree of freedom · Feetech STS3215 servos, six
operator bench capture pending
Line drawing of the ESP32-C6

ESP32-C6

groundwork ESP32-C6 rev v0.2 · 16 MB flash · toward six dollar class robots
operator bench capture pending

five lines, verbatim, from an ESP32-C6 boot capture

  OpenCastor C6 alive
  chip model   : ESP32-C6 (rev v0.2)
  flash size   : 16777216 bytes (16 MB) physical
  i2c total    : 5 devices on the shared bus
OpenCastor C6 heartbeat #1 tag=ALPHA uptime=2s free_heap=456464

That board paints its own pairing QR on its panel now, and the QR encodes opencastor.com/pair?demo=esp32c6. Scan it off the bench, or open the page and read what a real pairing QR carries.

The world it plugs into.

ROBOT.md manifests at robotmd.dev. The RCAN protocol at rcan.dev. The code at github.com/craigm26/OpenCastor. The documentation lives at docs.opencastor.com, behind the one Docs link in the header. The older pages of this site, the changelog, the tutorials, the beginner path, the hardware guides, the hub, the config explorer, the blog and the about page, all still work, and every one of them is in the footer below.

  • robotmd.dev ROBOT.md, the single-file robot manifest
  • rcan.dev the RCAN protocol these envelopes speak
  • github.com/craigm26/OpenCastor the runtime itself
  • Apache 2.0 the whole runtime, and every line of it is yours to read
  • Docs the one door, and it opens on docs.opencastor.com

Start talking.

pip install "opencastor>=3.1"
castor up

Python 3.10 to 3.13. The >=3.1 matters: a bare pip install opencastor resolves an older CalVer wheel with no castor up in it. castor up takes a bare host to a paired robot printing its pairing QR, on simulated wheels.

Read the code on GitHub
Get the iOS app on the App Store

Public beta, and we mean beta. Builds change most weeks, some things are rough, and the release notes tell you which.