Skip to main content

Realtime Voice

Realtime voice puts a speech-to-speech model in front of the normal pipeline, so small talk gets an instant answer while real work still goes to your agent.

┌──────────────────────────────┐
mic ──► │ realtime model (Gemini Live, │ ──► answers small talk itself
│ OpenAI Realtime, GPT-Live) │
└──────────────┬───────────────┘
│ delegate_to_main(...)
▼
os-server ──► agent runtime (OpenClaw, Hermes, …) ──► TTS

For each turn the realtime model either answers directly, calls delegate_to_main to hand the request to your agent runtime (tools, skills, memory), or rejects it as noise. With realtime disabled, every turn goes the plain STT → agent → TTS path.

By default the realtime model's reply is re-spoken through your configured TTS voice, so the Intern sounds the same whichever path answered. Delegated turns are always spoken by TTS.

Providers​

providerModel defaultVoicesReasoning knob
geminigemini-3.8-live-extended-thinkingPuck, Charon, Kore, Fenrir, AoedeLOW · MEDIUM · HIGH (MINIMAL is accepted but the default model doesn't support it)
openaigpt-realtime-2alloy, ash, coral, echo, fable, onyx, nova, sage, shimmerminimal · low · medium · high · xhigh
gptlivegpt-live-1marin, quartz, ripple, vesper, willow, stone, gleam, meridian, bossa, tempo, beacon, delta, cinder—
pipecat_v1qwen/qwen3.6-35b-a3b— (text out, spoken by HAL TTS)— (has web_search on/off)
none——Realtime off

The authoritative list for your firmware: GET /api/device/realtime-options.

Enable / change it​

Web UI: http://<device-ip>/setting?debug=true#realtime → Realtime.

API:

# Gemini Live, low thinking
curl -s -X PUT $DEVICE/api/device/config \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"realtime":{"enabled":true,"provider":"gemini","voice":"Kore","reasoning":"LOW"}}'

# OpenAI Realtime
curl -s -X PUT $DEVICE/api/device/config \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"realtime":{"enabled":true,"provider":"openai","voice":"alloy","reasoning":"minimal"}}'

# Off
curl -s -X PUT $DEVICE/api/device/config \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"realtime":{"enabled":false}}'

realtime fields: enabled, provider, model, voice, reasoning, api_key, base_url, web_search (pipecat_v1 only). Omitted fields stay unchanged. Any realtime change restarts HAL.

In config.json the block looks like this. Until it's edited once, os-server rewrites it from the defaults on every start; an edit through the API or UI sets "pinned": true to stop that. If you edit the block by hand, set "pinned": true yourself or your change is reset on the next restart:

"realtime": {
"enabled": true,
"provider": "gemini",
"api_key": "",
"base_url": "",
"gemini": { "model": "gemini-3.8-live-extended-thinking", "voice": "Kore", "thinking_level": "LOW", "google_search": true },
"openai": { "model": "gpt-realtime-2", "voice": "alloy", "reasoning_effort": "minimal" },
"gptlive": { "model": "gpt-live-1", "voice": "marin" },
"pipecat_v1": { "model": "qwen/qwen3.6-35b-a3b", "web_search": true },
"pinned": true
}

Keys and endpoints​

