This document describes the current target user- and agent-facing CLI contract for
remote-runner. The repository still contains the legacy seed-runner prototype; see
SEED_RUNNER_API.md for that older API.
- Local CLI first.
- Non-interactive use by default, so humans, scripts, and agents can share the same interface.
- JSON output for every resource/query/action command.
- No credentials in stdout, stderr, logs, reports, or JSON payloads.
- Recoverable state: machines, sessions, commands, logs, transfers, and artifacts can be queried after an interruption.
- Backend flexibility: SSH, tmux, rsync, SFTP, Slurm, Docker, or other mechanisms are implementation details.
The current MVP is Linux-first. The actively supported persistent session path targets Linux
machines reachable over SSH/SFTP with tmux available on the remote host.
Windows OpenSSH + WSL is not the current primary support target. startup_commands and
path_mappings remain in the machine schema because they are useful for compatibility and future
backend work, but machines that require startup commands may be explicitly rejected by the current
persistent session backend. See Platform Support.
All commands should eventually support:
--json
--timeout <seconds>
--state-dir <path>When --json is present, stdout must contain one JSON object. Errors should return non-zero exit
status and write one JSON object to stderr.
remote-runner machine add --jsonDefault MVP behavior is interactive when fields are omitted. The user supplies machine ID, host or
IP, port, user, authentication method, credential reference or password, and default working
directory. The user may also supply ordered startup commands that run immediately after SSH login
and before the default cwd/user command. Prompts are written to stderr so --json stdout remains
one JSON object.
Non-interactive flags remain available, but password values should not be encouraged as command-line flags because shell history is easy to leak. The recommended password path is hidden interactive input.
Same-name replacement is explicit:
remote-runner machine add --replace --confirm-replace lab-gpu-01 --jsonInteractive replacement warns on stderr when the machine already exists and requires typing the exact machine ID before overwriting the machine record. Replacement preserves prior sessions, logs, transfers, and artifacts.
Minimum stored fields:
{
"machine_id": "lab-gpu-01",
"host": "192.0.2.10",
"port": 22,
"user": "ely",
"auth_type": "key",
"key_path": "~/.ssh/id_ed25519",
"default_cwd": "/home/ely",
"startup_commands": [],
"path_mappings": []
}For a future or compatibility Windows OpenSSH machine that should enter WSL before Linux commands:
remote-runner machine configure-startup lab-win-01 \
--startup-command wsl \
--default-cwd /mnt/c/Users/example/Desktop/SSHRunner \
--jsonconfigure-startup updates only startup commands and the optional default cwd. It must preserve
credentials and existing sessions, logs, transfers, and artifacts.
Some machines expose different path namespaces for command execution and SFTP. For example, a
Windows OpenSSH host may enter WSL for commands, where the working directory is
/mnt/c/Users/.../SSHRunner, while SFTP sees the same directory as
C:/Users/.../SSHRunner. Configure an explicit prefix mapping:
remote-runner machine configure-path-map lab-win-01 \
--command-prefix /mnt/c/Users/example/Desktop/SSHRunner \
--file-prefix C:/Users/example/Desktop/SSHRunner \
--jsonfile put/get/list apply this mapping before SFTP. Transfer records and artifact manifests keep
the original user-supplied remote path, not the backend-specific path. This compatibility path is
not the current primary persistent-session support target.
remote-runner machine list --json{
"machines": [
{
"machine_id": "lab-gpu-01",
"host": "192.0.2.10",
"port": 22,
"user": "ely",
"auth_type": "key",
"default_cwd": "/home/ely",
"startup_commands": [],
"path_mappings": []
}
],
"summary": {
"machine_count": 1
}
}Credentials must be redacted.
remote-runner machine show lab-gpu-01 --jsonReturns one machine record with credential fields redacted or represented as references.
remote-runner machine doctor lab-gpu-01 --json{
"machine_id": "lab-gpu-01",
"reachable": true,
"auth_ok": true,
"default_cwd_ok": true,
"checked_at": "2026-05-07T10:00:00Z",
"errors": []
}Diagnostics should be clear enough for a human to fix config quickly, while avoiding credential leaks.
remote-runner machine remove lab-gpu-01 --jsonReturns the removed machine ID and whether related inactive sessions were retained or deleted.
remote-runner session create \
--machine lab-gpu-01 \
--cwd /home/ely/project \
--json{
"session_id": "sess_abc123",
"machine_id": "lab-gpu-01",
"cwd": "/home/ely/project",
"status": "active",
"backend": "tmux",
"created_at": "2026-05-07T10:00:00Z",
"log_dir_local": "/Users/ely/.remote-runner/logs/sess_abc123",
"transcript_file_local": "/Users/ely/.remote-runner/logs/sess_abc123/transcript.txt"
}If --cwd is omitted, use the machine's default_cwd. A session is the persistent remote shell
context for this work area; backend details such as tmux are implementation details, not a separate
top-level resource. In the current MVP, this persistent backend is Linux/SSH + tmux.
remote-runner session list --jsonReturns active and recoverable sessions with machine ID, cwd, status, creation time, command count, and log directory.
remote-runner session show --session sess_abc123 --jsonReturns the session record, last command, last exit code, command count, and log locations.
remote-runner session exec \
--session sess_abc123 \
--cmd "pytest -q" \
--timeout 300 \
--jsonThis is the default synchronous path. --mode wait runs the command inside the persistent session
shell and returns only after the command finishes. Shell-local state such as cd, exported
variables, aliases, and shell functions remains available to later commands in the same session.
Optional cwd override. This changes the persistent shell's current directory before running the command:
remote-runner session exec \
--session sess_abc123 \
--cwd /home/ely/project/subdir \
--cmd "make test" \
--jsonReturn shape:
{
"session_id": "sess_abc123",
"command_id": "cmd_abc123",
"machine_id": "lab-gpu-01",
"cwd": "/home/ely/project",
"command": "pytest -q",
"mode": "wait",
"status": "completed",
"exit_code": 0,
"stdout": "...",
"stderr": "",
"stdout_truncated": false,
"stderr_truncated": false,
"started_at": "2026-05-07T10:00:00Z",
"ended_at": "2026-05-07T10:00:08Z",
"duration_ms": 8123,
"log_file_local": "/Users/ely/.remote-runner/logs/sess_abc123/cmd_001.log"
}Behavior requirements:
- Preserve logs even when
exit_codeis non-zero. - Do not destroy the session on command failure.
- Return a clear busy/timeout state when a command cannot be accepted.
- Allow stdout/stderr truncation in JSON only if the full content is available through
log_file_localor a recoverable remote log reference. - Do not require sshfs, FUSE, reverse SSH, or mount setup.
session execdefaults to synchronous wait mode; long-running jobs should use--mode background.session execruns in the session shell. It must keep command boundaries and exit codes through Remote Runner command wrappers and state files, not by relying on chat memory.
remote-runner session exec \
--session sess_abc123 \
--cmd "docker compose up" \
--mode background \
--jsonBackground start returns immediately after the command is accepted in the session shell. The
response includes command_id, status=running, remote_state_dir, remote log/status file
references, timestamps, and log_file_local for the local summary record.
Background commands persist their own remote state under the session cwd in
.remote-runner/commands/<command_id>/. The CLI can be restarted later and still recover the
command state from local session records plus those remote files.
remote-runner session command list --session sess_abc123 --json
remote-runner session command show --session sess_abc123 --command-id cmd_abc123 --json
remote-runner session command result --session sess_abc123 --command-id cmd_abc123 --json
remote-runner session command wait --session sess_abc123 --command-id cmd_abc123 --timeout 20 --json
remote-runner session command stop --session sess_abc123 --command-id cmd_abc123 --jsonshow and result are aliases. They return the current command status, bounded stdout/stderr
excerpts, truncation flags, exit code when available, timestamps, local log path, and remote state
references when the command was started in background mode.
wait only limits the wait call itself. It does not kill the remote command on timeout. The
returned JSON includes wait_timed_out so callers can distinguish "still running" from
"finished".
stop sends a stop request for a background command. Repeating stop on a finished command must
return a clear result without losing logs or state.
remote-runner session send \
--session sess_abc123 \
--input "python" \
--json
remote-runner session read --session sess_abc123 --json
remote-runner session read --session sess_abc123 --since 1200 --jsonsession send sends raw input to the same session shell and presses Enter by default; use
--no-enter for partial input. This is for shell-panel and interactive workflows. For normal agent
automation, prefer session exec because it also records command boundaries, stdout/stderr,
exit code, timestamps, and logs.
session read returns captured transcript text, a cursor, and the requested since offset.
Callers can store the cursor and later request only the new transcript region. The local transcript
file is preserved in Remote Runner state for recovery.
remote-runner session logs --session sess_abc123 --jsonReturns ordered command log metadata and paths. A future text mode may print combined logs.
remote-runner session destroy --session sess_abc123 --jsonReturns final status and preserved log/transcript locations. Destroying a session stops the remote backend shell but preserves local session records, command logs, transfer records, artifacts, and transcript path.
remote-runner file put --session sess_abc123 --local ./input.txt --remote /home/ely/project/input.txt --jsonUploads a local file or directory to the remote path through SSH/SFTP or another explicit transfer backend. It must not require a mount.
remote-runner file get --session sess_abc123 --remote /home/ely/project/output.txt --local ./output.txt --jsonDownloads a remote file or directory to the local path.
remote-runner file list --session sess_abc123 --remote /home/ely/project --jsonLists remote file metadata for the path.
Transfer responses and persisted records should include direction, source, destination, timestamps, status, size/hash when available, and error details when failed.
Default MVP state root:
~/.remote-runner/
machines.json
sessions/
logs/
transfers/
artifacts/
runs/
The storage implementation can later move to SQLite or a daemon. The CLI contract must remain the source of truth for humans, scripts, and agents.
run commands sit above machine/session/file and provide a generic closed loop. They are not tied
to research, SEED, operations, training, or any other profile.
remote-runner run once \
--machine lab-gpu-01 \
--cwd /home/ely/project \
--input ./input.json=/home/ely/project/input.json \
--cmd "python train.py --input input.json --output output.txt" \
--artifact /home/ely/project/output.txt=./output.txt \
--json--input uses LOCAL=REMOTE; --artifact uses REMOTE=LOCAL. Both flags may be repeated. The
delimiter is = so Windows-style paths can still contain :.
Default behavior creates a session, uploads inputs, executes the command, downloads artifacts, saves
a run manifest, destroys the session, and preserves logs/state. Use --keep-session to leave the
created session active.
Minimum run manifest fields:
{
"run_id": "run_abc123",
"machine_id": "lab-gpu-01",
"session_id": "sess_abc123",
"cwd": "/home/ely/project",
"command": "python train.py",
"status": "succeeded",
"started_at": "2026-05-11T10:00:00Z",
"ended_at": "2026-05-11T10:00:08Z",
"inputs": [],
"command_result": {},
"artifacts": [],
"destroy_session_result": {}
}Non-zero command exit codes mark the run as failed but must preserve command logs and the run
manifest.
remote-runner run list --jsonReturns recoverable run summaries with run ID, machine ID, session ID, cwd, command, status, timestamps, exit code, input count, and artifact count.
remote-runner run show run_abc123 --jsonReturns the full run manifest.
Profile commands are intentionally above the generic run layer. Future commands may include:
remote-runner run verify
remote-runner artifact push
remote-runner artifact pull
remote-runner report evidenceThese may support operations, SEED, research, training, benchmark, or other workflows. They should not redefine the machine/session/file/run primitives.