yothere reads all of its configuration from environment variables through
yothere.config.settings. Nothing is required to start: the defaults give a working
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.
How configuration resolves
Configuration comes from two layers. A service wrapper first sources
$YOTHERE_HOME/yothere.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).
Core environment variables
| Variable | Meaning | Default |
|---|---|---|
YOTHERE_HOME |
writable state root for the whole fleet | ~/.yothere (falls back to an existing ~/.relay) |
YOTHERE_WORK_DIR |
the directory the agent runs jobs against (worker file and tool scope); refuses ~/Documents and ~/Desktop |
current directory |
YOTHERE_RUNNER_LABEL |
the launchd or systemd service label the watchdog keys its heartbeat check on | yothere-runner |
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.
Agent backends
A worker turn runs on the coding-agent subscription you already have. Two local harnesses
ship: claude (the Claude Code CLI) and codex (the OpenAI Codex CLI). They are peers, not
a main mode and a lesser one: same containment, same approve-send grant, same resume, same
caps. A remote harness reaches anything else that speaks the Brain Protocol.
| Variable | Meaning | Default |
|---|---|---|
YOTHERE_BRAIN |
the default brain name to resolve from brains.yaml |
unset |
YOTHERE_BRAINS_FILE |
the named-brain registry | $YOTHERE_HOME/brains.yaml |
YOTHERE_THREAD_HARNESS |
per-thread harness when no brain is named: claude, codex, or remote |
claude |
YOTHERE_WORKER_PERMISSION_MODE |
the machine’s LOCAL permission cap for how much an unattended worker turn may do without asking; the most restrictive of this cap and the cockpit’s per-machine tier wins, and the server can never raise a machine above it. default caps at the read-only (observe) tier, acceptEdits at workspace, bypassPermissions allows full; plan and dontAsk are also accepted, and an unknown value falls back to default. Lower it to scope a machine that holds sensitive data. |
bypassPermissions |
YOTHERE_WORKER_SEND_POLICY |
outward-send containment for an unattended worker turn. contained stops the worker at a blocked status before any outward action (email, push, message), the text-mode equivalent of showing a draft first. open is the explicit operator opt-out that runs it ungated. |
contained |
YOTHERE_WORKER_SEND_POLICY=open,
git push and gh stay ungrantable through the approval path: no human
approval can mint a grant for them. See the Trust page for the boundary the
send-gate does and does not enforce.Code workspaces
The git repos yothere may work in are registered with yothere workspace add <name> <path>,
not configured by env. The registry is workspaces.json next to your threads dir, and it holds
paths only: yothere never clones a repo and holds no git credential.
| Variable | Meaning | Default |
|---|---|---|
YOTHERE_WORKSPACES_FILE |
override the workspace registry path | workspaces.json beside YOTHERE_THREADS_DIR |
A thread bound to a workspace ignores YOTHERE_WORK_DIR and runs in its own git worktree under
$YOTHERE_HOME/worktrees/, on its own branch. Unbound threads keep running in YOTHERE_WORK_DIR
exactly as before. See the CLI reference.
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 }
local-codex: { harness: codex }
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.
Codex harness
Codex auth is a one-time login into the CODEX_HOME that yothere owns, not an API key and
not your personal ~/.codex (yothere never reads or writes that):
CODEX_HOME=~/.yothere/state/codex-home codex login
yothere doctor warns when that home has no auth.json and prints the exact command for
your paths.
| Variable | Meaning | Default |
|---|---|---|
YOTHERE_CODEX_HOME |
the CODEX_HOME yothere owns: its config.toml (which registers the send-deny gate), its auth, and the session rollouts a resume reads |
$YOTHERE_STATE_DIR/codex-home |
YOTHERE_CODEX_MODEL |
the model for a codex turn. A ChatGPT account rejects any -codex-suffixed id; valid values are gpt-5.5, gpt-5.4, gpt-5.4-mini |
gpt-5.5 |
YOTHERE_CODEX_BIN |
path to the codex binary. The desktop app keeps it inside its bundle (/Applications/ChatGPT.app/Contents/Resources/codex since ChatGPT.app absorbed Codex.app in mid-2026; /Applications/Codex.app/Contents/Resources/codex on older installs) and does not put it on PATH, so yothere falls back to those locations, newest first |
codex on PATH, else the desktop app bundle |
YOTHERE_CODEX_PRICE_TABLE |
JSON override for the token-to-USD price table | unset |
Parallelism
Running several agent turns at once is the point of the product, and both engines ship with the same shape: a pool of work turns, plus a separate pool for typed questions, so a reply never queues behind long work.
| Variable | Meaning | Default |
|---|---|---|
YOTHERE_LEASER_WORKERS |
concurrent work turns for the BYO leaser (yothere service). Same as --workers. |
5 |
YOTHERE_LEASER_CHAT_WORKERS |
concurrent chat turns for the leaser, on top of the work pool. Same as --chat-workers; 0 collapses it to a single pool. |
2 |
YOTHERE_MAX_CONCURRENCY |
concurrent work turns for the local advance engine (python -m yothere.runner loop). |
5 |
YOTHERE_CHAT_CONCURRENCY |
concurrent chat turns for the local runner, on top of the work pool. | 3 |
YOTHERE_MAX_TURN_CONTINUATIONS |
consecutive turn-budget overruns before a thread stops and asks for help instead of grinding. A clean turn resets the counter. | 20 |
Cost caps
The runner and the hosted planner both stop advancing turns once a cap is reached. A capped thread comes back to the Inbox as an answerable card, so you can continue it, raise its cap, or stop it.
| Variable | Meaning | Default |
|---|---|---|
YOTHERE_DAILY_COST_CAP_USD |
daily fleet spend ceiling in USD. A non-numeric value is ignored and the default stands. | 10.0 |
YOTHERE_PER_THREAD_CAP_USD |
per-thread spend ceiling in USD. A per-thread override lives in the thread’s own meta, and that is what “raise the cap” writes. | 10.0 |
Voice
Voice is a hosted service. You configure nothing and you bring no key: click Connect in the cockpit and the control plane mints a short-lived per-call token for the session. There is no key to set, no phone number, and nothing to install.
The one exception is the loopback dev path, yothere voice --local, which boots a media host
on 127.0.0.1:8767.
| Variable | Meaning |
|---|---|
YOTHERE_VOICECALL_BEARER |
bearer token gating the local media host’s protected routes; when unset, every protected route returns 403 (fails closed) |
YOTHERE_ALLOW_BOB_HARNESS |
set to 1 to opt in to the headless Claude Code harness on the local voice surface. Off by default. |
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
yothere doctor reports a hard FAIL.Output language
The agent’s output language is a per-workspace setting, not an environment variable. Choose
English or Español (Latinoamérica) in the cockpit at /settings, and the agent plans, asks,
reports, and speaks in it. English is the default for every account, and /settings is the
only place the choice is made: there is no auto-detection and no Accept-Language sniffing.
Prose follows your language; the machine contract underneath (thread states, the ask kinds a daemon reads) stays English by design, so a Spanish thread still rings you exactly like an English one.
Run modes
YOTHERE_AUTH_MODE selects the deployment mode.
| Mode | Behavior |
|---|---|
off (default) |
single-user local: no login, a loopback carve-out, one YOTHERE_HOME. This is the dev and quickstart path. |
hosted |
multi-tenant: the cockpit requires a login, each account is scoped to its own per-tenant home, and new users sign up at /signup, then /onboarding. This is how app.yothere.ai runs. |
Signup is open self-serve. In hosted mode an operator can also mint invite codes from the CLI,
for example to pre-provision a specific account with a set budget. 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 live at ~/.yothere-<tenant> under YOTHERE_TENANTS_ROOT (default: the
parent of YOTHERE_HOME). This is an implementation detail of hosted mode, not something you
set on your own machine.
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.