Empty api_key / base_url fall back to the LLM credentials: <llm_base_url>/ws/gemini for Gemini and <llm_base_url>/ws/openai for OpenAI/GPT-Live — the Autonomous proxy relays. A base_url you set is used as-is, so include the path (…/ws/gemini or …/ws/openai). GPT-Live can also take its own realtime.gptlive.api_key / base_url. If you point the LLM at your own provider, pin realtime to Autonomous first (see that page's Step 1), or give it your own provider key with the env overrides below.

Hear the model's own voice​

To play the realtime model's native audio instead of re-speaking through TTS (lower latency, but a different voice from the delegated turns), set in /opt/hal/.env:

HAL_REALTIME_NATIVE_AUDIO=true

This is automatic when both tts_provider and the realtime provider are gemini.

Environment overrides (/opt/hal/.env)​

Env vars win over the config.json block. Restart HAL after changing them.

General

VarDefaultEffect
HAL_REALTIME_ENABLEDfrom configForce on/off
HAL_REALTIME_PROVIDERfrom configgemini · openai · gptlive · pipecat_v1 · none
HAL_REALTIME_TURN_DETECTIONoffoff = HAL's own VAD decides turns · server_vad / semantic_vad = the provider decides (ignored by GPT-Live)
HAL_REALTIME_NATIVE_AUDIOfalsePlay the model's voice instead of TTS
HAL_REALTIME_REQUIRE_TRANSCRIPTtrueCost guard: drop turns with no transcript
HAL_REALTIME_NOISE_SPEECH_RATIO0.55Cost guard: drop turns that are mostly noise
HAL_REALTIME_MIN_COMMIT_DURATION_S0.8Cost guard: ignore very short blips

Gemini Live

VarEffect
GEMINI_API_KEY / GOOGLE_API_KEYKey (else realtime.api_key, else llm_api_key)
HAL_GEMINI_LIVE_BASE_URLEndpoint (else <llm_base_url>/ws/gemini)
HAL_GEMINI_LIVE_MODEL, HAL_GEMINI_LIVE_VOICEModel and voice
HAL_GEMINI_THINKING_LEVELMINIMAL · LOW · MEDIUM · HIGH
HAL_GEMINI_GOOGLE_SEARCHLet the model search the web

OpenAI Realtime

VarEffect
OPENAI_API_KEYKey
HAL_OPENAI_REALTIME_BASE_URLEndpoint (else <llm_base_url>/ws/openai)
HAL_OPENAI_REALTIME_MODEL, HAL_OPENAI_REALTIME_VOICEModel and voice
HAL_OPENAI_REASONING_EFFORTminimal … xhigh
HAL_OPENAI_TRANSCRIBE_MODELModel for the input transcript
HAL_OPENAI_NOISE_REDUCTIONe.g. far_field
HAL_OPENAI_VAD_THRESHOLDServer VAD threshold

GPT-Live (OpenAI /v1/live/sessions)

VarEffect
OPENAI_API_KEYKey
HAL_GPTLIVE_BASE_URLEndpoint (else the OpenAI Realtime base)
HAL_GPTLIVE_MODEL, HAL_GPTLIVE_VOICEModel (gpt-live-1) and voice
HAL_GPTLIVE_SAMPLE_RATE16000
HAL_GPTLIVE_DELEGATIONauto · client · responses
HAL_GPTLIVE_WEB_SEARCHWeb search tool

Pipecat v1

HAL_PIPECAT_API_KEY, HAL_PIPECAT_BASE_URL, HAL_PIPECAT_MODEL, HAL_PIPECAT_TEMPERATURE, HAL_PIPECAT_MAX_TOKENS.

Live (full-duplex) mode​

In Live mode the provider owns turn-taking entirely: a session stays open and the provider's VAD decides when you've finished speaking.

# /opt/hal/.env
HAL_LIVE_MODE=true

This forces server_vad turn detection and applies to the whole process; switching needs a HAL restart.

VarDefaultEffect
HAL_LIVE_VAD_START_SENSITIVITYlowHow easily speech starts a turn
HAL_LIVE_VAD_END_SENSITIVITY—How quickly a pause ends it
HAL_LIVE_VAD_PREFIX_PADDING_MS300Audio kept before speech start
HAL_LIVE_VAD_SILENCE_MS—Silence that ends a turn
HAL_LIVE_IDLE_HANGUP_S15Close the session after this much silence
HAL_LIVE_MAX_S600Max session length
HAL_LIVE_UPLINK_DURING_PLAYBACKmuteWhat the provider hears while the Intern is talking — see below
Voice barge-in is experimental

With the default HAL_LIVE_UPLINK_DURING_PLAYBACK=mute, the provider hears silence while the Intern speaks, so you can't interrupt it by voice — use the button. cancelled sends echo-cancelled audio (true full duplex, but only as good as the echo canceller, which currently isn't good enough on most units). always sends raw mic audio and lets the provider separate you from the Intern's own voice — the only mode where voice barge-in reliably triggers, but the model may hear itself and cut its own replies short. If you try always, lower the speaker volume.

Deeper protocol details: docs/realtime-voice.md.