Self-Hosted Runner
A self-hosted runner lets you run background AI agents on your own compute — your own laptop, a server, or a machine inside your network. You run one command from your dashboard on that machine, and it starts picking up agent work for your organization and running it there.
The runner only makes outbound connections to SetForth over HTTPS, so there is nothing inbound to open on your network. Each agent session runs inside Docker on the host, isolated from your other work.
Supported platforms
The runner is supported on macOS, Linux, and Windows:
- macOS — supported. Runs as a managed service via launchd.
- Linux — supported. Runs as a managed service via systemd.
- Windows — supported on Windows 10 and 11. Runs as a managed service via a Task Scheduler logon task, on the machine where you are already signed in.
Running the runner inside WSL2 (a Linux distribution such as Ubuntu) or a Linux virtual machine is an alternative for operators who would rather work in a Linux environment. Follow the Linux instructions there. It is no longer necessary on Windows, and it does mean providing Anthropic credentials inside the distribution rather than reusing the ones on your Windows user account.
Prerequisites
- A macOS, Linux, or Windows 10/11 host.
- Node.js 20 or newer on the machine that will run the runner.
- Docker (Desktop or Engine), installed and running. The runner always runs agents inside containers, so Docker must be available on the host. On Windows, that means Docker Desktop with the WSL2 backend, in Linux containers mode — every agent image is Linux. If you plan to install the managed service, also enable Docker Desktop → Settings → General → "Start Docker Desktop when you sign in".
- Anthropic credentials on the host. The agent is powered by Claude, so the host must provide credentials one of two ways (see Anthropic credentials below).
- A SetForth account with a role that can manage runners in your organization. If you cannot see the Runners page in your dashboard, ask an organization admin to grant you access or to register the runner for you.
Anthropic credentials
The agent runs inside a Docker container on the host and is powered by Claude, so the host must supply Anthropic credentials. You do not need the Claude CLI installed for normal operation — the agent runs in the container, not from your shell. Provide credentials one of two ways:
ANTHROPIC_API_KEYenvironment variable. Set this in the runner's environment. Usage is billed to your Anthropic API account.- A Claude subscription via
claude login. Sign in once on the host with the Claude CLI, and the runner uses that subscription. Usage counts against your Claude subscription. The CLI is only needed to perform that one-time login; it is not used at runtime.
Either option works. If both are present, the runner uses the ANTHROPIC_API_KEY.
Install
In your SetForth dashboard, open Runners and choose Add Runner. It gives you one command, already carrying a short-lived setup token and the address of your platform:
npx @setforth/self-hosted-runner start --token eo_rs_... --platform-url https://... --service
Run it on the machine that will host your agents. That is the whole install. The command checks the machine (Docker running, Anthropic credentials present) and reports what it found to the dashboard so you can follow along there, registers the runner, writes ~/.set-forth-runner/runner.config.yml with the key it was issued, and installs the background service. There is no config file to assemble and no API key to copy, and you can close the terminal afterward.
If a check fails, nothing is registered. Fix what the command prints and run the same command again. Re-running is safe: the machine is recognized and keeps the runner it already has instead of adding a second one.
Copy the command from the dashboard rather than typing it out. The setup token is valid for 24 hours and the dialog shows the exact expiry. Until then you can run the same command on as many machines as you like, and you can reopen the dialog any time for a fresh one.
The two flags
--platform-urlis the SetForth API origin to register with. It is required: no default is compiled into the CLI, because the backend is self-hosted per environment, so the dashboard renders its own address into the command rather than the CLI guessing. Re-running on a machine that is already set up can take the value from the config file the first run wrote.--serviceinstalls the background service and starts that, instead of running in your terminal. Use it for a machine that should stay online across reboots. It goes together with--token, as part of setting a machine up; to supervise a runner that is already configured, useinstall-servicebelow. On Linux run the command withsudo, and on Windows from an elevated prompt, because registering the service needs it. On macOS, run it as yourself. Drop the flag to run the runner in your terminal instead, which is handy for a first look or for a laptop you only want online while you are working.
Because npx runs the CLI out of a temporary cache, installing the service first copies the runner to ~/.set-forth-runner/app and points the service at that copy. Clearing your npm cache afterward is safe, and set-forth-runner update keeps that copy up to date.
From a config file
The one command covers a fresh machine. Use this path instead when you assemble a machine's configuration by hand, or when you already have a runner.config.yml from an earlier install.
In the Add Runner dialog, expand Write the settings file yourself. SetForth registers the runner and shows the contents of its config file, with the key already in it:
runnerId: # this runner's id
orgId: # your organization id
platformUrl: # the SetForth API origin to connect to
apiKey: # this runner's secret access token
Save that on the machine at a stable path, for example ~/.set-forth-runner/runner.config.yml. This path also needs the CLI on the machine, which the one command above gets through npx:
npm install -g @setforth/self-hosted-runner
That installs the set-forth-runner command. By default the runner reads runner.config.yml from the current directory, so start it from the folder that holds the file, or point at the file explicitly with --config /path/to/runner.config.yml.
Keep the API key safe
The apiKey is this runner's credential: anyone who has it can connect a runner to your organization and run agent work as you, so treat it like a password. It lives in runner.config.yml on the host either way, whether the setup command wrote the file or you saved it yourself, so keep that file out of version control and off shared machines. If the key is ever exposed, rotate it from the runner's menu in the dashboard (Rotate API key), which revokes the old one and shows you the replacement to write into the file.
Run the runner
If you installed with the one command, the runner is already running: as the background service, or in your terminal if you dropped --service. This section is for a runner you configured from a config file, and for changing how an existing one runs.
As a managed service (recommended)
Best for an always-on runner. It starts at boot or sign-in, restarts on crash, and keeps running across logout: launchd on macOS, systemd on Linux, a Task Scheduler logon task on Windows.
set-forth-runner install-service --config ~/.set-forth-runner/runner.config.yml
On Linux this needs sudo, because it writes a system unit. On Windows it needs an elevated prompt ("Run as administrator"), because registering a logon task does. Elevating on Windows preserves your identity, so the task still runs as you, with your Docker Desktop and your Anthropic credentials — it is never registered as SYSTEM. On macOS, run it as yourself without sudo.
The absolute path of the config file is recorded when you install the service, so keep runner.config.yml where it is afterward. The runner itself is not pinned that way when you install through npx: it is copied to ~/.set-forth-runner/app first, so a cleared npm cache cannot break the service. A background service has no shell environment, so the connection values must all be present in the config file itself (both the file the setup command writes and the one the dashboard shows already include them). Remove the service later with:
set-forth-runner uninstall-service
On Windows the task is named SetForthRunner and you manage it with:
schtasks /Query /TN SetForthRunner /V /FO LIST :: status
schtasks /Run /TN SetForthRunner :: start now
schtasks /End /TN SetForthRunner :: hard stop
Prefer set-forth-runner drain over schtasks /End: /End is a hard kill and takes any in-flight session with it. The service writes its output to %USERPROFILE%\.set-forth-runner\logs\service.out.log and service.err.log (on macOS, the same filenames under ~/.set-forth-runner/logs; on Linux the unit logs to journald, so use journalctl -u set-forth-runner).
In the foreground
Runs in the terminal, which is handy for a quick test. It stops when you close the terminal, so it will not come back after a logout or reboot:
set-forth-runner start
Stopping cleanly
To stop a runner without interrupting work in progress, drain it. The runner finishes its current session and then exits:
set-forth-runner drain
Pressing Ctrl-C does the same thing, as does sending SIGTERM on macOS and Linux. On Windows, use drain — ending the task is a hard kill, not a graceful stop.
Checking the config
To confirm a config file resolves to a complete runner identity without actually starting the runner:
set-forth-runner validate-config --config ~/.set-forth-runner/runner.config.yml
Configuration reference
Beyond the connection values, runner.config.yml accepts optional settings:
agent.permission_mode—defaultruns the agent with its normal permission prompts;skipbypasses them. Useskipwith caution.agent.allowed_tools— a list restricting which tools the agent may use (for exampleRead,Edit,Write,Bash). Omit it to allow the defaults.agent.disallowed_tools— a list of tools to deny. A denied tool wins over the allow list.agent.max_turns— a cap on how many turns the agent may take per session.agent.env— extra environment variables to pass to the agent process.local_logs— write per-session logs on the host for local troubleshooting. Setenabled: trueand, optionally, adirectory(you can also override the location with theRUNNER_LOG_DIRenvironment variable).
A sample config with the connection values plus a few optional settings. Adding optional settings to a file the setup command wrote is safe: a later re-run merges into it and only replaces the identity fields. Leave platformUrl as you found it, whether the setup command wrote it or you copied it from the dashboard. Pointing a runner at the wrong origin is the usual reason it registers and then picks up no work.
runnerId: your-runner-id
orgId: your-org-id
platformUrl: # the origin already in your config, unchanged
apiKey: your-secret-api-key
agent:
permission_mode: default
allowed_tools:
- Read
- Edit
- Write
- Bash
max_turns: 100
env:
MY_CUSTOM_VAR: "value"
local_logs:
enabled: true
directory: ~/.set-forth-runner/logs
Updating
One command installs the latest version and drains the runner so it restarts on the new build:
set-forth-runner update
If you run the runner as a managed service, it restarts automatically after draining. If you run it in the foreground, start it again after the upgrade. If the installed version did not change, the runner is left running.
