Self-hosting

Run yothere as an always-on service on your own machine, and validate it with doctor.

Beta · access required

yothere is in invite-only beta. The commands below work once you have access.Request access and we'll get you set up.

yothere is local-first: the runner, its watchdog, and the cockpit all run on your own machine, and the network-facing surface binds loopback by default. This page installs the always-on services from your live settings and validates them with doctor. For the variables these services read, see Configuration; for the setup flow that precedes them, see Onboarding.

Overview

Three always-on pieces keep a fleet moving: a runner that advances every thread, a watchdog that alerts you if the runner goes stale, and an optional cockpit and voice server. An optional cloudflared tunnel fronts the cockpit when you want to reach it from a phone or Twilio. Everything roots at YOTHERE_HOME and is generated from your live settings, so two hosts on one machine differ only by config.

Install the services

python -m yothere.service install generates the wrappers and units from your settings and loads them: launchd user agents on macOS, systemd user units on Linux. The plan builder is pure, so --dry-run prints every file path and command it would run, verbatim.

# print the exact artifacts, change nothing
python -m yothere.service install --cockpit --dry-run

# install the runner and watchdog (plus the cockpit/voice unit with --cockpit)
python -m yothere.service install --cockpit

Flags: --cockpit also installs the cockpit and voice unit (needs the [voice] extra), --dry-run prints the plan and runs nothing, --env-file <path> sets the env file the wrappers source (default $YOTHERE_HOME/relay.env), and --python <path> pins the interpreter the services run with.

python -m yothere.service uninstall   # stop and remove units and wrappers; keeps relay.env and threads/
python -m yothere.service status      # liveness of each installed unit
Note There is no yothere service or yothere doctor subcommand. Both run as modules: python -m yothere.service and python -m yothere.doctor.

The four services

Service Module Role
runner yothere.runner loop the headless advance loop; keep-alive (launchd KeepAlive / systemd Restart=always)
runner-watchdog yothere.runner_watchdog a one-shot heartbeat check that runs about every 60 seconds; sends ONE alert if the runner heartbeat goes stale, then stays quiet for a suppression window. It deliberately does not self-heal
voice yothere.voicecall.pipeline serve the /overview cockpit plus voice surface; binds 127.0.0.1:8767; needs the [voice] extra; installed only with --cockpit
tunnel cloudflared (host adapter) a cloudflared tunnel fronting the voice surface at a public wss:// host for Twilio inbound; optional

service install generates the first three units (runner, watchdog, and the voice unit with --cockpit). The tunnel is a separate cloudflared unit you add through the host adapter, not something service install writes. The watchdog exists because launchd KeepAlive silently respawns a crash-looping runner forever: a stale heartbeat means restarts are not sticking, which is exactly the failure you need surfaced rather than papered over.

Validate

python -m yothere.doctor runs read-only checks and reports pass, warn, or fail with a one-line fix for anything not passing. It exits non-zero on any failure, so scripts can gate on it.

python -m yothere.doctor
python -m yothere.doctor --json          # feed the cockpit setup card
python -m yothere.doctor --probe-brain   # also check the brain is reachable
python -m yothere.doctor --bundle        # write a redacted diagnostics tarball for an issue

The checks cover the interpreter, the package, the home layout, the brain, the runner service and its heartbeat age, the cockpit, the notifier, MCP registration, and which credentials are present. The one hard security gate is exposure: if the server binds a non-loopback address with auth off and no bearer token, doctor returns FAIL with the fix (set YOTHERE_VOICECALL_BEARER, or YOTHERE_AUTH_MODE=hosted, or bind 127.0.0.1).

The cockpit tunnel

The cockpit and voice server binds 127.0.0.1:8767, so by default it is reachable only from the machine it runs on. Two ways to reach it from elsewhere:

  • Public wss for Twilio inbound. Front the loopback server with a cloudflared tunnel to a public host, then set YOTHERE_VOICE_PUBLIC_WSS to that wss:// hostname. This is the tunnel service in the table above.
  • Tailnet. Keep the server loopback and tailnet-only, and set YOTHERE_TRUST_TAILNET_IDENTITY=1 so a browser on your tailnet connects without putting the bearer in the URL.

Any non-loopback bind requires YOTHERE_VOICECALL_BEARER (the bearer gate fails closed when it is unset), or doctor’s exposure check will FAIL.

Logs and gotchas

  • Never put logs under ~/Documents. On macOS, TCC revokes launchd access to ~/Documents after OS updates, which makes the worker die on every start (an exit-78 crash loop) with no signal that the fleet has stopped. Use ~/Library/Logs or another non-Documents path. On macOS, service install defaults launchd logs to ~/Library/Logs/relay and refuses a YOTHERE_LOG_DIR under ~/Documents, warning and using the safe default instead.
  • No ephemeral interpreters. A service templated against a uvx or pipx run cache dies silently when that cache is garbage-collected. Install persistently (uv tool install yothere or pipx install yothere), then re-run install. doctor flags an ephemeral interpreter as a failure.
  • Linux headless. systemd user services need a login session; on a headless box run loginctl enable-linger $USER (install warns when XDG_RUNTIME_DIR is unset).

macOS and Linux

Platform Units Location
macOS launchd user agents (.plist) ~/Library/LaunchAgents
Linux systemd user units (a .service per role, plus a .timer for the watchdog) ~/.config/systemd/user

Windows is out of scope; use WSL2, which takes the systemd path.

The full host-adapter contract (the five pieces: a venv with the wheel, a config env file, service wrappers, service units, and a notifier bridge) is documented in the product repo at docs/HOST-ADAPTER.md (private repo, available to beta hosts). service install automates the wrappers and units; the venv, the env file, and the notifier bridge are yours to provide.