| name | fix-clawbody-openclaw-compatibility |
| description | Repair ClawBody on a Wireless Reachy Mini when it cannot connect to a current OpenClaw gateway or OpenAI Realtime API. Use for `protocol mismatch`, `control ui requires device identity`, `beta_api_shape_disabled`, an app stuck reconnecting to Realtime, or a robot on a routed network that must reach a loopback-only gateway securely. |
Fix ClawBody OpenClaw Compatibility
Keep the OpenClaw gateway loopback-only, use an authenticated tunnel when the robot is remote, and patch ClawBody only after reproducing the specific handshake failure. Upstream may incorporate these fixes, so inspect the installed source before editing.
Establish the network path
-
Confirm Reachy's daemon is healthy at http://<robot>:8000/api/daemon/status.
-
Prefer a reverse SSH tunnel from the gateway host to Reachy when the gateway host can route to the robot:
ssh -NT -o BatchMode=yes -o ExitOnForwardFailure=yes \
-R 127.0.0.1:18789:127.0.0.1:18789 pollen@<robot>
-
Configure ClawBody with OPENCLAW_GATEWAY_URL=ws://127.0.0.1:18789 and the gateway token. Store .env as mode 0600.
-
Make the tunnel persistent with the host's service manager. Use keepalive options and a dedicated SSH key; never commit the key or tokens.
This preserves gateway.bind: "loopback" and exposes the forwarded port only on Reachy's loopback interface.
Repair the OpenClaw handshake
Run a bridge-only connection test before starting the robot app. Apply each repair only when its matching error appears.
protocol mismatch
ClawBody commit 37852552f9e6045867cceee6eb05711311831a17 hard-codes protocol 3. OpenClaw 2026.6.10 requires protocol 4. Confirm the gateway's expected version from its installed message handler or error details, then set ClawBody's PROTOCOL_VERSION to that value.
control ui requires device identity
ClawBody incorrectly identifies its programmatic client as the browser Control UI. In openclaw_bridge.py:
- set client ID to
gateway-client;
- set client mode to
backend;
- request only
operator.read and operator.write;
- remove the synthetic WebSocket
Origin header.
Keep token authentication. Do not disable OpenClaw device-auth or origin security globally to accommodate this client bug.
Repair the OpenAI Realtime handshake
For invalid_request_error.beta_api_shape_disabled, migrate openai_realtime.py from the beta to the GA SDK shape:
- use
client.realtime.connect(...), not client.beta.realtime.connect(...);
- set session
type to realtime and output_modalities to ["audio"];
- nest input/output formats, transcription, voice, and VAD below
audio;
- use 24 kHz PCM:
{"type": "audio/pcm", "rate": 24000};
- rename response events to
response.output_audio.delta and response.output_audio_transcript.{delta,done}.
Use a current Realtime model alias such as gpt-realtime. The GA session configuration has this shape:
await conn.session.update(session={
"type": "realtime",
"model": model,
"output_modalities": ["audio"],
"instructions": "Render only explicitly supplied OpenClaw text verbatim.",
"audio": {
"input": {
"format": {"type": "audio/pcm", "rate": 24000},
"transcription": {"model": "whisper-1"},
"turn_detection": {
"type": "server_vad",
"create_response": False,
"interrupt_response": False,
},
},
"output": {
"format": {"type": "audio/pcm", "rate": 24000},
"voice": "cedar",
},
},
"tools": [],
"tool_choice": "none",
})
Make OpenClaw own every voice turn
Do not load identity or memory into Realtime and do not expose robot or OpenClaw
tools there. Realtime is a speech transport, not a second agent:
- Configure server VAD with
create_response: false and
interrupt_response: false so speech events and transcription continue without
an automatic assistant response.
- On
conversation.item.input_audio_transcription.completed, send the transcript
to OpenClaw using the exact session key agent:main:reachy.
- Let OpenClaw load its canonical workspace files and use its normal skills,
memories, and tools. Add one narrow Dylan-equivalent full-action exception for
this exact authenticated physical session; retain messaging policy elsewhere.
- Render only OpenClaw-authored text through the dedicated OpenAI Speech endpoint
with PCM output. Buffer OpenClaw's complete final response and send it through one
Speech request, then stream that request's PCM bytes directly to playback. Do not
split a response across separate TTS requests: voice cadence and prosody reset at
each boundary and can produce audible gaps or stutter. Continue consuming the
gateway's incremental assistant events into the text buffer: some gateway runs do
not emit a separate completion event, so switching to a final-event-only chat path
can leave the robot waiting indefinitely after transcription. Do not send the text to a
conversational Realtime response: even an out-of-band response can interpret it
as a new user turn and add words.
- Add an owner-only control-socket
speak command for proactive speech from other
OpenClaw sessions. Do not call it inside agent:main:reachy, whose final response
is already rendered automatically.
- Extend microphone suppression as each PCM chunk is queued, including all audio
already waiting to play plus a short tail; otherwise Reachy's speaker can trigger
VAD and feed its own words back to OpenClaw.
On Reachy Mini Wireless 1.9, the local GStreamer playback appsrc expects float32
stereo shaped as (frames, 2) at 16 kHz. OpenAI Speech PCM is mono; resample the
mono frames, duplicate them into both channels, and preserve the two-dimensional
shape when calling push_audio_sample. Do not flatten the stereo buffer: the SDK
uses shape[0] as the frame count, and a flattened buffer can turn 100 ms of mono
into 50 ms of stereo data on a 100 ms timeline, producing choppy playback. If an
initial PCM reserve moves an audible interruption to exactly the reserve boundary,
the live producer is still catching the physical sink. Buffer the complete Speech
PCM response and submit it to Reachy as one contiguous clip. This increases response
latency but removes both network underruns and the artificial prebuffer boundary.
For a low-latency physical session, include thinking: "minimal" and fastMode: true
in Reachy's chat.send request; unlike sessions.patch, these per-turn fields do not
require the bridge to request operator.admin. Do not lower the defaults for messaging
or other OpenClaw sessions, and do not broaden the robot client's gateway scopes. To use
a smaller model only for the physical session, first add it to
agents.defaults.models, then run sessions.patch for the exact
agent:main:reachy key from a local authenticated admin client. Keep that admin action
off the robot. If the gateway emits assistant text but no completion event, use a short,
configurable post-text settle window based on observed event timing rather than a
multi-second generic timeout. Use a
supported low-latency transcription model, specify the input language, and tune
server-VAD silence conservatively so normal pauses are not clipped.
This leaves one source of truth for personality and memory and prevents Realtime
from competing with OpenClaw for a turn or altering OpenClaw's final response.
Keep a distinct processing pose active from completed transcription until the TTS
request has returned playable audio. A fixed upward/side tilt with asymmetric antenna
motion reads more clearly than a subtle idle sway and tells the user not to interrupt.
Enable face tracking on Reachy Mini Wireless
Reachy Mini Wireless 1.9+ provides daemon-side tracking through
ReachyMini.start_head_tracking(weight=...). Prefer this over installing MediaPipe:
the available ARM MediaPipe build requires NumPy 1.x, while Reachy Mini 1.9 requires
NumPy 2.x. Arm daemon tracking at a subtle idle weight such as 0.25. On Realtime
speech_started, set a weight around 0.85; on speech_stopped, immediately
return it to 0.25. This makes Reachy look directly at the speaker while listening
and retain gentle awareness without overpowering OpenClaw movement after the
utterance ends. Call stop_head_tracking() during app cleanup.
Set ENABLE_FACE_TRACKING=true and use a dedicated tracker type such as
HEAD_TRACKER_TYPE=daemon so ClawBody does not also initialize YOLO or MediaPipe.
Restarting the ClawBody app is sufficient; a full robot reboot is not required.
Verify both Speech-gated face tracking active and released in the app log.
Provide dependency-free dances
Do not report an emotion fallback as a successful dance. The optional
reachy_mini_dances_library may be absent, and names such as wave, nod, and
bounce are not emotion names. Provide built-in Move implementations for every
advertised dance, keep daemon tracking at no more than idle weight 0.25, and
include backend: "builtin" in a successful result.
When Reachy Mini 1.8.4+ is installed, load the official datasets through
RecordedMoves: pollen-robotics/reachy-mini-dances-library and
pollen-robotics/reachy-mini-emotions-library. Expose the complete live catalog,
not a short enum. Emotion presets have synchronized audio sidecars; start the sound
when queueing the move and suppress microphone forwarding for the move duration.
The official dance dataset is motion-only, while dance1 through dance3 in the
emotion dataset are vocalized dance presets.
Verify
Require all of the following:
- The gateway still listens only on
127.0.0.1:18789/::1:18789.
- Reachy's
curl http://127.0.0.1:18789/health returns a live response through the tunnel.
- A bridge-only authenticated
chat.send returns a known marker without error.
- Reachy's app status reports ClawBody
running.
- Daemon logs contain
OpenClaw gateway connected and report Realtime as speech transport with automatic responses disabled and zero tools.
.env remains owned by the Reachy user and mode 0600.
- A direct voice turn appears in the exact OpenClaw session and only OpenClaw's completed final text is spoken.
- Face tracking activates on speech, releases after speech, and a built-in dance then moves visibly.
- Logs show a complete PCM buffer before the first speaker push, and playback receives
(frames, 2) stereo without dropped GStreamer buffers.
References