docs GitHub

Concepts

The box and the Mac

Vyre runs on two kinds of machine. The box is a server you own: a Linux machine, or a Mac that stays on. Your sessions, agents and watchers live there, and the Vyre app talks to it. The Mac is the computer you sit at: it runs Lumen and your own terminal sessions, and reaches the box over your private network. A Windows PC is a device in the same way as the Mac, with an app instead of Lumen (see Windows). Each machine runs one vyred, the Vyre daemon, with a different set of modules switched on.

One process per machine

vyred is the same program everywhere. The machine's kind, machine in ~/.vyre/config.json, decides what it starts:

MachineWhereWhat it starts
servera Linux server, or a Mac you chose as the serverthe whole set, including the address the Vyre app connects to and the relay
soloa Mac with nobody else to connect (the default on macOS)the full local set, Lumen included, and nothing that serves other devices
devicea computer that joins a server (the default on Windows)the local set that reaches the server

A Linux machine defaults to server. A Mac starts as solo, and becomes a server when you choose to make it the server for My Cloud, or run the Mac server installer. Older configs that say "role": "box" or "role": "local" still work and are read as server and as solo. See config.

Every module declares the machines it runs on ("roles" in its module.json: box, local, or both when omitted). What that means today:

Runs onModules
Server onlynames (address and certificates), network, hooks, computers, glass, artifacts, releases, hands-desktop, and the box's side of chrome
Mac onlycapsule (Lumen), hands (computer use on macOS), screen, sideview, voice, apps, and the Mac's side of chrome
Bothmost of the rest, including projects, recall, memory, vault, watchers, threads, sessions, agents, gate, learn, harness, presence, link, relay, onboard, github, team, spend, files, push, settings and update

Modules has the full list with each module's machines. A Mac chosen as the server runs the Mac-only modules too, so Lumen stays on it.

A module that runs on both machines works on that machine's own data: recall on the Mac searches the Mac's Claude Code transcripts, recall on the box searches the box's. The one exception is reading and messaging a session: on the box, you see the paired Mac's sessions beside the box's own and can message them, through the link (see The box reads the Mac's sessions). vyre modules lists what started on the machine you run it on, and why anything failed.

You can move a module on or off with modules.enable and modules.disable in config.json. enable starts a module even when its roles do not include this machine's kind.

What runs where on a Linux box

A Linux box runs Vyre in Docker Compose, from /srv/vyre. The stack is box/compose.yml:

  • vyre: vyred and the sessions it runs. A small spawner starts vyred as uid 1000 (vyre) and the sessions Vyre runs itself as a second user, vyre-agent (uid 1001), which cannot open vyred's socket. vyred reaches your other devices over Wink, Vyre's own network core, which it runs itself: there is no sidecar container, no TUN device and no extra capability.
  • docker-api: a filtered Docker API for the agents' computers, under the computers profile.

The container publishes no port on the host. The host needs Docker and nothing else. A small vyre wrapper in /usr/local/bin runs commands inside the container, so vyre status on the host works as it does on a Mac.

The alternative without Docker is a systemd unit, installed with sudo vyre up --system --user <account> (the account vyred runs as, never root). See without Docker.

Who can reach the box

  • No port is published on the host. A server has no first-run page: you pair it from your Vyre app.
  • The built-in network and the relay carry every connection. Your devices reach vyred through them, not through a port on the host. See your private network.
  • Callers are identified from the identity list, never by a header. A device that reaches the box arrives as device:<id>, and the entry on your identity list says who it is.
  • vyred runs as uid 1000 (vyre), not root. Only the spawner is root in the container, with every capability dropped except the few it needs to start a process as another user.

The reasoning is in ADR 0002.

Folders and volumes

The installer puts the stack in /srv/vyre (VYRE_DIR moves it), owned by you, not root:

/srv/vyre/
  compose.yml          the stack: vyre, and docker-api under the computers profile
  compose.build.yml    used when COMPOSE_FILE lists it: build the image from VYRE_SOURCE
  src/                 the unpacked vyre.tgz, when the image is built from it
  vyre.env.example     copy to vyre.env for CLOUDFLARE_VYRE_TOKEN and similar
  vyre.env             optional, yours, read by the vyre container
  .env                 COMPOSE_PROJECT_NAME, COMPOSE_FILE, VYRE_SOURCE, DOCKER_GID; yours to add COMPOSE_PROFILES
