Skip to main content

Configuration

Voxa is configured mostly through environment variables, with no CLI flags: everything on this page except the interrupt policy table below is read from the process environment, which is populated (in order) from a .env file in the current directory, then from ~/.voxa/.env on top of it as overrides.

In hosted (zero-config) mode, every variable in this page is optional. The launcher generates and persists whatever it needs (an auth token, a pairing code, the relay URLs) the first time you run voxa, so a bare voxa with no .env at all works out of the box. You only need to set variables explicitly when you want to change the default behavior, such as self-hosting with your own Gemini key.

Core

NameDefaultWhat it does
VOXA_PORT8787The port the local server binds to. Also used for the tunnel target and health checks.
VOXA_HOST127.0.0.1Read by the server config, but the voxa launcher hardcodes uvicorn to bind 127.0.0.1 regardless of this value. The server never listens on your LAN by default.
VOXA_AUTH_TOKENauto-generated, persisted to ~/.voxa/auth_tokenThe shared secret the phone must present on its WebSocket connection. Required; the launcher creates one for you if it's unset.
VOXA_MODEattachWhich Claude controller drives sessions: attach runs a visible, interactive Claude session in tmux; drive runs a headless session through the Claude Agent SDK with a read-only watch log.

Transport

NameDefaultWhat it does
VOXA_RELAY_URLunset (Tailscale preferred) or https://api.voxa.spaceThe relay your laptop dials out to for pairing. When set, the relay dial-out transport is used instead of Tailscale or a tunnel. Point this at your own server_cloud deployment for a self-hosted relay.
VOXA_LIVE_PROXYunset, or wss://api.voxa.space/live in hosted modeThe metered Gemini Live proxy URL. When set, the laptop uses RemoteOperator (voice routed through the proxy) instead of talking to Google directly.
VOXA_RELAY_CODEauto-generated, persisted to ~/.voxa/relay_codeThe pairing code that matches your phone to your laptop through the relay.
VOXA_RELAY_TOKENunsetAn additional auth token passed to the relay bridge client, for relay deployments that require one.
VOXA_FORCE_RELAYunset (false)Forces the hosted-relay default even when Tailscale is available and would otherwise be preferred.

Voice

NameDefaultWhat it does
GEMINI_API_KEY""Your Google AI Studio key for Gemini Live. Required only in self-host / direct mode (when VOXA_LIVE_PROXY is unset); optional in hosted mode, where the cloud proxy uses its own key.
GEMINI_LIVE_MODELgemini-2.0-flash-live-001The Gemini Live model id used by the laptop's direct operator. Override if you have access to a newer model.
VOXA_BARGE_IN1 (on)Whether you can interrupt the operator mid-sentence using Gemini's voice activity detection. Set to 0 to restore half-duplex behavior (wait your turn).
VOXA_IDLE_TIMEOUT180Seconds of no speech or work before the voice session auto-disconnects, so an idle call doesn't keep burning minutes.

Calls and rings

