CLI.
relay is a daemon with a CLI that talks to it over a unix socket, the same shape as tailscaled/tailscale.
relay — pool GPU memory across machines and serve it as one OpenAI-compatible endpoint
relay serve run the pool daemon (the hub)
relay status show pool capacity, node health, and layer placement
relay bench [--model NAME] measure tokens/sec and where the layers ran
relay chat [--model NAME] streaming terminal chat REPL against the pool
relay doctor [POOL_URL] diagnose why this machine is not working
relay doctor --bundle write one redacted .zip for a bug report:
version, the doctor report, pool status,
config and the agent log, with machine
names, addresses and keys replaced
relay token [--label NAME] [--shard-only]
mint a single-use join token; --shard-only
restricts the joining node to pipeline
shards, never a whole-model replica
relay up --pool URL --token TOKEN join a pool and start lending VRAM
relay up --token TOKEN (no --pool) discover a hub advertising on
the LAN and join it — see RELAY_ADVERTISE_LAN
relay down stop lending and forget the pool
relay node ls list enrolled nodes and their trust level
relay node set ID --trust T change a node's trust (replicas|shard-only);
downgrading to shard-only stops its
running replica immediately
relay node rm ID [--wipe] remove an enrolled node; --wipe also
orders it to erase its local file cache
relay model add NAME PATH register a GGUF the hub owns
relay model catalog a short starter list, each entry marked
with where it would run in THIS pool
relay model search QUERY find GGUF files on Hugging Face, with size
and the same fit verdict, before downloading
relay model pull REPO[:FILE] download a GGUF from Hugging Face and register it
relay model ls list registered models and host-only status
relay model set NAME --host-only[=false]
keep a model on the hub only — no node
replica, no shard — even if that makes it
unavailable when the host is full
relay model rm NAME unregister a model
relay token --api [--rotate] show (or rotate) the default /v1 API key
relay key ls list named API keys and their quotas
relay key add NAME [flags] create a named, quota-bearing API key
relay key rm ID remove a named API key
relay audit tail [-n N] show the most recent /v1 requests
relay cache prune [--dir PATH] [--max-gb N]
(on a lending node) free disk by removing
unpinned cached files right now, down to
N GB if given, or all of them if not
relay install-service [--node] print a systemd unit
relay uninstall [--yes] [--keep-models]
remove relay's state, credential and login
item from this machine, and print what
needs root (units) or cannot be self-deleted
(the binary)
relay version print version and protocol
Environment: RELAY_LISTEN, RELAY_STATE_DIR, RELAY_LLAMA_BIN, RELAY_VRAM_RESERVE_MB,
RELAY_PARALLEL_SLOTS, RELAY_PIPELINE_DEPTH, RELAY_API_KEY, RELAY_DEBUG,
RELAY_ADVERTISE_LAN (hub, off by default — see README's Security posture)
RELAY_CACHE_MAX_GB (node), RELAY_MODELS_MAX_GB (hub) — visibility-only
disk budgets: warn in relay status and after a push/pull, never deleteThe usage footer lists only the most common environment variables. The full set is on Configuration.
--help is a command list, not a flag reference: usage() spells out only the few flags it shows inline, and most of the flags documented below never appear in it at all — relay model set --pin, relay doctor --pin-llama, relay up --sandbox and relay up --upgrade-tls among them. Where this page and the built-in help disagree, the code is what shipped. parseAnywhere splits flags from positionals before parsing, so relay model add qwen /m.gguf --vram-mb 6000 works exactly like relay model add --vram-mb 6000 qwen /m.gguf. The standard library’s flag package alone would silently discard the trailing flag. Serving and using the pool
relay serve
Runs the hub. --listen overrides RELAY_LISTEN for this process. On a fresh state dir it also generates the default /v1 API key and prints it exactly once — see Security.
That same first run — keyed to the API key having just been generated, not to whether the state dir already existed — also prints a Next: block: find a model that fits this pool (relay model catalog), lend another machine’s GPU (relay token), then a curl line to send the pool its first request. A later restart of an already-initialized hub does not repeat it — relay status shows the same three things again at any time.
relay status
| Field | Type | Notes |
|---|---|---|
--json | bool | Print the raw /status document. The only way to see replica placement; plain text only shows the shard/host-supervised model. |
Field reference for the JSON document: API.
The plain-text output carries two things worth knowing about before you reach for --json. The per-node table has a DISK column alongside GPU, LENDING, FREE, TRUST, LINK, STATE and LAST SEEN. And on an https pool a tls line appears under pool: with a Let's Encrypt domain it names the domain and says no pinning is needed, otherwise it prints the CA fingerprint to pin in every client that talks to /v1. That fingerprint is the value relay chat --ca-fingerprint and relay up --ca-fingerprint expect — see Security.
Below that, a WORKLOAD table lists every live whole-model replica — where it is running, its state, requests in flight, when it last served one, and how long it has been resident — busiest first. A sharded model does not get a row here: it already appears under serving with its layer split, and listing it twice would read as two workloads instead of one. And when something worth explaining has happened recently — a placement refused, a fallback to sharding, an eviction — a short recent placement decisions block follows, newest first: the same detail string the API error and the hub’s own log carry, so there is one wording to remember rather than three. Successful placements are not in it; the ring is for what needed explaining, and would be pushed out in seconds by ordinary traffic otherwise.
relay chat
A streaming terminal REPL against the pool’s /v1/chat/completions, with an agentic tool loop scoped to the current repository. Pure Go, no CGO.
| Field | Type | Notes |
|---|---|---|
--model | string | Model to chat with (default: auto-route picks one per turn; with exactly one registered model, that one). |
--url | string | Pool base URL (default: the local daemon's listen address). |
--ca-fingerprint | string | Pin this hub CA fingerprint for an https:// pool (default: RELAY_CA_FINGERPRINT, or auto-resolved). |
With no models registered on the pool it exits rather than opening an empty session. RELAY_API_KEY is read from the environment for the bearer token.
| Slash command | Effect |
|---|---|
/model [NAME] | Pick a model interactively, or switch by name. |
/models | List models available on this pool. |
/tools | List tools available to the agent. |
/repo [PATH] | Reload repo context and tools from PATH (default: cwd). |
/run on|off | Allow run_command without asking (default: off). |
/system TEXT | Set — or, with no text, clear — an extra system instruction. |
/file PATH | Attach a file's contents to your next message. |
/clear | Clear the conversation history. |
/status | Show pool status. |
/help | Show this help. |
/exit | Quit. /quit and Ctrl-D also work. |
The agent’s tools are read_file, write_file, list_dir, search, and run_command. run_command asks for confirmation before every call unless /run on turns that off — treat that switch the way you would any other “run whatever it suggests” mode.
relay bench
| Field | Type | Notes |
|---|---|---|
--model | string | Model to benchmark (default: the only registered model). |
--tokens | int | Tokens to generate per run (default 128). |
--runs | int | Timed runs, after one warmup (default 3). |
--url | string | Pool base URL (default: the local daemon's listen address). |
--ca-fingerprint | string | Pin this hub CA fingerprint for an https:// pool (default: RELAY_CA_FINGERPRINT, or auto-resolved). |
Joining a pool and lending VRAM
relay up
| Field | Type | Notes |
|---|---|---|
--pool | string | Hub base URL, e.g. http://hub.example:8383. Omit it alongside --token to discover a hub advertising on the LAN instead. |
--token | string | Single-use join token from relay token on the hub. |
--name | string | Name this machine appears as (default: hostname). |
--reserve-mb | int64 | VRAM kept for this machine's owner, in MB. |
--rpc-port | int | Local port for the llama.cpp rpc-server (default 50052). |
--tray | bool | Show the Relay icon in the notification area (default: platform support). |
--auto-pause | bool | Pause lending automatically while another process uses the GPU (off by default). |
--sandbox | string | Process sandbox for rpc-server/llama-server: auto (default), require, or off. |
--upgrade-tls | bool | Migrate this already-enrolled node onto mutual TLS. Requires an https:// --pool and a fresh --token. |
The first run enrolls and stores a credential at the default credential path; a later relay up with no flags reuses it and re-applies the previously stored settings (name, reserve, rpc-port, auto-pause, sandbox mode) rather than reverting to defaults.
--sandbox takes one of three values, validated before anything starts: auto sandboxes when a mechanism is available on this machine and falls back to running unsandboxed when none is; require fails the launch outright rather than running unsandboxed; off disables sandboxing entirely. Empty means auto.
relay down
| Field | Type | Notes |
|---|---|---|
--keep-credential | bool | Stop lending but keep the credential for a later relay up. |
Stops the running agent process (found via a PID file beside the credential), not only the credential. An earlier version left the process serving layers with no way to reclaim its identity, which was the worst of both outcomes.
relay token
Run on the hub; hand the line it prints to whoever owns the GPU.
| Field | Type | Notes |
|---|---|---|
--label | string | Note recorded with the token, e.g. whose machine it is. |
--shard-only | bool | Enroll the joining machine shard-only: it only ever runs pipeline-sharded activations, never a whole-model replica, so it never sees a plaintext prompt. Default is replicas. |
The token works once and pins the hub’s TLS certificate. What each trust level can actually see is on Security; relay token --api is a different thing entirely and is covered under API keys below.
Models
relay model ls
One row per registered model: name, VRAM, layers, host-only, pinned, path.
relay model add NAME PATH
Takes no flags in the common case; GGUF header parsing fills in everything. Overrides exist for the rare header that doesn’t say enough:
| Field | Type | Notes |
|---|---|---|
--vram-mb | int64 | Override: total VRAM the model needs, in MB. |
--layers | int | Override: transformer layer count. |
--vram-per-layer-mb | int64 | Override: VRAM per layer, in MB. |
--overhead-mb | int64 | Override: non-layer VRAM (KV cache, activations). |
--embedding | bool | Override: embedding model; always runs host-only. |
--utility | bool | Override: small utility model; always runs host-only. |
relay model catalog
Prints a short, curated starter list — seven entries, from a 398 MB smoke-test model up to an 18.6 GB mixture-of-experts model — spanning the sizes an ordinary desktop pool actually has. Each row marks where that model would run in this pool right now: the host, a specific node, shards (slow) if it only fits split across the pool, or that it does not fit at all. With no hub reachable it still prints the list, just without a verdict, since reading this before relay serve has ever run is a normal way to arrive at it. The fit check adds this pool’s KV cache reservation on top of the GGUF’s own size — the same comparison placement itself makes — but the authoritative number comes from the file’s metadata once it is actually downloaded.
relay model search QUERY
| Field | Type | Notes |
|---|---|---|
--limit | int | How many Hugging Face repositories to list (default 10). |
--all | bool | List every GGUF file in each matching repository, not only the ones that fit this pool. |
Searches Hugging Face for repositories carrying GGUF files and, per repository, lists each file’s size and the same fit verdict catalog prints — before anything downloads. By default only files that fit this pool are shown, with the rest counted rather than silently dropped; a split GGUF set (numbered parts) is folded into one entry carrying the total size, since sizing one part on its own reads as far smaller than the model actually is. HF_TOKEN is forwarded for gated repos.
relay model pull REPO[:FILE]
| Field | Type | Notes |
|---|---|---|
--name | string | Name to register the model under (default: derived from the filename). |
With no filename, a default quantization is picked from the repo’s file listing (Q4_K_M by preference, else the smallest). The pick is printed along with the REPO:FILENAME syntax for choosing a different one, and the download then starts — there is no confirmation prompt. HF_TOKEN is forwarded for gated repos.
relay model set NAME
At least one of the two flags must be given explicitly, or the command prints its usage and stops. Both are booleans you reverse with =false.
| Field | Type | Notes |
|---|---|---|
--host-only[=false] | bool | Never place this model off the hub — no node replica, no shard — even if that leaves it unavailable when the host is full. Setting it while the model is already on a node stops that placement immediately. |
--pin[=false] | bool | Never evict this model to make room for another one. |
relay model rm NAME
Unregisters the model. The GGUF file itself is not deleted.
Nodes
relay node ls
One row per enrolled node: id, name, GPU, lendable VRAM, trust, link latency, state, and last-seen time.
relay node set ID --trust replicas|shard-only
--trust is required and must be one of those two values. Downgrading a node to shard-only stops any replica it was running right then, rather than waiting for the scheduler to reclaim it. What each level lets a node see, and how the hub enforces it, is on Security.
relay node rm ID
| Field | Type | Notes |
|---|---|---|
--wipe | bool | Also order the node to erase its local file cache. The order is recorded as a tombstone before the node row is deleted, and delivered the next time that identity tries (and fails) to reconnect. |
Without --wipe, removal revokes the credential and leaves the cache on disk.
API keys and the audit log
relay key add, relay key rm, relay token --api --rotate — goes over the control socket to the running daemon, which is the single writer of those files. The read-only ones — relay key ls, relay token --api without --rotate, relay audit tail — read the state dir directly and work with no daemon running. A rotated default key is only read at daemon startup, so it takes effect on the next relay serve restart. relay token --api
| Field | Type | Notes |
|---|---|---|
--api | bool | Show the default /v1 API key instead of minting a join token. |
--rotate | bool | With --api, generate a new key. The old one stops working once relay serve restarts. |
If RELAY_API_KEY is set in the environment it overrides the stored key, and --rotate reports that it had no effect rather than silently rotating a key nothing is using.
relay key ls
Named keys and their quotas: id, name, tokens/day, max concurrency, requests/min, and tokens used today. A key with no rate limit of its own shows default — it inherits RELAY_V1_RATE_LIMIT rather than being unlimited.
relay key add NAME
| Field | Type | Notes |
|---|---|---|
--tokens-per-day | int64 | Daily token quota (0 = unlimited). |
--max-concurrency | int64 | Max simultaneous requests (0 = unlimited). |
--requests-per-minute | int64 | Per-key /v1 request rate limit (0 = inherit RELAY_V1_RATE_LIMIT). |
The secret is printed once, at creation, and never again.
relay key rm ID
Removes a named key; it stops authenticating against /v1 immediately.
relay audit tail
| Field | Type | Notes |
|---|---|---|
-n | int | Number of most recent entries to show (default 50). |
One row per /v1 request: time, key id, model, placement, prompt and completion token counts, latency, and status. Prompt and completion text is never recorded.
Diagnostics
relay doctor [POOL_URL]
An optional positional URL is what the three hub-side checks run against: hub (GET /healthz), tls-handshake (the mutual-TLS handshake) and v1-tls. Omit it and all three report as skipped — the middle one under the shorter label tls, which is the only place that spelling appears; once it actually runs it reports as tls-handshake throughout. Every check reports on stdout, including whether a sandbox mechanism is actually available on this machine — which is reported as skipped when --sandbox off or RELAY_SANDBOX=off is in effect. See Troubleshooting for what each check reports.
| Field | Type | Notes |
|---|---|---|
--pin-llama | bool | Record the currently-resolved rpc-server/llama-server binaries as trusted (trust on first use) for future llama-sha256 checks. Runs before the other checks and prints the sha256 and build number it pinned. |
--bundle | bool | Write a redacted diagnostics archive instead of printing the report — see below. |
--bundle-out | string | Where to write --bundle's archive (default: ./relay-diagnostics-<timestamp>.zip in the current directory). |
llama-sha256 check reports as unpinned rather than as passing. --bundle re-runs the same checks with their output captured rather than printed, and writes it into a .zip alongside the build version, relay status, the config, and a tail of the agent log — one file to attach to a bug report instead of several pasted terminals. It is redacted before the archive is written: hostnames, paths, the API key, node names, and the TLS fingerprint are all replaced with stable placeholders, and a verification pass has to confirm none of them survived before anything is written to disk.
relay version
Prints one line: version, commit, and protocol version. --version and -v are accepted as aliases.
relay help
Prints the command list above and exits 0. --help and -h are accepted as aliases, and relay with no arguments at all does the same thing. An unknown command also prints it, but exits non-zero with unknown command.
ls subcommand also answers to list, and every rm subcommand also answers to remove — on relay node, relay model and relay key alike. The short forms are the ones relay help prints, and the ones used throughout this page. Housekeeping
relay cache prune
Run on a lending node, not the hub. It frees disk now by removing unpinned cached files, and prints how much it freed.
| Field | Type | Notes |
|---|---|---|
--dir | string | Cache directory (default: beside the node credential file). |
--max-gb | int64 | Prune down to this many GB rather than removing every unpinned file. 0 (the default) removes all of them; a negative value is rejected. |
Safe to run while the node is lending: both this command and the live agent’s LRU eviction only ever remove files, and a file removed out from under a running push is simply re-verified and re-written.
relay install-service [--node]
| Field | Type | Notes |
|---|---|---|
--node | bool | Generate the node definition instead of the hub one. |
--listen | string | RELAY_LISTEN for the hub. |
--advertise | string | RELAY_ADVERTISE_URL: the address nodes should join at. |
--reserve-mb | int64 | VRAM to keep for the machine's owner, in MB (default 4096). |
--os | string | Target platform: linux, darwin or windows (default: runtime.GOOS). |
--boot | bool | Windows hub only: run from machine start as SYSTEM so it survives a logout, instead of at logon. |
--bin | string | Path to the relay binary on the target machine. |
Prints the unit/task definition rather than writing it, so it’s safe to run anywhere and readable before install. On Windows, --node generates a logon-time scheduled task rather than a service: a Windows Service runs in session 0 with no desktop, so it can lend VRAM but never show the tray icon. --boot only changes the hub’s Windows task, trading that same visible-session convenience for surviving past a logout. See Nodes & Windows.
relay uninstall [--yes] [--keep-models]
| Field | Type | Notes |
|---|---|---|
--yes | bool | Skip the confirmation prompt. |
--keep-models | bool | Keep downloaded model weights — they are large and re-downloading is slow — and remove the rest of the hub state (database, TLS material, API key) around them. |
Removes what relay put on this machine for the current user: the node credential, settings and model cache; the hub’s state directory (database, TLS material, API key, and downloaded models unless --keep-models is given); and, if one was registered, the “start on login” item. Nothing absent from this machine is listed — a hub-only install has no node credential, and claiming one was removed would misdescribe what just happened to the disk. It stops the running node agent first if one is up, prints exactly what it removed and what failed, and prompts with This cannot be undone. unless --yes was given.
Two things it deliberately never does, both printed as commands to run yourself: it never touches a systemd unit under /etc/systemd/system, since that belongs to whoever installed it and a tool that quietly sudoes is worse than one that prints the command; and it never deletes the running binary — impossible on Windows, and on Unix it would take this command’s own output down with it if anything failed halfway through.