/usr/local/bin/vyre    the host wrapper

The data lives in Docker volumes, all labelled run.vyre=1:

VolumeMounted atHolds
vyre_vyre-home/home/vyre.vyre/ (Vyre's home folder, below) and .claude/, .claude.json (Claude Code's own state and transcripts)
vyre_vyre-work/workprojects
vyre_vyre-agent-home/home/vyre-agentthe home of the sessions Vyre runs (their own ~/.claude and transcripts)
vyre_vyre-accounts/home/acctone private home per signed-in AI account (uids 2000 to 2063), holding that account's own login
vyre_docker-api-bearer/var/lib/vyre-secretsthe secret vyred uses to talk to the computers' Docker proxy

A container can be recreated at any time: nothing in it matters but the volumes. Backing them up and moving them is in Box care.

Vyre's home folder

Every vyred keeps its state in one folder, ~/.vyre (mode 0700), with the same layout on every machine:

On a server

On a Docker box it is /home/vyre/.vyre, inside the vyre_vyre-home volume.

/home/vyre/.vyre/
  config.json             settings (0600); onboarding writes name, network, onboard
  vyre.db, -wal, -shm     the store (SQLite, WAL)
  vault/                  sealed vault items
  certs/                  acme-production.key, <name>.crt, <name>.key
  names/                  the name directory key, once the hosted directory exists
  modules/, watchers/     what you installed and what Claude wrote
  embedder/               the search model's library (about 105 MB), fetched on first use: safe to delete
  models/                 the search model's weights (about 23 MB), a cache: safe to delete
  logs/                   YYYY-MM-DD.log from vyred
  vyred.sock, vyred.pid

On this Mac

On the Mac it is ~/.vyre in your own home folder. It has the same store, vault, search model and logs, and no certs/ or names/, because the Mac serves nothing to other devices. It also holds link.json (0600), the key that pairs this Mac with its box, and capsule/Vyre.app, Lumen as built on this Mac.

AI accounts and Claude Code on the box

  • Your accounts. Sessions run on your own accounts: Claude (through the Claude Agent SDK), Codex and Grok (through the Agent Client Protocol), and OpenRouter. A login account (Claude, Codex, Grok) is signed in once, by asking your assistant to start the provider's own sign-in (Claude's is also part of setup), and its login lives in that account's own private home on the box (vyre_vyre-accounts). A key (an Anthropic or OpenRouter API key) lives in the Vault. Either way the secret reaches the session's process at start and is never shown to a model.

  • Claude Code itself is installed in the image, and its state, ~/.claude/ and ~/.claude.json, lives in vyre_vyre-home, so a new image keeps it.

  • To run claude by hand on the box, run it in the container:

    cd /srv/vyre && docker compose exec vyre claude
    

    Its transcripts land in the same ~/.claude/projects/, which is how Vyre's history sees them.

The agents' computers

Agents get their own containers (Specification, Section 7.9) through docker-api, Vyre's own Docker proxy (core/dockerproxy, run from the same image). It is off until the computers profile is on; turning it on is in Box care.

  • The host's Docker socket is mounted into the proxy and nowhere else. vyred reaches it at http://docker-api:2375 on an internal network.
  • The proxy allows only what agents' computers use (create, start, stop, pause, unpause, inspect, list, remove, exec on a computer, volume inspect) and refuses every other endpoint.
  • It checks request bodies too: a create must match core/computers/driver/policy.js and the box's VYRE_COMPUTERS_* settings, and every per-container call is checked against the labels the Docker Engine itself reports.
  • It runs as uid 1000 with a read-only root, no capabilities, and the socket's group, DOCKER_GID in /srv/vyre/.env, which the installer fills in from the socket.
Why Why a proxy, and not the Docker socket?

Whoever holds the Docker socket is root on the host. The proxy keeps the socket in one small container and lets vyred ask only for the calls a computer needs, so a mistake or a prompt injection in a session cannot turn into a privileged container.

What runs on the Mac

On the Mac, vyre up starts vyred in the background with the local set of modules. It opens no listener for other devices. It serves its API on ~/.vyre/vyred.sock to the CLI, the Harness hooks in your terminal sessions, and Lumen. A Mac you chose as the server is different: it also keeps itself awake while it runs.

How the Mac finds the box

vyre up on a Mac with no box configured asks for your server's pairing code. It shows three words, and you confirm they match on both screens and approve with your passkey. You can also give it the address:

