Current state — August 17 afternoon: Recognition is the milestone. In a live kitchen session the camera path named Cindy, Bennie, and Chewy correctly. Speech is now local on the M3 Ultra from a private speaker-finetune; the public name is still Milo. First audio on a long line dropped from 13.7s to 4.6s after sentence-sized generate, and every clip is peaked to the speaker ceiling. Hardware volume stays at 100 without rewriting it. E3/E5, the no-whistle boot guard, and integrated tool-history pairing remain passed. Dance-when-asked is a prompt fix, not an attended proof. Act-from-voice stays off.

A Reachy Mini Wireless (Pollen Robotics / Hugging Face, $499 DIY kit) is the body. Milo is the mind. Same identity as desktop Hermes Milo — not a separate chatbot bolted onto a cute shell.

Recognition liveAttended: Cindy, Bennie, and Chewy named correctly
Local speechM3 Ultra custom-voice; public credit is Milo
E3 / E5 passedNamed arrival plus 20-exchange soak still stand
Protocol pairedInjected calls recorded before delivery and termination
Authority boundedNo email, deploy, purchase, or act-from-voice
Fully assembled Reachy Mini standing on the floor
Assembled. Somewhere between Baymax and a mars lander.

Architecture

Stock path keeps the body. Custom path is everything that makes it Milo.

Current Reachy Milo voice architecture: the robot renders approved intents, the M4 owns voice and policy, and external services have no body authority
Architecture updated August 17. The conversation app is the single live camera client and exports frames for identity; expression policy still lives in the brain and the body only renders approved intents. Private addresses, credentials, paths, and household identity stay off the public diagram.
LayerStock (HF / Pollen)Custom (J&M)
Body / IODaemon, motors, media, conversation appHeadless startup and recovery, native gaze/motion manager, volume lock, and app-owned identity-frame export to the M4 vision service
BrainOptional hosted realtime rollbackLocal far-field STT → realtime language response → local M3 custom-voice speech
MemoryLayered recall + hard relationship facts + durable schema-v2 writeback outbox
EyesBody camera and media stackNative gaze and expression share one movement manager. The same app media stream exports people/dog identity frames without a second LOCAL camera client; the path now has real cold-boot recovery proof.
KnowledgePhrase-gated read-only research with spoken filler; no side-effect tools

What we copied from Reachy Claw

We reviewed suharvest/reachy-claw and copied three things: a single motor writer, intent instead of a second neck owner, and read-only observability first. That is the conversation app’s one movement manager composing gaze and expression. We did not switch stacks. Milo’s brain, memory, recognition, and authority stayed ours. We did not copy their dashboard.

What's live

CapabilityState
Headless applianceLive. Conversation app, local services, motor wake, health and recovery are automated.
Voice loopLive. Far-field VAD; local large-v3-turbo STT on Metal; realtime language response; local M3 custom-voice speech (cloud speech remains rollback).
MemoryHardened. Layered recall plus serialized schema-v2 durable writeback; ambiguous delivery stops rather than replaying.
Voice authorityHardened. Literal speech control is loopback-only, authenticated, idle-gated, and fenced against cancellation races.
Read-only researchLive. Only the explicit “ask Big Milo” phrase opens the research path; spoken filler prevents dead air.
Vision and gazeRecognition milestone. Attended August 17: Cindy, then Bennie on the floor, then the person he was facing. People/dog frames stay app-owned. Native tracking and expression still share one motion owner.
Everyday expressionLive. Allowlisted play_emotion only. The separate dance() tool is now allowed when someone asks; it is not an attended proof yet. A tool-only turn still forces a short spoken fallback.
Proactive greetingE3 passed. Best-vs-runner-up identity margin, absence/cooldown gates, one clear named greeting, one welcome motion, and no duplicate dispatch in the controlled arrival.
Conversation soakE5 passed. Fresh attended run: 20 completed exchanges, smooth native tracking, clean audio, no competing neck writes, and no autonomous expressive movement. Interrupted transcript revisions were not counted. A later journal audit found one completed exchange with a transient emotion tool-result history mismatch; the integrated protocol correction is now deployed and verified.
Sleep and wakeImplemented. Nap lowers the body without killing the conversation app; wake restores it.
Act-from-voiceOff by design. No email, deployment, purchases, or privileged action path.

Development status at a glance

Reachy voice development split into live, next, and deliberately parked work
August 17 afternoon: recognition of Cindy, Bennie, and Chewy is live, and speech is local on M3. E3/E5 and the protocol pairing still stand. Fallback remains parked.

The protocol bug that passed the first test

One E5 exchange sounded and moved correctly but still left a transient protocol error. Milo’s server-side expression helper had emitted play_emotion directly to the robot without first recording the matching function call in conversation history. The robot returned its result immediately, but the server had no pending call to pair it with.

The first repair looked right and passed helper-level tests: record the synthetic call before yielding it. A delayed integrated review found the miss. In the real stream, a terminal EndOfResponse object had replaced the last runtime-bearing response chunk, so the helper silently lacked the conversation context it needed. Worse, the synthetic tool appeared after response termination.

