yothere reads all of its configuration from environment variables through
yothere.config.settings. Nothing is required to start: the defaults give a working,
single-user, local setup. This page lists the variables that matter, the brains.yaml
registry, the two run modes, and how the legacy RELAY_* names still resolve. For the
setup flow, see Onboarding; to run it as a service, see
Self-hosting.
How configuration resolves
Configuration comes from two layers. A service wrapper first sources
$YOTHERE_HOME/relay.env (a mode-600 KEY=value file) into the environment, then the
process environment applies on top. settings reads env at import time, and every
writable path roots at YOTHERE_HOME (default ~/.yothere, falling back to an existing
~/.relay). Secrets for the voice and brain surface live in a separate mode-600 file, so
generated unit files never carry a secret.
Core environment variables
| Variable | Meaning | Default |
|---|---|---|
YOTHERE_HOME |
writable state root for the whole fleet | ~/.yothere (falls back to an existing ~/.relay) |
YOTHERE_RUNNER_LABEL |
the launchd or systemd service label the watchdog keys its heartbeat check on | relay-runner |
YOTHERE_WORK_DIR |
the directory the harness runs jobs against (worker file and tool scope); refuses ~/Documents and ~/Desktop |
current directory |
YOTHERE_NOTIFIER |
needs-eyes ping channel: telegram, command, or none |
none |
YOTHERE_NOTIFY_PY |
path to the telegram bridge script (used when YOTHERE_NOTIFIER=telegram) |
unset |
The command notifier reads its shell command from YOTHERE_NOTIFIER_CMD (the message is
passed as the last argument). The derived roots YOTHERE_DATA_DIR, YOTHERE_STATE_DIR,
YOTHERE_LOG_DIR, YOTHERE_THREADS_DIR, and YOTHERE_VAULT_DIR default to subdirectories
of YOTHERE_HOME and rarely need overriding.
Voice environment variables
Only the voice surface (the [voice] extra) reads these. GEMINI_API_KEY drives Gemini
Live and is shared with the LLM router, so it can live in a shared LLM env file rather than
being duplicated. The Twilio credentials live in the voice env file.
| Variable | Meaning |
|---|---|
GEMINI_API_KEY |
Google AI Studio key for Gemini Live (cockpit voice) |
DEEPGRAM_API_KEY |
legacy STT key, off the call path now, still parsed for back-compat |
TWILIO_ACCOUNT_SID |
Twilio account SID (inbound and outbound phone) |
TWILIO_AUTH_TOKEN |
Twilio auth token |
TWILIO_NUMBER |
the Twilio phone number |
YOTHERE_VOICE_PUBLIC_WSS |
the public wss:// host Twilio dials into (the cloudflared tunnel hostname) |
YOTHERE_VOICECALL_BEARER |
bearer token gating the dangerous surface; when unset, every protected route returns 403 (fails closed) |
YOTHERE_TRUST_TAILNET_IDENTITY |
set to 1 so a browser on your tailnet connects without the bearer in the URL (>= 1.4.0) |
YOTHERE_ALLOW_BOB_HARNESS |
set to 1 to opt in to the bob (headless Claude Code) harness on the voice surface |
127.0.0.1:8767 by default. If you bind a non-loopback address you MUST set
YOTHERE_VOICECALL_BEARER (or run hosted auth), or the server fails closed and
doctor reports a hard FAIL. See Self-hosting.The brains.yaml file
A brain is whatever does the work; yothere drives it over the Brain Protocol.
$YOTHERE_HOME/brains.yaml maps names to connection descriptors:
default: local-claude
brains:
local-claude: { harness: claude }
remote-brain: { harness: remote, url: "wss://host/brain", token_env: "MY_BRAIN_TOKEN" }
A thread targets a brain by its brain meta key; otherwise YOTHERE_BRAIN, then the
file’s default, then local-claude. A remote (non-loopback) endpoint must require a
token and should be wss://. Reference the token with token_env: so the secret is not in
the file, and treat brains.yaml like an SSH key (do not commit it). yothere init seeds
this file only when it does not already exist.
Run modes
YOTHERE_AUTH_MODE selects the deployment mode.
| Mode | Behavior |
|---|---|
off (default) |
single-user local: no login, a loopback and tailnet carve-out, one YOTHERE_HOME. Byte-identical to the pre-auth cockpit, so local use stays zero-config. |
hosted |
multi-tenant: the cockpit requires a login, each account is scoped to its own per-tenant home, and signup is invite-gated (/signup, then /onboarding). |
In hosted mode, mint invite codes from the CLI. Each redemption at /signup creates
exactly one account, stamped with the daily cost cap you set:
yothere invite create --uses 1 --budget 5 --email [email protected]
Per-tenant homes currently live at ~/.relay-<tenant> on disk. That path prefix is not
covered by the env shim below and is renamed separately from the environment variables, so
it stays .relay- for now.
Legacy RELAY_ aliases
Every YOTHERE_* variable also accepts its RELAY_* sibling. An env shim mirrors both
prefixes at package import, and again when parsing an env file, so a config keyed either
way resolves regardless of which name a reader asks for. When both names are present, the
reader’s requested name wins (the shim only fills a name that is unset and never overwrites
an explicit value).
# these are equivalent
export YOTHERE_WORK_DIR=~/code/my-project
export RELAY_WORK_DIR=~/code/my-project
New configs should use the YOTHERE_* names. The legacy RELAY_* names stay supported for
at least one release.