openclaw/docs/nodes/talk.md
Peter Steinberger 20816f676f
feat(voice): universal talk waveform driven by real audio levels (#102901)
* feat(voice): universal talk waveform driven by real audio levels

One shared Siri-style talk animation across iOS, watchOS, macOS, and
Android (TalkWaveformView + exact Compose port), replacing per-platform
fakes: synthetic speaking pulses, constant speech-detected power, static
Android bar rows, flat realtime listening. Listening/recording follow
live mic levels on every route; agent speech follows the real playback
envelope (AVAudioPlayer metering + playback-aligned PCM envelope +
WebRTC stats); voice-note recording shows a live capture wave.

* fix(android): order waveform imports and sync native i18n inventory

* chore(i18n): resync native inventory after rebase
2026-07-09 16:33:25 +01:00

14 KiB

summary read_when title
Talk mode: continuous speech conversations across local STT/TTS and realtime voice
Implementing Talk mode on macOS/iOS/Android
Changing voice/TTS/interrupt behavior
Talk mode

Talk mode covers five runtime shapes:

  • Native macOS/iOS/Android Talk: local speech recognition, Gateway chat, and talk.speak TTS. Nodes advertise the talk capability and declare which talk.* commands they support.
  • iOS Talk (realtime): client-owned WebRTC for OpenAI realtime configs that select webrtc transport or omit transport. Explicit gateway-relay, provider-websocket, and non-OpenAI realtime configs stay on the Gateway-owned relay; non-realtime configs use the native speech loop.
  • Browser Talk: talk.client.create for client-owned webrtc/provider-websocket sessions, or talk.session.create for Gateway-owned gateway-relay sessions. managed-room is reserved for Gateway handoff and walkie-talkie rooms.
  • Android Talk (realtime): opt in with talk.realtime.mode: "realtime" and talk.realtime.transport: "gateway-relay". Otherwise Android stays on native speech recognition, Gateway chat, and talk.speak.
  • Transcription-only clients: talk.session.create({ mode: "transcription", transport: "gateway-relay", brain: "none" }), then talk.session.appendAudio, talk.session.cancelTurn, and talk.session.close for captions/dictation without an assistant voice response. One-shot uploaded voice notes still use the media understanding audio path.

Native Talk is a continuous loop: listen for speech, send the transcript to the model through the active session, wait for the response, then speak it via the configured Talk provider (talk.speak).

Client-owned realtime Talk forwards provider tool calls through talk.client.toolCall instead of calling chat.send directly. While a realtime consult is active, clients can call talk.client.steer or talk.session.steer to classify spoken input as status, steer, cancel, or followup. Accepted steering queues into the active embedded run; rejected steering returns a reason such as no_active_run, not_streaming, or compacting.

Transcription-only Talk emits the same Talk event envelope as realtime and STT/TTS sessions, but uses mode: "transcription" and brain: "none". All Talk sessions broadcast events on the talk.event channel; clients subscribe to it for partial/final transcript updates (transcript.delta/transcript.done) and other session telemetry.

Behavior (macOS)

  • Always-on overlay while Talk mode is enabled.
  • Listening → Thinking → Speaking phase transitions.
  • On a short pause (silence window), the current transcript is sent.
  • Replies are written to WebChat (same as typing).
  • Interrupt on speech (default on): if the user talks while the assistant is speaking, playback stops and the interruption timestamp is noted for the next prompt.

Voice directives in replies

The assistant can prefix a reply with a single JSON line to control voice:

{ "voice": "<voice-id>", "once": true }

Rules:

  • First non-empty line only; the JSON line is stripped before TTS playback.
  • Unknown keys are ignored.
  • once: true applies to the current reply only; without it, the voice becomes the new Talk mode default.

Supported keys: voice / voice_id / voiceId, model / model_id / modelId, speed, rate (WPM), stability, similarity, style, speakerBoost, seed, normalize, lang, output_format, latency_tier, once.

Config (~/.openclaw/openclaw.json)

{
  talk: {
    provider: "elevenlabs",
    providers: {
      elevenlabs: {
        voiceId: "elevenlabs_voice_id",
        modelId: "eleven_v3",
        outputFormat: "mp3_44100_128",
        apiKey: "elevenlabs_api_key",
      },
      mlx: {
        modelId: "mlx-community/Soprano-80M-bf16",
      },
      system: {},
    },
    speechLocale: "ru-RU",
    silenceTimeoutMs: 1500,
    interruptOnSpeech: true,
    realtime: {
      provider: "openai",
      providers: {
        openai: {
          apiKey: "openai_api_key",
          model: "gpt-realtime-2.1",
          speakerVoice: "cedar",
        },
      },
      instructions: "Speak warmly and keep answers brief.",
      mode: "realtime",
      transport: "webrtc",
      brain: "agent-consult",
    },
  },
}
Key Default Notes
provider - Active Talk TTS provider. Use elevenlabs, mlx, or system for macOS-local playback paths.
providers.<id>.voiceId - ElevenLabs falls back to ELEVENLABS_VOICE_ID / SAG_VOICE_ID, or the first available voice with an API key.
providers.elevenlabs.modelId eleven_v3
providers.mlx.modelId mlx-community/Soprano-80M-bf16
providers.elevenlabs.apiKey - Falls back to ELEVENLABS_API_KEY (or gateway shell profile if available).
speechLocale device default BCP 47 locale id for on-device Talk speech recognition on iOS/macOS.
silenceTimeoutMs 700 ms macOS/Android, 900 ms iOS Pause window before Talk sends the transcript.
interruptOnSpeech true
outputFormat pcm_44100 macOS/iOS, pcm_24000 Android Set mp3_* to force MP3 streaming.
consultThinkingLevel unset Thinking level override for the agent run behind realtime openclaw_agent_consult calls.
consultFastMode unset Fast-mode override for realtime openclaw_agent_consult calls.
realtime.provider - openai for WebRTC, google for provider WebSocket, or a bridge-only provider through Gateway relay.
realtime.providers.<id> - Provider-owned realtime config. Browsers receive only ephemeral/constrained session credentials, never a standard API key.
realtime.providers.openai.speakerVoice alloy Built-in OpenAI Realtime voice id (the older voice key still works but is deprecated). Current gpt-realtime-2.1 voices: alloy, ash, ballad, cedar, coral, echo, marin, sage, shimmer, verse; marin and cedar are recommended for best quality.
realtime.transport - webrtc: client-owned OpenAI WebRTC on iOS and in the browser. provider-websocket: browser-owned, stays on Gateway relay on iOS. gateway-relay: keeps provider audio on the Gateway; Android uses realtime only with this transport.
realtime.brain - agent-consult routes realtime tool calls through Gateway policy; direct-tools is legacy direct-tool compatibility; none is for transcription/external orchestration.
realtime.consultRouting - provider-direct preserves the provider's direct reply when it skips openclaw_agent_consult; force-agent-consult routes finalized user transcripts through OpenClaw instead.
realtime.instructions - Appends provider-facing system instructions to OpenClaw's built-in realtime prompt (voice style/tone); the default openclaw_agent_consult guidance stays.

talk.catalog exposes canonical provider ids and registry aliases, each provider's valid modes/transports/brain strategies/realtime audio formats/capability flags, and the runtime-selected readiness result. First-party Talk clients should read that catalog instead of maintaining provider aliases locally; treat an older Gateway that omits group readiness as unverified rather than definitively unconfigured. Streaming transcription providers are discovered through talk.catalog.transcription; the current Gateway relay uses the Voice Call streaming provider config until a dedicated Talk transcription config surface ships.

macOS UI

  • Menu bar toggle: Talk
  • Config tab: Talk Mode group (voice id + interrupt toggle)
  • Overlay: the orb renders the universal talk waveform (shared with iOS, watchOS, and Android). Listening follows the live mic level, Speaking follows the actual TTS playback envelope, Thinking breathes softly. Click the orb to pause/resume, double-click to stop speaking, click X to exit Talk mode.

Android UI

  • Voice tab toggle: Talk
  • Manual Mic and Talk are mutually exclusive capture modes.
  • Manual Mic and realtime Talk prefer a connected Bluetooth Classic or BLE headset microphone; if it disconnects, the app requests another headset input or falls back to the default microphone, restoring the default preference once capture stops.
  • Manual Mic stops when the app leaves the foreground or the user leaves the Voice tab.
  • Talk Mode keeps running until toggled off or the node disconnects, using Android's microphone foreground-service type while active.
  • Android supports pcm_16000, pcm_22050, pcm_24000, and pcm_44100 output formats for low-latency AudioTrack streaming.

Notes

  • Requires Speech + Microphone permissions.
  • Native Talk uses the active Gateway session and only falls back to history polling when response events are unavailable.
  • The gateway resolves Talk playback through talk.speak using the active Talk provider. Android falls back to local system TTS only when that RPC is unavailable.
  • macOS local MLX playback uses the bundled openclaw-mlx-tts helper when present, or an executable on PATH. Set OPENCLAW_MLX_TTS_BIN to point at a custom helper binary during development.
  • Voice directive value ranges (ElevenLabs): stability, similarity, and style accept 0..1; speed accepts 0.5..2; latency_tier accepts 0..4.