reachy-mini-cli
reachy-mini-cli is the agent and command-line tool for operating a Reachy Mini — an expressive desk robot with a movable head, two antennas, a rotating body, a mic array that hears direction, a camera, and a speaker. Every capability the robot has becomes a noun you run from a shell or drop into an agent loop: hold the hardware, feel alive, orient to sound, turn toward motion, speak, think out loud, feel a head pat, fall asleep, and survive a reboot.
If the agents family has a method, a brain, and a mind — devague is the method that works an idea backwards into a plan, lobes is the split brain that serves cognition across machines, and colleague is the swappable mind you point at a repo — then this is the body. reachy-mini-cli is the embodiment leg: the place where all of that stops being text on a screen and starts leaning its antennas toward the person who just spoke.
Everything on this page is real. The version is 0.31.0, the same one on PyPI. The captures were taken today against a live robot on the rig we call spark — its daemon answering on localhost, an arducam for eyes — and where a service was down, you will see the robot's honest fallback rather than a staged success.
- installuv tool install 'reachy-mini-cli[daemon]'
- repoagentculture/reachy-mini-cli
- packagereachy-mini-cli 0.31.0 · PyPI
- licenseMIT
- python>=3.12
- commandsreachy-mini-cli · reachy
- transportssdk (in-process) · http (daemon REST)
- hardwarehead + antennas + body · mic array · camera · speaker
- agents familythe body — embodiment leg
Everything the robot can do is a verb you can type
Start with what it is: a small desk robot that can look at you, listen for you, and move like it means it. reachy-mini-cli does not hide any of that behind an app — it hands you each capability as a plain command, so a human at a terminal and an agent in a loop reach the robot exactly the same way.
Here is the whole body, one line at a time. None of these is a promise for later; each is a noun that ships in 0.31.0.
hold the hardware
daemon owns the robot process and wakes it; device and move run low-level state and one-shot goto / wake / sleep animations against it
feel alive
demo-mode is the always-on idle loop — gentle breathing, glances, antenna sway — so the robot looks present even when nothing is happening
orient to sound
listen leans its antennas toward a voice, then turns its head and body to face it — reading real direction-of-arrival off the mic array
turn toward what it sees
vision turns toward motion or light in the camera using pure pixel math — frame differences and brightness, no machine-learning model in the loop
speak
say is a dumb pipe — text in, voice out to the speaker — in either a text-to-speech voice or an offline harmonic one, with no LLM and no senses attached
think out loud
think is the cognition loop: it reads the senses, asks an LLM for a first-person thought, speaks it sentence by sentence, and moves its body in step with the words
feel a head pat
pat notices a hand on its head from how the pose deviates from what it commanded — no touch sensor — and leans into the touch: lean, nuzzle, settle
fall asleep
sleep decays from alert to drowsy to asleep when left alone, and wakes on speech, a snap, its wake word, or a pat
survive a reboot
service enrolls exactly one presence mode — demo or live — as a boot-enabled systemd unit, one presence per reboot; hardening that survival on real hardware is open work the tracker owns
$ reachy-mini-cli whoami
nick: reachy-mini-cli
version: 0.31.0
backend: unknown
model: unknown
$ reachy-mini-cli doctor
reachy-mini-cli doctor: healthy
[ok] source_checkout: no culture.yaml found alongside the package; identity checks skipped
$ reachy-mini-cli quickstart
reachy-mini-cli quickstart — install and start real mode
Real mode — local robot (recommended)
-------------------------------------
1. Install once (CLI + daemon binary + SDK):
uv tool install 'reachy-mini-cli[daemon]'
# or: pip install 'reachy-mini-cli[daemon]'
# the installed command is 'reachy-mini-cli' (short alias: 'reachy')
2. Start the daemon (wakes the robot on start):
reachy-mini-cli daemon start
3. Verify it answers:
reachy-mini-cli device status
4. Make it do something:
reachy-mini-cli listen run # orient to sound (Ctrl-C to stop)
reachy-mini-cli demo-mode start # feel-alive idle loop (background)
reachy-mini-cli move goto --z 10 --pitch -5 # one motion command
5. Put it back down when you are done:
reachy-mini-cli daemon stop
… (the remote/HTTP-only path and the always-available no-daemon commands elided)
Exit codes: 0 success, 1 user error, 2 environment/setup error.
One robot, fourteen nouns — and every one of them speaks JSON
This is the entire surface, nothing held back. Fourteen entries: thirteen robot nouns and one row of agent-first introspection that needs no robot at all. Every noun supports --json, and reachy-mini-cli explain <noun> prints its full flag reference.
Read the transport column closely — it is the whole operating model in one word per row. The sense nouns default to the in-process sdk transport; device, app, and move default to http, the daemon's REST API. That split is not an accident; it is the next section.
| noun | what it does | transport |
|---|---|---|
daemon | start / stop / status the local reachy-mini-daemon process | none — manages the process |
device | daemon and live robot state (status, state) | http (default) |
app | list / start / stop daemon apps | http |
move | one-shot goto / wake / sleep animations | http (default) |
demo-mode | always-on "feel alive" idle loop (breathe, glances, sway) | sdk / http |
behavior | 50 Hz engine that composes named behaviors per channel | sdk / http |
listen | two-tier sound orienting (antenna lean → head/body turn); --live folds every sense into one loop | sdk default |
vision | turn toward motion or light (pure pixel math, no ML) | sdk default |
say | dumb pipe: text → voice (TTS or offline harmonic) → speaker | sdk default |
think | LLM cognition loop: speaks and expresses; --export JSONL feed | sdk default |
pat | feel a head pat and lean into it (no touch sensor) | sdk only |
sleep | decay to sleep when idle; wake on sound / wake-word / pat | sdk default |
service | boot-persist exactly one presence mode (demo or live) via systemd --user | none — manages systemd |
whoami · quickstart · learn · explain · overview · doctor · cli | agent-first introspection — no robot needed | — |
$ reachy-mini-cli device status
type: daemon_status
robot_name: reachy_mini
state: running
wireless_version: False
desktop_app_daemon: False
simulation_enabled: False
mockup_sim_enabled: False
no_media: False
media_released: False
camera_specs_name: arducam
backend_status:
ready: False
motor_control_mode: enabled
last_alive: None
control_loop_stats:
mean_control_loop_frequency: 49.66561043533144
max_control_loop_interval: 0.02042984962463379
nb_error: 0
motor_controller: ControlLoopStats(period=~20.00ms, read_dt=~1.92 ms, write_dt=~0.19 ms)
error: None
error: None
wlan_ip: None
version: 1.7.3
hardware_id: 37a38ce3a26e0727
$ reachy-mini-cli device state
control_mode: enabled
head_pose:
x: 0.0007060431511311815
y: 0.0005256924657005011
z: -0.0011491551363928543
roll: -0.005241699183951121
pitch: -0.004407163898566546
yaw: -0.07900591329492851
head_joints: None
body_yaw: 0.0015339807878858025
antennas_position:
- 0.01687378866674205
- -0.024543692606170175
timestamp: 2026-07-17T00:25:15.449314Z
passive_joints: None
doa: None
One robot serves one mind at a time — learn that or fight it
If you take one thing from this page, take this. The CLI will happily let you launch any two nouns at once, but the hardware underneath has two single resources, each with room for exactly one owner. This trips up humans and agents alike, over and over.
The first single resource is the SDK client and its media session: on the sdk transport, listen, think, and sleep each open a single-consumer mic session, and vision and pat read camera frames and head pose through the same one in-process client. So only one sdk-sense noun can own the robot at a time. The second is the head itself — every move, from an idle sway to a sound-orienting turn to an expression, flows through one serial motion queue, one move at a time, so motion is always smooth and never fights itself. Two motion drivers still fight over the same head.
Because both resources are single-owner, you cannot run two sdk-sense nouns as separate processes against one robot. The second one is starved — a stray pat process next to listen gets throttled to about one hertz, far too slow to feel a hand. There are exactly two ways to compose behaviors anyway, and the diagram beside this traces both.
two single resources
one in-process SDK client with a single-consumer media session, and one serial motion queue for the head — each serves exactly one owner
five nouns, one seat
listen, think, sleep, vision, and pat are mutually exclusive on the sdk transport — run one sdk-sense owner per robot, never two
escape one — fold into one loop
listen run --live folds think, vision, and sleep into listen's single loop alongside the head-pat hook: every live sense on one media session, in one process
escape two — put the second noun on http
an http-transport noun polls the daemon's DoA route and opens no SDK client at all, so it never competes — the way to layer a second behavior onto the one local owner
the head is arbitrated by priority
when senses are folded together, think, pat, and sleep drop flag files the always-alive idle layer reads, yielding the motion channel by rank: sleep over pat over think
$ reachy-mini-cli move wake
uuid: f020512e-0567-4a76-a894-8cc5e3d521e6
$ reachy-mini-cli listen run --max-ticks 120
… (SDK/daemon version-mismatch RuntimeWarning elided — kept verbatim in the say · harmonic pane above)
[listen] orienting to sound via sdk: dwell=1.5s hold=3s speed=18deg/s; Ctrl-C to stop
[listen] antenna lean +22 (0.3s)
[listen] look +17 (1.5s)
[listen] idle +17 (2.2s)
[listen] idle +7 (2.2s)
[listen] escalate body -45 head -8 (3.8s)
[listen] idle -8 (2.2s)
[listen] idle -8 (2.2s)
[listen] escalate body +44 head +0 (4.0s)
[listen] idle +0 (2.2s)
[listen] idle +0 (2.2s)
[listen] escalate body -35 head +0 (4.0s)
[listen] idle +0 (2.2s)
[listen] idle +0 (2.2s)
[listen] antenna lean +15 (0.3s)
[listen] antenna lean +10 (0.3s)
[listen] escalate body -45 head -9 (1.6s)
[listen] idle -9 (2.2s)
[listen] idle -9 (2.2s)
[listen] antenna lean +16 (0.3s)
[listen] antenna lean -21 (0.3s)
[listen] stopped after 79 tick(s)
One process, every sense — this is the presence the robot boots into
Because only one sdk media owner can run at a time, the supported way to run all the senses at once is a single command — listen run --live — that folds think, vision, and sleep into listen's loop beside the head-pat hook. Every live sense rides the one media session and the one motion queue, in one process, arbitrated by those priority flags. This is exactly the loop the live boot presence runs.
Below is each behavior the folded loop carries. Where a sense is honest about its limits, the card says so — because the robot does too.
listen — two tiers of reaction
tier one leans the antennas toward every sound; tier two turns the head and then the body on speech or a snap — reading real direction-of-arrival, not a guess
vision — sight without a model
turns toward motion by frame difference and toward light by brightness centroid — pure pixel math, no ML and no GPU; on http it can report its specs but not move
pat — touch without a sensor
there is no touch sensor; pat infers a hand from how the actual head pose deviates from the commanded one, then leans, nuzzles, and settles into it
sleep — a real drowsiness curve
decays alert to drowsy to asleep when nothing is happening, breathing slower as it fades, and wakes on speech, a snap, its wake word, or a pat
demo-mode — the feel-alive idle
the motion-only presence for a robot that just needs to look present: continuous gentle breathing, glances, and antenna sway, with no senses attached
service — one presence per reboot
boots exactly one presence — the idle demo loop or the folded live loop — through systemd; enabling one disables the sibling, so two presences never fight for the robot
$ reachy-mini-cli service status
mode: live
presence_unit: reachy-live.service
daemon_healthy: True
units:
reachy-daemon.service:
enabled: enabled
active: active
reachy-demo-mode.service:
enabled: disabled
active: inactive
reachy-live.service:
enabled: enabled
active: active
It reasons, speaks a sentence at a time, and moves as it thinks
think is the noun where the body and the mind meet. Each turn snapshots what the robot just perceived — direction-of-arrival, mic loudness, speech — and asks an LLM for one or two short first-person sentences. The reply is sentence-streamed: the first sentence is synthesized and played while the later ones are still being generated, so thinking and speaking overlap the way they do in a person. Emoji markers in that stream drive calm body poses that arrive timed to the words, and each move goes onto the one serial motion queue. The captured demo runs three gestures against three spoken phrases so you can watch the timing without an LLM in the loop.
It has a second voice for when the first one is unreachable. The default is text-to-speech over an HTTP service, but that service can be down — and when it is, live cognition would otherwise go silent. So the robot also has a harmonic voice: fully offline, deterministic, in-process. It does not read the words; it renders their meaning as a short note-melody in the robot's own identity signature. Today's captures show both halves, separately and honestly: the default TTS run failed clean — an error line, a hint, no crash — and a second run that asked for the harmonic engine up front played its melody with no TTS service anywhere. The engine is chosen per run, with --voice-engine or REACHY_VOICE_ENGINE; when the speech service is down, the harmonic voice is the one you reach for.
And you can watch it think from outside. think run --export - streams a newline-delimited JSON feed of what the robot is thinking, saying, and feeling — one object per line, tagged thinking, message, or emotion, each with a timestamp. The renderer lives out of this repo by design: the CLI emits a documented contract, and a separate consumer renders it. Be precise about what ships, though — in 0.31.0 only the stdout sink, spelled '-', is wired. Point --export at a file or an HTTP URL and the CLI says so itself: only '-' (stdout) is supported in this version; HTTP and file sinks are future work. A clean exit-one error, not a broken promise.
One last honesty: the loop does nothing gracefully. When the sense-event buffer is empty, the turn is a no-op — no LLM call, no audio. A bounded think run against a quiet room can finish with zero spoken turns and that is the design, not a failure. It is the same discipline everywhere in this tool: the robot would rather do nothing legibly than fake being busy.
$ reachy-mini-cli think demo --voice-engine harmonic
/home/spark/.local/share/uv/tools/reachy-mini-cli/lib/python3.12/site-packages/reachy_mini/reachy_mini.py:164: RuntimeWarning: Reachy Mini SDK and daemon versions do not match: SDK=1.9.0, daemon=1.7.3. Running different versions can create issues. Install matching reachy_mini versions for the SDK and daemon.
self.media_manager = self._configure_mediamanager(media_backend, log_level)
[think demo] done — expressed 3 gesture(s), spoke 3 phrase(s)
$ reachy-mini-cli think expressions
🤔 head_z+2, head_roll+8, head_pitch+6, head_yaw-5, antenna_right-10, antenna_left-10
😮 head_x-3, head_z+5, head_pitch-8, antenna_right+30, antenna_left+30
🙂 head_pitch+5, antenna_right+12, antenna_left+12
👂 head_y+2, head_roll-5, head_pitch+3, head_yaw+15, antenna_right+20, antenna_left-5, body_yaw+8
😐 head_z+3, head_pitch-3, antenna_right+5, antenna_left+5
🎉 head_z+6, head_pitch-10, antenna_right+40, antenna_left-40, body_yaw+10
😔 head_x+2, head_z-3, head_pitch+12, antenna_right-25, antenna_left-25
$ reachy-mini-cli think run --max-turns 1 --max-ticks 40 --voice-engine harmonic
/home/spark/.local/share/uv/tools/reachy-mini-cli/lib/python3.12/site-packages/reachy_mini/reachy_mini.py:164: RuntimeWarning: Reachy Mini SDK and daemon versions do not match: SDK=1.9.0, daemon=1.7.3. Running different versions can create issues. Install matching reachy_mini versions for the SDK and daemon.
self.media_manager = self._configure_mediamanager(media_backend, log_level)
[think] thinking out loud via sdk; voice engine: harmonic; turn-interval=1s mute-after-speak=2.5s; Ctrl-C to stop
[think] stopped after 0 spoken turn(s)
## Gate 2 — Demo run (scripted verification)
Run `reachy-mini-cli think demo` and observe:
### c1 / h1 — Movement is timed to thoughts, not random
- [ ] The **first gesture** (`🤔`) fires **before or with** the first spoken
phrase ("I wonder what that sound was."), not after it is finished.
- [ ] The **second gesture** (`👂`) fires **before or with** the second phrase
("There it is again, to my left."), not before the first phrase starts.
- [ ] The **third gesture** (`🙂`) fires **before or with** the third phrase
("Ah — it's just the fan.").
- [ ] No gesture fires **during silence** between phrases.
… (Gate 2's remaining c5/h5 calm-body and c7/h7 distinct-expression checklist items elided — same three gestures, judged for stillness and visual distinctness)
## Notes
… (the coalescing-behaviour and rubric-path notes elided)
- **JSON result:** `reachy-mini-cli think demo --json` should output a JSON
object with `"status": "ok"`, `"expressed"` (list of 3 emojis), and
`"spoken"` (list of 3 phrase strings).
$ reachy-mini-cli say run "Hello — I am the page material now."
[say] synthesizing 35 char(s) …
error: TTS endpoint unreachable: [Errno 111] Connection refused
hint: start the TTS server or point REACHY_TTS_URL at a reachable host (tried http://localhost:9000/v1/audio/synthesize)
$ reachy-mini-cli say run "Hello from the capture batch" --voice-engine harmonic
[say] synthesizing 28 char(s) …
[say] playing 44640 PCM bytes …
/home/spark/.local/share/uv/tools/reachy-mini-cli/lib/python3.12/site-packages/reachy_mini/reachy_mini.py:164: RuntimeWarning: Reachy Mini SDK and daemon versions do not match: SDK=1.9.0, daemon=1.7.3. Running different versions can create issues. Install matching reachy_mini versions for the SDK and daemon.
self.media_manager = self._configure_mediamanager(media_backend, log_level)
Built for an agent to drive — every answer is structured, every failure is a contract
This is not a robot toy with a scripting hook bolted on. It is an agent-first CLI, cited from teken's agent-first foundation, and the same design that makes it pleasant for a person makes it drivable by a model. The introspection verbs — whoami, quickstart, learn, explain, overview, doctor — all work with no robot attached, so an agent can learn the whole surface before it touches hardware.
The contracts underneath are the point. Every command takes --json. Results go to stdout and diagnostics to stderr, never mixed, so a program can read the answer without parsing around chatter. Exit codes mean one thing each: zero for success, one for a user-input error, two for an environment error, three and up reserved. And a failure is never a Python traceback — it is a structured error: line with a hint: line telling you what to do next.
You can see the contract hold on a bare install. Run an sdk-transport noun without the SDK extra and you do not get an ImportError — you get exit two and a hint to install [sdk] or use --transport http. In today's captures the same shape appears for real: the TTS service was down, and the robot returned its clean error-and-hint pair rather than crashing; a second run asked for the offline harmonic voice explicitly and played through the same speaker. The failures on this page are the CLI keeping its promises.
introspection with no robot
whoami, quickstart, learn, explain, overview, and doctor run on any install — an agent reads its own identity, the bring-up sequence, and per-noun docs before a robot is even attached
--json on every verb
structured output on every command, results on stdout and diagnostics on stderr and never mixed, so a program reads the answer cleanly and a human reads the chatter separately
exit codes that mean one thing
zero success, one user-input error, two environment error, three and up reserved — the same policy across every noun, so a caller branches on the number without scraping text
error: and hint:, never a traceback
every failure is a structured pair — the TTS-down capture shows it live: a clean error line and a hint naming the fix, the exit code intact, and not a stack trace in sight
a bare install fails cleanly
the SDK is an optional extra, lazily imported; run an sdk noun without it and you get exit two with a remediation to install [sdk] or use --transport http — never an ImportError from deep in a library
The tracker is public too — this is direction, not a promise
The body is still growing, and its direction is checkable the same way its facts are: on the open tracker. Four currents stand out — and note that everything in this section is open work, not something 0.31.0 ships.
A presence that truly survives a reboot
service can already boot-persist one presence, but the lifecycle around it has known gaps. The open work closes them: a service unit that is written but never starts, a daemon noun that cannot restart or stop a service-managed daemon, the underlying fact that the daemon does not survive a host restart on its own, and a followable log stream to see why when it does not.
- #62 `service` can write a `reachy-daemon.service` that never starts (203/EXEC) — bare-name ExecStart fallback
- #61 `daemon` noun cannot restart or stop a service-managed daemon — no `restart` verb, and `stop`/`status` are pidfile-only
- #21 Daemon does not survive host restart — must be started manually
- #10 Add `daemon logs [--follow]` (daemon log websocket + tracked log file)
Sight that reaches cognition
Vision today is a standalone reflex — turn toward motion or light with pure pixel math. The open work makes it real on hardware and wires it into thinking: the live reachy_mini camera-frame API, camera cues flowing into the think event feed so the robot reasons about what it sees, and the foundational camera support they both rest on.
From heard to addressed — live conversation
The robot already hears words; holding a conversation is the next leg. The open work turns orientation into address: a head-turn when it hears its own name, durable audio-service addressing, LLM-judged context for what is meant for it, live-verifying the wake word once the STT service is healthy, and a pluggable wake-word backend.
- #55 Live conversation: head-turn-on-name, durable audio-service addressing, LLM-judged context
- #39 Live-verify sleep wake-word once model-gear Parakeet STT is healthy (blocked-by model-gear#39/#40)
- #36 sleep: pat-only wake when audio-listening is off; pluggable wake-word backend (external HTTP STT default + on-box openwakeword [cpu])
One supervisor, one mesh
The single-SDK-owner model wants to become code, not just a rule. The open work proposes a unified real-mode supervisor that owns the single sdk session, a shared process-supervisor to de-duplicate the motion and vision supervisors, an arbitration so demo-mode and reachy_nova stop fighting over the head, and mesh awareness that publishes robot and daemon liveness to the Culture mesh — the spark-reachy use-case.
- #45 Redesign strategy: a unified real-mode supervisor that owns the single sdk session
- #30 Extract a shared process-supervisor to de-duplicate reachy.motion / reachy.vision supervisors
- #12 `demo-mode` and reachy_nova both drive the robot — define motion ownership so they don't fight
- #14 Mesh awareness: publish robot/daemon liveness to the Culture mesh (the `spark-reachy` use-case)
reachy-mini-cli is open source, MIT, and running for real — the captures on this page are a live robot's own sessions, taken today on the rig we call spark. Every fact above traces to the public repo, the packaging metadata, the export schema, or the CLI's own output, and where a service was down you saw the honest fallback, not a staged success.
That is the whole thesis of the body: the same agent-first discipline the rest of the family runs on — structured output, honest failures, one clear owner of a shared resource — made physical, in a robot that leans toward you when you speak and answers in its own voice even when the network does not. Install it, bring the daemon up, and say something near it.