A remote is a machine you connect to inference with belt remote. It hosts agent harnesses and, when you opt in, runs commands the platform dispatches.
The daemon connects outbound to inference over an authenticated WebSocket. Nothing listens on your machine, and the daemon runs only what the server sends.
When to use a remote
| Use case | What the remote provides |
|---|---|
| Agent harness hosting | Keeps coding agents (Claude Code, Codex, Cursor, and similar) logged in on a machine inference can drive |
| Command execution | Runs shell commands on a connected host and streams stdout/stderr back (belt remote exec) |
A remote is not a private inference engine. Engines run GPU/CPU workloads from the task queue. Remotes host harnesses and optional shell execution on a machine you control. See Private workers for compute on your hardware.
Security model
- Outbound only. The daemon dials
wss://api.inference.sh/ws/remotes/{id}with your API key. No inbound ports are opened. - Your OS user. Commands run as the user who started
belt remote. That user is the blast radius. - Exec is opt-in. Command execution is disabled unless you start the daemon with
--exec. Without it the daemon refusesremote_exec_startframes, and the API refuses the dispatch with403 exec_disabled. - Destructive CLI gate.
belt remote execprompts for confirmation, or requires--yesin non-interactive mode.
Run the daemon under a dedicated user or inside a container if you need a smaller blast radius.
Connect a machine
Prerequisites
- Belt CLI installed and logged in (
belt login) - An API key or session with the
remotes:writescope. Thestandardkey preset includes it.
Start the daemon
1belt remoteOn start, belt:
- Creates or loads a machine identity key (
remote_ed25519) in~/.inferencesh/. WhenBELT_CONFIG_DIRis set, the key lives in that directory instead. The private key stays on the machine; only the public key is sent. - Probes the machine for installed agent harnesses (see Harness discovery)
- Registers the remote with
POST /remotes, sending the name (default: hostname), public key, belt version, system info, and probe results - Connects to the remote WebSocket and sends a heartbeat every 5 seconds
1belt remote --name "work-laptop" # custom display name2belt remote --exec # allow command execution on this hostPress Ctrl-C to disconnect. The remote moves to disconnected once its heartbeat goes stale, and back to running when the daemon reconnects.
With --exec, registration sends exec_enabled: true and the daemon accepts command-dispatch frames. Without it, the status output shows exec: disabled.
System info
Registration includes a best-effort snapshot of the host in system_info:
| Field | Meaning |
|---|---|
hostname | Machine hostname |
os | OS label: the distro PRETTY_NAME on Linux, macOS on macOS, otherwise the Go OS name |
cpus | One entry with name, cores, and frequency |
ram.total | Total physical memory in bytes |
A probe that fails reports a zero value. Registration does not fail because of it.
Harness discovery
The daemon probes the host for known agent harnesses and sends the results in the harnesses field of POST /remotes. Each entry describes one harness binary found on PATH:
| Field | Meaning |
|---|---|
kind | Harness identifier (for example claude-code, codex, cursor) |
command | Resolved path to the harness binary |
version | First line of <command> --version. Best effort, may be empty. |
logged_in | true when a vendor auth file exists. It does not prove the harness is usable. |
Probes run concurrently. Each --version call has a 3 second timeout.
Supported harness kinds:
kind | Binary looked up | Auth file checked (existence only) |
|---|---|---|
claude-code | claude | ~/.claude/.credentials.json or ~/.claude.json |
codex | codex | ~/.codex/auth.json |
gemini | gemini | ~/.gemini/oauth_creds.json |
cursor | cursor-agent | ~/.cursor/cli-config.json |
opencode | opencode | ~/.local/share/opencode/auth.json |
amp | amp | ~/.config/amp/settings.json |
Discovery never reads credential files. logged_in comes from os.Stat on the auth path. File contents are not opened or transmitted.
Harnesses not in the table are omitted from registration.
The API exposes harnesses as profiles on each remote (harness_kind, command, status). On every POST /remotes the server reconciles the harnesses payload into profiles, one per harness kind. A harness with logged_in: true becomes idle; without it the profile is unavailable. If a harness disappears from the host, its profile stays and becomes unavailable.
Registration keys on the machine's public_key. Restarting belt remote on the same host updates the existing remote and its profiles. It does not create a duplicate. GET /remotes/{id} returns the same profiles array.
Run a command
Dispatch a command to a connected remote and stream its output:
1belt remote exec <remote-id> -- ls -la2belt remote exec <remote-id> -- bash -c "echo hi && uname -a"| Flag | Purpose |
|---|---|
--cwd <path> | Working directory on the remote |
--timeout <seconds> | Kill the command after N seconds (0 = no limit) |
--pty | Run under a pseudo-terminal |
The remote must have been started with belt remote --exec. Otherwise the API returns 403 exec_disabled.
belt remote exec is marked destructive and prompts for confirmation before dispatching. In CI or scripts, pass --yes / -y, or call the REST API directly.
Exit behavior. belt exits with the remote command's exit code when the run status is exited. Runs that end as killed or denied print an error and exit 1.
REST API
For automation, use the exec API:
POST /remotes/{id}/execscreates a runGET /execs/{id}/output?after_seq=Nreturns sequenced stdout/stderr chunksGET /execs/{id}returns the run statusPOST /execs/{id}/signalcancels a running exec
These calls need the remotes:read and remotes:write scopes. Full reference: Remotes API and Exec Runs API.
Troubleshooting
| Symptom | Likely cause |
|---|---|
not logged in | Run belt login or set INFSH_API_KEY |
exec_disabled | Restart the daemon with belt remote --exec |
remote_offline | The daemon is not connected. Start belt remote on the host. |
Run ends as denied | The API could not deliver the command to the daemon |
Run ends as killed with command execution is disabled on this remote | The daemon refused the run. Restart it with --exec. |
| No output | Poll /execs/{id}/output with an increasing after_seq cursor |
Next
→ CLI setup: command table and automation notes
→ Remotes API: register, list, update, and delete remotes
→ Exec Runs API: dispatch commands and read output