NameDefaultWhat it does
VOXA_INSTALL_HOOK1 (on)Whether voxa installs the global Claude Code hook on launch. Set to 0 or false to skip it.
VOXA_INTERRUPT_CONFIG_FILE~/.voxa/interrupt_policy.jsonWhere the per-project interrupt policy (the mute switch and the rest of the ladder's tuning knobs) is stored. Override to point at a different file, mainly useful for tests or a custom install layout.
VOXA_DECISION_LOG_FILE~/.voxa/hooks.jsonlWhere Voxa appends every hook event and every interrupt decision it makes. Read it to see the reason and the human-readable why behind a ring, a notify, or a silent drop.
VOXA_NOTIFY_RULES_FILE~/.voxa/notify_rules.jsonThe legacy notification-rules file from before the interrupt ladder existed. Only ever read once, to migrate a pre-existing file into interrupt_policy.json the first time Voxa starts; new installs never write to it.
VOXA_RING_DEBOUNCE_SECONDS6Collapses rings that arrive within this window of each other into a single call, since the finish hook and the screen scraper can both report the same finish.
VOXA_APPROVAL_SCRAPE_TIMEOUT2.0Max seconds to wait for a pane scrape (building the approval card) before sending the blocked push without it. A human is already blocking, so this caps how long they wait.
VOXA_SCRAPER_GRACE_SECONDS10Grace period used by the screen scraper when confirming a finish.

Interrupt policy

Unlike everything else on this page, these are not environment variables. They live in the JSON file pointed at by VOXA_INTERRUPT_CONFIG_FILE (~/.voxa/interrupt_policy.json by default), keyed per project (by cwd) plus a reserved __default__ section: edit the file directly to change them. See call flow for how they fit into the ring / notify / silent ladder.

Anything Voxa cannot parse here falls back to the built-in default rather than failing, so a typo costs you that one setting and nothing else.

Silencing a project

mute_finish and mute_blocked are the mute switch, and they are what the phone's own per-project mute toggle writes. Both demote ring to notify: you lose the call, never the information, so a muted project is still queued and still shows up when you next answer.

KeyDefaultWhat it does
mute_finishfalseSet true to stop this project calling you when a turn finishes. Covers a clean finish and a failing build alike (a broken build is still a finish outcome).
mute_blockedfalseSet true to stop this project calling you when Claude is blocked waiting on you, including the escalation that would otherwise ring a frozen agent after block_escalate_seconds. Mute wins over that escalation outright.

Mute the whole machine by putting either key in the __default__ section, or one project by putting it under that project's cwd:

{
"__default__": { "mute_finish": false },
"/Users/you/code/noisy-project": { "mute_finish": true, "mute_blocked": true }
}

The rest of the ladder

KeyDefaultWhat it does
ring_threshold70Reserved for a future sensitivity dial, and today it does exactly one thing: at 101 or higher (Voxa's reserved "never ring" sentinel, since priority tops out at 100) a landed turn that clears the trivial floor gets a notify instead of a call. It has no effect at all on blocked or broke, so it is not how you silence a project. Use mute_finish and mute_blocked above for that.
presence_window_seconds90How recently you must have submitted a prompt anywhere in the fleet to count as "at the laptop." Within this window, a ring is demoted one rung to notify.
coalesce_seconds30How long Voxa waits after the first pending interrupt before delivering the batch, so several sessions finishing close together become one call instead of several.
min_seconds_between_rings600The minimum gap enforced between two calls. A ring that would otherwise arrive sooner is demoted to notify.
block_escalate_seconds300How long a blocked session can sit unanswered, with nobody present, before it rings regardless of the presence and rate-limit guards.
block_overridetrueWhether a long-unanswered block is allowed to override those guards at all. Set false to let a block stay demoted indefinitely instead.
escalation_tick_seconds15How often a background ticker re-checks blocked sessions for the block_escalate_seconds override, since a frozen agent stops sending hooks on its own.
fallback_min_work_seconds600Part of the trivial floor: a landed turn shorter than this, and under fallback_min_tool_calls, stays silent.
fallback_min_tool_calls15The other half of the trivial floor: a landed turn with fewer tool calls than this, and under fallback_min_work_seconds, stays silent.

Self-host push

NameDefaultWhat it does
APNS_KEY_PATH""Path to your Apple Push Notification service .p8 key file.
APNS_KEY""The .p8 key contents directly, as an alternative to APNS_KEY_PATH (useful on container hosts where mounting a file is awkward).
APNS_KEY_ID""Your APNs key id, from the Apple Developer portal.
APNS_TEAM_ID""Your Apple developer team id.
APNS_BUNDLE_ID""The app's bundle id (space.voxa.app for the official app).
APNS_SANDBOXfalseWhether to use Apple's sandbox APNs environment. Set true for Xcode/dev-signed (sideloaded) builds; false for TestFlight/App Store builds.

Push (agent-initiated calls) is only enabled once APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID, and either APNS_KEY or APNS_KEY_PATH are all set. Leave any of them unset and Voxa still works fine as a voice client, it just won't ring you.

Connected Macs

NameDefaultWhat it does
VOXA_MACHINE_NAMEyour hostnameThe display name for this laptop in the phone's Connected Macs roster.
VOXA_MACHINE_HEARTBEAT_SECONDS60How often the laptop refreshes its last_seen heartbeat in the roster.
MACHINE_ROSTER_TTL_DAYS30How long a Mac can go without a heartbeat before it's pruned from the roster.

Claude

NameDefaultWhat it does
ANTHROPIC_API_KEYunsetOptional override for Claude auth. By default, the Claude Agent SDK reuses your existing Claude Code login, so this is only needed if you want to use an API key instead.
VOXA_ISOLATE_CLAUDEunset (off)Runs the driven Claude session in an isolated config directory (no global hooks/plugins) instead of your normal Claude environment. Useful if one of your own hooks interferes with Voxa's driven session; your auth still comes from the Keychain either way.

Misc

NameDefaultWhat it does
VOXA_ADMIN_TOKENunsetGates the /admin-api/* dashboard. Leave unset to disable the dashboard entirely.
SENTRY_DSNunsetEnables Sentry error reporting when set.
VOXA_ENVproductionThe environment tag attached to Sentry reports.
VOXA_DISCOVERY_TTL2.5How long (in seconds) discovered terminal state is cached before being refreshed.
VOXA_BUSY_DECAY_SECONDS300For controllers with no screen to verify against, how long a "busy" flag is trusted after the last send before it's healed back to idle, so a wedged flag can't block dispatches forever.