vyre box add [email protected]                     # put the box on a server you can SSH to
vyre up --box                                    # make this Mac the box
vyre up --connect https://alex.vyre.run            # a box you already set up

How they talk

The Mac's vyred is a client of the box, at network.box. The box sees the Mac as device:<id>, like any of your devices, because it identifies callers from the entry on your identity list and never by a header (ADR 0002).

The link module turns the two machines into one system:

  • Pairing. On the Mac, vyre link pair <address> (or vyre up --connect <address>, or a yes to vyre up's question) asks the box and shows a code. Starting Vyre never asks a box on its own, and a home other than ~/.vyre never talks to a real box unless VYRE_ALLOW_REAL_BOX=1 is set. Approve the request in the Vyre app and confirm with your passkey (see presence). You can approve from the Mac that is asking or from your phone. From the asking Mac the box asks for a fresh passkey proof and checks the typed code, so a model on the Mac cannot approve for you. The Mac keeps a link key in ~/.vyre/link.json (mode 0600) and pins the box's key, so a different box at that address is refused.
  • Box tools from the Mac. A module on the Mac calls ctx.remote(tool, input); a surface calls link.call. Both reach POST /v1/tools/<tool> on the box.
  • Box events on the Mac. The Mac proxies the box's event stream at /v1/link/events, so Lumen sees box threads as they happen.
  • The Mac's sessions on the box. The box reads the paired Mac's sessions through the link. See the next section.
vyre link                 # on the Mac: paired or not, and whether the box answers
vyre link pair https://alex.vyre.run
vyre link unpair          # forget the box

The box reads the Mac's sessions

Your Claude Code history stays on the Mac. The box reads it through the link when you ask, so the Vyre app lists the Mac's sessions beside the box's own (the app reads them and does not act on them; ADR 0021).

  • No port on the Mac. While paired, the Mac holds one request open to the box (link.serve). The box answers it with a question, or with nothing after 60 seconds; the Mac runs the question and sends the answer back (link.reply), then asks again. An idle Mac costs one request a minute.
  • A short list of reads. Only projects.catalog, projects.list, recall.search, recall.sessions, recall.thread, recall.transcript, threads.list and threads.asks cross, and both ends check the list (core/link/allow.js). The Mac runs them as module:link.
  • Two writes, yours only. threads.send types into a Mac session, and threads.answer answers one of its questions. Both cross only when you make the call from your own surface, never an agent, MCP, a guest or a module, and the Mac checks that again before it runs one. An answer also carries a one-use proof, signed by the box and tied to that question and those exact words, that the Mac checks. If a terminal or Lumen holds the session, your message waits until it is free and says so ("alex-mac is busy in your terminal").
  • Only for you. On the box these tools take machines: "all" or "local". They ask the Mac when you call them from the Vyre app, the CLI, Lumen or your own device, or when a module passes machines: "all". Agents, MCP and guests get the box's rows alone. Each row the Mac sends is labelled source: "mac" and machine (the Mac's paired name); the box's rows say source: "box".
  • Nothing is copied. Nothing the Mac answers is written to the box's store. Only recall.thread and recall.transcript carry a conversation, and only when you open a session the box does not have.
  • Marked as the Mac's. A Mac session carries the Mac's name. You can add a Mac session to a box project; the project's thread list reads it from the Mac.
  • An absent Mac is an answer. A Mac that is not polling answers mac_offline at once and the box shows its own rows, with an "alex-mac offline" chip read from link.macs, and a message to it is not sent. A slow Mac delays a read by at most 5 seconds. A Mac that dropped off in the middle of a request looks online for up to a minute, and a read in that window ends in timeout.

The box cannot search the Mac's files: files are not on the list.

When the box is away

The Mac keeps working without the box (floor rule 9, see the security floor). Once a call to the box fails, the next ones fail fast with box_unreachable and are retried with a growing pause, up to 30 seconds. The link emits link.lost and, when the box answers again, link.connected. A module that asked the box for something gets the error at once and can fall back to what the Mac has.

What it will not do

  • The Mac does not serve the Vyre app. The app talks to the box.
  • The box never trusts the Mac because of a header or a shared secret alone: every connection is identified from the identity list first.
  • Link tools on the box (link.*) cannot be driven through ctx.remote or link.call.
  • The box never runs a tool on the Mac outside the reads and the two writes above, and never as an agent or a module.

Next