The durable correction holds the terminal marker, retains the last real response chunk, records and emits the injected call, and only then ends the response. The regression now exercises the public processing flow and proves tool-before-terminal ordering, pending history before client delivery, immediate output pairing, one terminal after multi-pass lookup, and no default-history mutation for out-of-band work. The full suite passed 726 tests; a separate reviewer reproduced normal affirmation, plain, lookup, and out-of-band flows before deployment.

Lesson: ordering guarantees belong in integration tests at the consumer boundary. A helper test can prove its own contract while missing the object lifecycle that starves it of context.

How the camera stack actually works

One lesson took a while: the camera needs a single owner per live path. Two LOCAL media clients on the Pi can fight the sensor and leave one with empty frames. The failed design had a separate broker open a second LOCAL media client. At boot it initialized before the daemon camera producer, degraded permanently to no camera, and returned HTTP 503. The repaired path keeps the conversation app as the single client: it atomically exports a JPEG from its existing live stream, and the uploader sends that frame to M4 vision. A real power cycle now proves the exported frame and proactive-identity path recover cleanly. The old broker, grabber, and external tracker remain disabled.

PieceJob
App frame exportThe conversation app atomically mirrors a JPEG from its existing media stream every 2 seconds. No second camera client.
frame_uploaderSends the app-exported JPEG to M4 vision for people/dog identity. The old broker is disabled rollback source.
Native gaze pathThe daemon tracker and conversation-app movement manager own the neck together. The external milo-head-tracker remains installed only as a disabled rollback path.
M4 vision_serviceNames people and dogs. Does not drive the neck anymore
Camera toolReads the conversation app’s own fresh media frame first; M4 latest remains the final fallback.

Early look-at was a LAN hack: M4 bbox every 2s → short head pulse → snap center when the face dropped. It felt like a tick. Moving tracking beside the camera helped, but the final jerk remained because two systems still owned the same neck. In one controlled interval, 11 external moves overlapped with 115 rejected app targets. The durable answer was one owner: the external tracker is disabled and the native movement manager composes gaze with expression.

STT: the mic was fine

Far-field VAD needed real tuning (silence thresholds, no live-transcript race). But the “Hey mama / Big Mile / Chirri” class of failures was not the array.

BeforeAfter
faster-whisper tiny.en, greedymlx-audio whisper-large-v3-turbo on M4 Metal
No vocabulary biasPrivate household and lab vocabulary prompt
Speculative reopen 3s1.5s (fewer doubled sentences)

Same room, same distance sometimes transcribed clean long sentences under tiny.en — and sometimes invented fluent wrong English. That is a small model + greedy decode, not a dead mic.

First cutover of large-v3-turbo briefly made him “deaf”: a s2s rename_args bug turned gen_kwargs into nested kwargs={} and every STT call crashed. Fixed the same night. Mic and VAD never stopped hearing — the decoder was throwing.

Reliability

Reviewers (Opus / Sonnet / Opus 5): good homebrew; make power-on boring before more toys. That sprint landed.

ItemStatus
Cold boot and watchdogPassed / live. Robot-off health is soft so an unplugged body does not cause a restart loop.
Realtime protocol and cancellationHardened. Stale output and post-cancel token races fail closed; server-injected tools are recorded before client delivery and before the terminal response marker.
Honcho writebackHardened. Private SQLite outbox, shared lock, strict permissions, retention, reconciliation hold, and content-redacted health.
Local operator statusCurrent. Human-readable deterministic text is available while JSON remains the automation default.
Provider fallbackNot yet proven end to end. This is the largest remaining appliance-grade reliability gap.
Current integrated verification726 tests passed, plus focused protocol checks and an independent reproduction of affirmation, plain, lookup second-pass, and out-of-band flows.

Operational rule: health checks expose content-redacted counts and alert codes only. Ambiguous memory writes stop for human reconciliation rather than being silently replayed.

Knowledge contract

TierAllowedNot allowed
0 BodyTalk, motion tools, camera describe, standing facts
1 Read-onlyLookup / Hermes web on ask big MiloSide effects
2 ActNot wired from voice (email, deploy, purchases…)

Deep research is still first-person Milo thinking harder — not “I asked another agent.”

Build notes worth keeping

First frame from the robot camera: living room and chocolate puppy
First good frame after the ribbon reseat. Recognition target, volunteer.

Assembly (short)

One evening + morning, ~50 steps, complete kit. Stewart-platform neck is the party trick.

Inner boxes with printed part blueprints
Blueprint boxes.
Assembled Stewart platform neck
Six rods, six motors, 6-DOF head.

Next

Voice note: the live voice is a private local custom-voice on Apple Silicon. Public credit is Milo. The clone basis does not leave the house. Cloud speech is rollback only.

New: Skip the Transcript: Direct Audio to Inkling — design note for deleting Whisper and sending the VAD clip to local Inkling-Small. Not live.

Separate architecture track: Reachy is the first adapter target for a shared voice runtime. The reviewed contract spine has continued to grow, but it still has no live provider, audio, robot driver, persistent listener, or production cutover. The working Reachy stack remains the rollback baseline. See One Milo, Many Bodies.

New: Playing with StackChan — a second Milo body on the desk. Goal is the same job as Reachy (talk, look, recognize faces). Face works; camera and speech are not on yet.