A remote is a machine you connect to inference with belt remote start. It hosts agent harnesses and, when you opt in, runs agent sessions or shell 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.
A managed sandbox is a remote the platform runs for you: a VM that starts belt and registers the same way.
In the workspace
After you run belt remote, open Resources → Remotes in the sidebar (/remotes) to see connection status, harness profiles, and whether exec is enabled on each host.
Select a machine to open its page:
- Agents — one card per harness profile on the machine. Launch asks for the folder the harness should work in and opens a chat with the harness-backed agent for that profile. A card cannot launch while the machine is offline, the daemon was started without
--agents, the harness is signed out or too old, or the profile is busy. - Machine — belt version, hostname, OS, CPU, whether agent sessions and command execution are on, and last seen.
- Rules — allow, ask, and deny rows for commands, workspace folders, and harness tools on this machine. They apply to every
remote_execdispatch and harness call on the remote, and only the remote's owner or someone with awriteshare can change them (see Agent and remote permission rules). - Sessions on this machine — harness sessions inference started and sessions opened locally. Continue on an idle session reopens it in a chat.
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 |
| Agent sessions | Runs harness processes for chats the API opens on this host (belt remote start --agents) |
| 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 and harness sessions run as the user who started
belt remote start. 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. - Agent sessions are opt-in. Inference may drive harness sessions (Claude Code, Codex, Cursor, and similar) on your machine only when you start the daemon with
--agents. Without it the daemon refusesremote_session_openframes, ignores other session frames, and answers opens withagent sessions are disabled on this remote (start it with --agents). - Permissions stay with you. The daemon never auto-approves harness tool calls locally. Approval requests go through inference.sh; a person allows or denies them, or has already chosen always allow for that tool in the chat, in which case the API answers the harness without parking the run. See Remote harness runs.
- Destructive CLI gate.
belt remote execprompts for confirmation, or requires--yesin non-interactive mode. - Yours unless you share it. Teammates can see your remote. Running commands, opening harness sessions and placing agents on it needs you (its owner) or a
useorwriteshare. Changing it, deleting it, editing its policy rules and managing its shares needs you or awriteshare. Anyone else who can see it gets403("this remote is another person's machine and is not shared with you for this"); admins do not bypass this. Heartbeats and the daemon connection are yours alone. See Visibility and sharing. Exec runs and their output are visible only to the person who started them and to the remote's owner; environment variable values are never returned by the API. - Policy paths follow symlinks (belt v1.19.13+). When workspace policy evaluates remote exec, workspace, or harness paths, the API asks a connected daemon with
--execor--agentsto resolve absolute paths through symlinks on the host. Allow rules must match a path both as written and where it leads, so a symlink cannot bypass a rule on the literal path. If the daemon is too old, offline, or does not answer within a few seconds, paths are judged as written; an otherwise-allowing rule may surface as a person approval instead. ~paths use the daemon's home. On registration the daemon may sendhome(the account's absolute home directory). Workspace rules and calls that name~/…resolve against that path on the machine before symlink resolution. Whenhomeis missing (older daemon),~paths are judged as written.
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 remote start2belt remote start --name "work-laptop" # custom display name; later starts keep it3belt remote start --exec # allow command execution on this host4belt remote start --agents # allow harness agent sessions on this host5belt remote start --exec --agents # both exec and harness sessionsOn 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). The CLI shows discovering agent harnesses... as its own step before registration.
- Registers the remote with
POST /remotes, sending the name (default: hostname), public key, belt version, system info, the daemon account's home directory (used to expand~in workspace rules; stored, not returned on remote API responses), probe results, and opt-in flags (exec_enabled,agents_enabled) - Connects to the remote WebSocket and sends a heartbeat every 5 seconds (every third beat also carries a fresh
system_infosnapshot with live CPU, RAM, and home-disk usage)
Foreground vs background. With no flags, belt remote start holds the terminal open until you stop it:
| Input | Effect |
|---|---|
| d or Ctrl-D | Detach: hand the connection to a background daemon and return your shell. Same as starting with --detach / -d. |
| Ctrl-C | Disconnect and exit; the API marks the remote disconnected when the WebSocket closes, and running again when you start belt remote and the socket reconnects. |
Detaching is only available on an interactive terminal (not when stdin is piped or in CI). Only one belt remote start may run per machine at a time (foreground or detached), keyed on the machine identity in ~/.inferencesh/. That instance holds a lock that belt remote status and belt remote stop use — including foreground sessions, which status labels mode: foreground, in the terminal that started it instead of a log file path.
A second belt remote start with the same --exec and --agents flags exits with a remote is already running on this machine (pid …); run belt remote stop or disconnect that session first. If you start again with different --exec or --agents values, belt offers to restart the running daemon with the new permissions (stopping ends any harness agent sessions on that machine, so it asks for confirmation). Decline the prompt or run in non-interactive mode without --yes, and belt leaves the running remote unchanged — use belt remote stop, then start again with the flags you want. Press d to detach: belt closes the foreground connection, releases the lock, and starts a background daemon with the same flags. If agent sessions are running in the foreground process, they are its children and would end on detach; belt asks you to press d a second time to confirm.
Flags such as --exec, --agents, and --name belong to belt remote start, not the bare belt remote group. Typing belt remote --exec prints an error that names belt remote start --exec instead of a generic unknown-flag message.
To start in the background immediately:
1belt remote start --detach # or -d2belt remote status # pid, remote id, connection state and how long it has held3belt remote logs -f # follow the daemon log4belt remote stop # stop the running remote (foreground or background)Logging. Foreground belt remote start shows harness discovery and registration progress in the terminal and prints connection warnings there. Info-level WebSocket diagnostics are suppressed so they do not interleave with the spinner. When a harness session closes with an error, the daemon logs a warning such as session for chat <id> closed: <reason> — the same reason inference shows on the failed chat turn. A detached daemon writes info and warn logs to the file path shown by belt remote status; use belt remote logs or belt remote logs -f to read them.
With --exec, registration sends exec_enabled: true and the daemon accepts command-dispatch frames. With --agents, registration sends agents_enabled: true and the daemon hosts harness sessions the API opens over the socket (remote_session_* frames). Without each flag, the corresponding frames are refused on the host. Exec and agent sessions are independent opt-ins — you can enable either or both.
Detached daemon. belt remote start --detach keeps the socket open in the background. Use belt remote status, belt remote logs -f, and belt remote stop to manage it. belt remote list shows remotes registered to your team in the API.
Connection state. belt remote status follows the socket for as long as the daemon runs and shows the belt release the daemon runs (belt v1.28.6 and later). state is connected or retrying, with how long it has held, for example retrying (for 54m12s). While it is retrying, reason carries the server's answer to the last attempt (server answered 401 Unauthorized) or the transport error, and hint names the fix when retrying alone will not get past it. The daemon logs the same lines on every change.
Logins. The daemon reads belt's credential again before every connection attempt, so it outlives the login it was started with. When a session expires or is replaced, the next reconnect is refused with 401; run belt login and the daemon reconnects with the new login on its next attempt, within 30 seconds, without a restart. The team does not follow: the remote stays in the team that was active when it started, and belt auth switch in another terminal does not move it. On a server, an API key scoped to remotes:read and remotes:write (belt login --key-stdin, or INFERENCE_API_KEY in the daemon's environment) is not tied to a login session's lifetime.
With --agents, registration sends agents_enabled: true and the platform can list harness sessions on the machine and open chats that run the harness in a folder you choose. When a profile reports capabilities.approvals: true, risky tool calls wait for your approval in the workspace; harnesses with approvals: false (Pi today) run tools without asking. Without --agents, the status output shows agents: disabled.
Start at boot
A remote started with belt remote start does not come back after a reboot. belt remote enable installs it as a service instead: a systemd user unit on Linux, a LaunchAgent on macOS.
1belt remote enable --name build-box --exec --agents # same flags as start2belt remote status # mode: service, started at boot (login on macOS)3belt remote disable # remove the serviceenable writes a systemd user unit (~/.config/systemd/user/belt-remote.service), enables it, and starts it. No root is needed: the service runs as you, with your login and the PATH you enabled it from, so harness discovery finds the same CLIs your shell does. If a remote is already running on the machine, enable asks before restarting it as the service, because stopping it ends its agent sessions; decline and the service takes over at the next boot.
| Behaviour | Detail |
|---|---|
| Boot | Starts when the machine boots. This needs lingering for your user; enable turns it on with loginctl enable-linger, and prints the sudo command to run when it is not allowed to. Without lingering the service starts at your first login. |
| Crash or failed start | Restarted after 15 seconds, for as long as it takes. A boot with no network yet is a failed start. |
belt remote stop | Stops it until the next boot. A clean exit is not restarted. |
belt remote status, logs | Work as for any remote. The service logs to the same file as a detached daemon. |
Changing --name, --exec, --agents | Run belt remote enable again with the flags you want. |
belt remote disable | Removes the unit. A remote running at that moment keeps running until you stop it. |
On macOS, enable writes ~/Library/LaunchAgents/sh.inference.belt-remote.plist and loads it with launchctl bootstrap. Running enable again boots out the previous agent and waits up to 30 seconds for launchd to unload it before bootstrapping, so bootstrap does not fail with 5: Input/output error (belt v1.28.6 and later). It behaves as on Linux, with one difference: a LaunchAgent starts when you log in, so a Mac comes back on its own after a reboot only with automatic login turned on (System Settings > Users & Groups). belt remote stop stops it until the next login, and disable marks it disabled and removes the plist.
Stopping or restarting the service ends the commands and agent sessions the daemon was running. A process a command deliberately left behind (a server started with nohup, for example) is not killed, the same as with a remote started by hand.
List and remove remotes
Account-level commands (run from any machine where you are logged in):
1belt remote list # name, host, id, status, exec, last seen, belt version2belt remote rm <id>... # remove stale rows from your account3belt remote rm <id> --yes # skip the confirmation promptbelt remote list marks the row for this machine when a local daemon is running. After a machine has been offline for more than a week, list prints a hint to remove it with belt remote rm.
belt remote rm calls DELETE /remotes/{id}. Use it for retired or reinstalled hosts that left an old registration behind. It is destructive (confirmation prompt, --yes in non-interactive mode). Removing a row does not stop a daemon that is still running on that host — run belt remote stop there first. If a stopped daemon starts again later, registration creates a new remote id (same machine key, fresh account row).
Update belt on a remote
Update a connected remote to the latest belt from any machine, or with update on the remote's page:
1belt remote update <id>The remote stops taking new work and waits up to 10 minutes for running commands and agent turns to finish. It installs the release while still connected, then restarts the way it was started. It keeps its name and settings and reconnects on the new version. Idle agent sessions and open terminals end with the restart. If the install fails, the remote goes back to work on the version it has.
How it restarts depends on the platform:
| Platform | Restart |
|---|---|
| Linux, macOS | In the same process (same pid), so belt remote status, a terminal running it in the foreground and the systemd service all keep tracking it |
| Windows, detached | A new background process; belt remote status and stop follow it |
| Windows, foreground | The new version runs in the same console; the terminal's prompt returns when it ends |
A remote cannot update itself when belt was installed by a package manager (Homebrew, Scoop), or when the user it runs as cannot write to belt's directory. The answer says what to do on the machine instead.
When belt is updated on the machine
A remote also follows a belt installed over the one it runs, whatever installed it: belt update, belt's own auto-update, the install script or a package manager. It checks once a minute. Nobody asked for this restart, so it ends nothing: it waits until no command, agent turn or terminal is running, for as long as that takes, and keeps taking work meanwhile. Before it switches, it runs belt --version on the new file (no network); a file that does not run, or reports the release already running, is ignored. Then it restarts onto the installed belt the same way as above. Idle agent sessions resume on the next message.
belt update says when the remote on the machine will move. A remote started before belt v1.28.6 does not follow updates; belt update prints the command that restarts it once.
Tags
Tags group remotes for workspace policy: a rule written for tag:prod applies to every machine that carries the prod tag, including machines you add later. Tags are lowercase letters, digits, -, _, or . (up to 40 characters each, at most 20 per remote). The API stores them normalized (trimmed, lowercased, deduplicated).
Changing tags needs manage_policy on the remote's workspace, because tags decide which policy rules apply to a machine. Unlike other remote writes, it does not require owning the remote.
1belt remote tag <id> # show tags2belt remote tag <id> prod eu-west # add tags (merged with existing)3belt remote tag <id> --rm eu-west # remove tagsWith --json, it prints the tag list after any changes. The REST equivalent is PUT /remotes/{id}/tags.
System info
Registration and periodic heartbeats include a best-effort snapshot of the host in system_info (collected with gopsutil, the same approach private engines use):
| Field | Meaning |
|---|---|
hostname | Machine hostname |
os | OS label: the distro PRETTY_NAME on Linux, macOS on macOS, otherwise the Go OS name |
cpus | One aggregate entry with name, cores, optional frequency, and usage (0–100% across all cores) |
ram.total, ram.used, ram.usage | Physical memory in bytes and used percentage (0–100) |
volumes | Optional. One entry for the filesystem that holds your home directory (where harness sessions and opened folders live): path, size, used, free, and usage percent |
A probe that fails reports a zero value. Registration does not fail because of it.
While the daemon stays connected, every third WebSocket heartbeat (~15 seconds at the default 5 second interval) carries an updated system_info. The API stores each snapshot on the remote, so GET /remotes/{id} and the workspace remote page can show live load the way engine cards do. The first heartbeat after connect omits system_info because registration already sent one; CPU usage on later snapshots reflects load since the previous collection. The HTTP heartbeat endpoint does not carry system_info.
Harness discovery
The daemon probes the host using the agentprotocol harness registry and sends the results in the harnesses field of POST /remotes. Each entry describes one registry harness whose binary was resolved on the machine (PATH, known install paths, or global package lists):
| Field | Meaning |
|---|---|
kind | Registry id for the harness (for example claude, codex, cursor). Profiles also expose display_name and vendor for UI. |
command | Resolved path to the harness binary |
version | Harness version from discovery. Best effort, may be empty. |
logged_in | true when the harness looks configured on this machine. It does not prove the harness can serve a run. |
capabilities | What hosted sessions can do with the session driver belt would use for this harness (see below). Omitted when the harness cannot be driven from inference. |
Probes run in parallel for every registry harness. The daemon checks config directories, PATH, known install locations (for example ~/.local/bin), and a cached global npm list -g (10 second timeout for the whole list). When a binary is found, it runs <command> --version with a 3 second timeout per harness. A stalled or slow binary leaves version empty instead of blocking registration. A machine with several CLIs usually finishes discovering agent harnesses... in about a second before registering remote... appears.
Discovery uses the shared harness registry in agentprotocol (harness.DetectInstalled). Every registry entry with a resolvable binary on the host is reported; kind is the registry id (for example claude, codex, gemini, cursor). Examples:
kind | Typical binary | logged_in hint |
|---|---|---|
claude | claude | Harness config directory present (for example ~/.claude/) |
codex | codex | ~/.codex/ config present |
gemini | gemini | ~/.gemini/ config present |
cursor | cursor-agent | Cursor CLI config present |
logged_in means the harness looks configured on this machine (Configured() in the registry). It is not proof the harness can serve a run. Discovery never reads credential files or transmits tokens.
Account and plan limits. Registration does not wait for usage probes. Once connected, the daemon rediscovers its harnesses and runs each one's usage probe to read who it is signed in as and its plan's rate-limit windows (in parallel, 60 second timeout each). It sends the result on a heartbeat right after it connects and then every 5 minutes. A probe that reports authenticated or unauthenticated also sets logged_in. A failed probe keeps the last good windows. A running session can report newer numbers between probes.
The API exposes harnesses as profiles on each remote (harness_kind, command, status, capabilities, account, limits). 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.
capabilities on a profile mirror what the daemon reported at registration. The API branches on these flags instead of hard-coding harness names:
| Field | Meaning |
|---|---|
steer | A prompt sent during a running turn can redirect it |
approvals | The harness can pause on tool permission requests |
interrupt | A turn can be cancelled without ending the session |
resume | A session can be reopened by harness session id after the process exits |
tools | The harness can call tools the API supplies at session open: the platform catalog tools search_tools, list_tools and execute_tool. See Platform catalog tools |
images | An image attached to a message reaches the harness's model as an image. When false, the harness gets the image's path on your machine and sees it only if it opens the file (belt v1.21.0+) |
files | The daemon passes attached files to the harness (belt v1.21.0+). Older daemons do not report it, and get the files' URLs in the prompt text instead |
capabilities is omitted on older daemons or harnesses the session host cannot drive (no session driver in the registry); treat missing values as false.
Registration keys on the machine's public_key within your active team (from your API key or belt auth switch). Restarting belt remote start on the same host updates that team's remote and its profiles; it does not create a duplicate. After you switch teams, the same machine gets a separate remote row for the new team. Remotes for other teams remain in the list until heartbeats go stale and the platform marks them disconnected, then stopped.
Agent harness sessions
When --agents is enabled, the daemon hosts one harness process per chat the API opens on this machine. Inference owns the conversation, run lifecycle, and every permission decision; the daemon launches the harness, forwards events, and applies prompts, interrupts, and permission resolves the API sends.
How harnesses are launched. The session host picks a driver from the agentprotocol registry for each harness id:
| Harness | Driver | What runs on your machine |
|---|---|---|
claude (Claude Code) | Native | Your claude binary over its stream-json control protocol |
codex | Native | Your codex binary via codex app-server |
| Other harnesses with a session driver | ACP | The harness CLI in Agent Client Protocol mode |
Belt passes only the resolved binary path. It does not add registry auto-approve flags, pinned models, or endpoints. Tool permission prompts always reach inference so a person can allow or deny them. The harness uses your vendor login from its own config; credentials never pass through the daemon.
- One chat, one session. Re-opening the same chat attaches to the existing process instead of starting a second writer.
- Open before launch.
remote_session_openreserves the chat slot beforeremote_session_openedis sent; the harness starts in the background so a prompt right behind the open waits on the session instead of racing the launch. - Event position on open.
remote_session_openedincludesseq, the last event number the session has produced (0for a fresh process). Callers use it to tell a restarted harness from a replay after reconnect. - Replay buffer. While the socket is down, the daemon keeps a bounded buffer of session events so the API can
replayafter reconnect. - Host limits. The daemon caps concurrent harness sessions (default 8) even when a profile allows more.
- Local session index. Call
GET /remotes/{id}/sessions(or the WebSocketremote_sessions_listframe) to see harness sessions on disk — metadata only: id, title, cwd, last updated, and whether a process currently holds the session. The daemon returns at most 500 sessions, newest first. When liveness is inferred rather than proven,live.heuristicistrue(the workspace remote page marks those states with?in the UI). - Closing sessions. When inference sends
remote_session_close, or you stop the daemon (Ctrl-C,belt remote stop, or process exit), the daemon asks the harness to end. If the process is still running after a short grace period, belt kills it when the session driver supports that, so a wedged agent cannot hold a slot or block shutdown. Stopping the daemon ends all hosted sessions in parallel. ACP-backed harnesses also report closed when their CLI process exits on its own.
Harnesses without a session driver in the agentprotocol registry are not offered for hosted sessions (capabilities is omitted at registration).
Questions from the harness
When a harness asks you a question instead of asking for permission, the chat shows it as a form: one choice (or several) per question, plus a field for an answer of your own. Submit, decline or cancel it like an MCP server's input request. Your answer goes back to the harness in its own format. No "always allow" rule or "allow every tool" setting answers a question for you (belt v1.28.4+).
| Harness | What asks |
|---|---|
| Claude Code | AskUserQuestion |
| Grok | ask_user_question (and leaving plan mode, which is asked as an approval of the plan) |
| ACP harnesses | elicitation/create |
Refusing the interrupt (instead of answering the form) tells the harness you declined.
Background work
Some harnesses leave a command or a watch running while their turn goes on (Grok's background commands and monitors). The tool call that starts it completes at once. When the work ends, the chat shows a line saying what ended and how, for example `make build` failed (exit 2).
If the harness then starts a turn of its own about it, that turn is a run of its own in the chat, answering that line, even though nobody sent a message. While your own run is still going, the harness's work is part of that run (belt v1.28.4+).
Platform catalog tools
When a profile reports capabilities.tools: true, the API gives the harness the three meta tools loop agents enable with internal_tools.meta: search_tools, list_tools and execute_tool. The harness keeps its own tools on the machine; these three reach the apps, agents, skills and knowledge in your workspace.
remote_session_open carries the tool definitions in tools. The daemon does not run them. When the harness calls one, the daemon sends remote_session_tool_call (call_id, name, arguments, run_id, optional tool_invocation_id). The API records the call on the chat as type: internal, runs it as that run, with lifecycle gates and tool approval as for any loop tool, and answers with remote_session_tool_result (text, is_error). Approvals for these calls are not sent with remote_session_resolve, which is only for tools the harness runs on your machine. A tool name outside the three is refused.
Profiles without capabilities.tools get no tools on open, and the harness sees only its own tools.
Wire format: Remotes API — WebSocket protocol.
Attachments
Images and files you attach to a message in a harness chat go to the harness with the prompt (belt v1.21.0+).
- The API sends the message's text and its files together in
remote_session_prompt. - The daemon downloads each file into a temporary folder for that chat on your machine (https only, up to 50 MB and 60 seconds per file) and hands the harness a local path. The folder is deleted when the chat's session ends.
- The harness gets each image as an image when it supports them (
capabilities.images). Every other file, and every image for a harness without image support, is named in the prompt by its local path, so the harness can open it with its own tools.
| Gets images as images | Gets the image's path |
|---|---|
| Claude Code, Codex, Pi, Copilot, Droid, Gemini, Goose, Hermes, Kimi, Kiro, OMP, OpenCode, Qwen | Cursor, Grok, Kilo |
Some harnesses resize or re-encode an image before sending it to their model (OMP scales small images up, for example).
If a download fails, the harness gets the file's URL instead of a path. A daemon older than belt v1.21.0 does not report capabilities.files; for those, the API writes the files' URLs into the prompt text, so the harness still sees them. Update belt on the machine to get attachments as files.
Wire format: Remotes API — WebSocket protocol.
Profile-backed agent chats
When you start the daemon with --agents, registration only exposes harness profiles on the remote. Workspace agents are created when you launch a harness from the app or API — not when the machine connects — so connecting a laptop does not fill your agent list.
Launch from the workspace. Pick a remote, harness profile, and folder; the UI calls POST /remotes/{id}/sessions and opens the returned chat. Optionally pick an existing harness session to resume (from sessions listed for that folder).
Launch from the API. Same endpoint — profile_id, work_dir, and optional resume_session_id. See Launch harness chat.
The first launch for a profile creates one harness agent and reuses it afterward:
| Agent field | Value |
|---|---|
title | {display name} · {remote name} — for example Claude Code · work-laptop |
harness | Agentprotocol registry id for the profile (claude, codex, …). inference is the default for template agents you deploy yourself. |
profile_id / remote_id | Bound to that harness profile and remote |
| Versions | Harness agents do not use editable platform versions — instructions, tools, and skills come from the harness on your machine. |
Each chat carries the folder in work_dir and optional harness_session_id when you resume. Harness sessions are folder-bound; the agent record is not.
Send messages with POST /agents/run, POST /chats/{id}/messages, the SDK, or the workspace. The API opens the harness session on your remote in that folder, streams replies into the chat, forwards permission prompts as approval interrupts, and records harness-side tool calls without re-running them on inference.sh. Harnesses that report capabilities.steer: true (for example Claude Code and Codex) accept follow-up user messages during an in-progress turn; others queue them for the next turn.
Requirements: the remote must stay connected with --agents, the profile must be idle with valid capabilities, and the harness must remain logged in on the host. See Agents API — Remote harness runs for run lifecycle, queueing, stop, and error strings.
Disconnect. If the daemon drops while a harness run is active, the run fails with the remote disconnected and is not retried. Restart belt remote start --agents and send a new message to continue the conversation (the chat keeps harness_session_id when the harness can resume the session).
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 start --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.
Stopping a command. Pressing Ctrl-C in belt remote exec kills the command on the remote and waits for the run to end; a second Ctrl-C exits without waiting. To kill a run started elsewhere (by an agent's remote_exec, or through the API), use its run id:
1belt remote kill <run-id>... # confirms first; --yes to skipThe run id is the exec_run_id in a remote_exec result, or the id of a run from the Exec Runs API. belt remote kill reports killed <id> once the remote confirms the run ended, and says so when it does not.
A kill, like a timeout, ends the whole process tree. Each run gets its own process group, and the daemon kills the group, so what sh -c "build && serve" started dies with the shell. This is why a run id is enough: there is no need for pkill -f, which matches on command text and also matches the shell that runs it. A process that left the group on purpose (setsid, a daemonizing server) survives; the run still ends, two seconds after the kill at the latest.
Disconnects. A short network drop marks the remote disconnected but does not kill commands already running on the host. Runs fail when the remote stays disconnected long enough to reach stopped (about 30 seconds) or when you signal them. Start the daemon again before that grace ends if you need long-running execs to survive a laptop sleep or Wi‑Fi outage.
REST API
For automation, use the exec and session APIs:
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 execGET /remotes/{id}/sessionslists harness sessions on a connected remote with--agents(see List harness sessions)POST /remotes/{id}/sessionsopens a new harness chat in a folder (see Launch harness chat)
These calls need the remotes:read and remotes:write scopes; launching a harness chat also needs agents:execute. Full reference: Remotes API and Exec Runs API.
From agents
Agents can run commands on connected remotes when you enable the Remotes internal tool category (internal_tools.remote: true). The runtime exposes remote_list and remote_exec — the same exec-run domain as the REST API and belt remote exec, with the remote's --exec opt-in and API scopes still enforced. remote_list reports each remote's exec_enabled flag (whether the daemon started with --exec).
When inference drives a coding harness on your machine (a chat bound to a remote profile), the remote must be connected with --agents as well. Shell commands from the Remotes internal tools still require --exec only.
remote_exec completes asynchronously: the agent turn waits on the tool invocation until the exec run finishes (like an app tool waiting on its task). The run also appears as an execution node in the chat trace. Stopping the chat signals in-flight exec runs through the same graph sweep that cancels app tasks. If the remote disconnects before the command ends, the run becomes killed with error remote disconnected before the command finished so the turn does not hang.
→ Internal Tools: Remote tools
Troubleshooting
| Symptom | Likely cause |
|---|---|
not logged in | Run belt login or set INFERENCE_API_KEY |
agents_disabled | Restart the daemon with belt remote start --agents |
exec_disabled | Restart the daemon with belt remote start --exec |
remote_offline | The daemon is not connected. Run belt remote start on the host. |
belt remote status shows retrying with server answered 401 Unauthorized | The login the daemon holds expired or was replaced. Run belt login on that machine; the daemon reconnects on its next attempt (within 30 seconds). |
belt remote status shows retrying with server answered 403 Forbidden or 404 Not Found | The current login may not use this remote (a different account), or the remote was removed with belt remote rm. Log in as the account that started it, or belt remote stop and start again to register it. |
| The remote is gone after a reboot | It was started by hand. Run belt remote enable with the same flags to start it at boot. |
belt remote enable on macOS fails with 5: Input/output error | launchd was still unloading the previous agent. Run enable again, or upgrade belt: v1.28.6 and later wait for the unload. |
The remote still runs an old belt after belt update on the machine | From belt v1.28.6 it moves once nothing is running. A remote started before that release needs one restart; belt update prints the command. |
starting at boot needs systemd | belt remote enable supports Linux with systemd. Elsewhere, use belt remote start --detach and your platform's own service manager. |
a remote is already running on this machine | A start with the same --exec / --agents as the running daemon. Run belt remote status, then stop or disconnect that session, or start with different flags to get a restart prompt. |
left the running remote as it is; 'belt remote stop' then start again | You declined restarting to change --exec or --agents, or confirmation failed in non-interactive mode. Run belt remote stop, then belt remote start with the flags you need. |
Unknown flag on belt remote --exec | Pass flags to belt remote start (the error repeats your flags with start in the command). |
a remote daemon is already running | Same as above when starting with --detach directly. |
harness_not_drivable | The harness is installed but the daemon cannot drive it (no capabilities on the profile) |
| Launcher card shows too old | The harness CLI on the host is below the session driver's minimum. Run support.upgrade_cmd on the machine (shown on the card), then restart belt remote start so discovery picks up the new version. |
| Launcher card is ready with a yellow version warning | The installed build is outside inference's tested range or could not be matched to the registry. You can still launch; upgrade when convenient if you hit issues. |
| Harness profile shows idle but launch or chat fails with a sign-in error | The harness CLI on the host is signed out. Sign in there (for example claude login), then restart belt remote start so enrollment re-runs discovery. Keyring-only harnesses can look idle when only the config directory exists; file-based harnesses need the registry login file (for example ~/.codex/auth.json). Cursor: export CURSOR_API_KEY in the same environment as the remote daemon (systemd unit, container env, or shell) so discovery sees key-only auth; a bad or revoked key can still fail on the first turn. |
| Chat on thinking for ~1 minute, then a failed turn | The harness did not finish opening within 60 seconds — often blocked on sign-in or an update in the terminal. Run the same CLI locally in the project folder, complete any prompts, then send again. |
| Turn failed: did not start a session within | Same as above: the harness CLI on the host must reach a drivable state within 60 seconds of open. |
Turn failed after ~2 minutes with no message, tool call, permission request or response / no_response | The harness accepted the prompt but never showed a sign of work (stuck starting MCP servers, waiting on a backend, or wedged). Read the agent stderr tail in the error or belt remote logs, fix the CLI on the host, then send again. |
| Long coding turn used to stop around 10 minutes | Older belt builds capped whole ACP prompts at 10 minutes. Current belt (agentprotocol v0.12.1+) only bounds time until the first sign of work (2 minutes); after that the turn runs until the harness finishes. Upgrade belt on the machine running belt remote start. |
| Turn failed: no session for chat / session ended / failed to open | The daemon no longer holds that chat's session (restart, crash, concurrent session limit, or a prior open failure). Start a new session from the remote page or launch a fresh chat. |
remote_timeout on session list | The daemon did not answer within 10 seconds — check connectivity |
Session list error right after belt remote restart | Heartbeat can mark the remote running before the WebSocket is back. Wait a few seconds or click refresh; the page retries listing automatically. |
| discovering agent harnesses... for more than ~15 seconds | A hung npm list -g or many harnesses timing out on --version. Upgrade belt (agentprotocol v0.10.2+), or run npm list -g manually to fix npm. |
Harness session closes with agent sessions are disabled on this remote | Restart the daemon with belt remote start --agents |
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. |
Run ends as killed with remote disconnected before the command finished | The remote missed heartbeats long enough to go disconnected or stopped while the command was still running. Restart belt remote start and dispatch again. |
Agent run fails with the remote disconnected | Same disconnect sweep while a profile-backed harness turn was in progress. The turn is not retried; send a new message after the daemon reconnects. |
Agent run fails with this remote does not run agent sessions | Start the daemon with belt remote start --agents. |
Agent run fails with the harness this agent runs on is not available | Harness logged out, profile unavailable, or missing session capabilities — fix login on the host and reconnect the daemon. |
| 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