Self-Hosted Runners
Harness agents do their work on a runner: a Linux machine that runs each agent's turn in a sandboxed container. XPT can provide runners for you, or you can run one on hardware of your own — a VPS, a dedicated server, or a machine in your own network.
A self-hosted runner keeps the agents' working files, the code they run and their sandboxes on your machine. This page is for the administrator who sets one up.
How it fits together
| Direction | The runner connects out to XPT over HTTPS and WebSocket. Nothing connects in, so no port needs to be opened. |
| What it does | It takes turns for the workspaces it serves, runs each one in its own container, and sends back the agent's messages and files. |
| What stays on the host | Each thread's working directory, under /var/lib/xpt-runner, plus the containers. |
| What leaves the host | Turn events and result files to XPT; the agents' own calls to Anthropic or OpenAI with your workspace's keys; image downloads. No telemetry, no analytics. |
| Credentials | The runner token is stored root-only on the host. Provider keys and XPT tool tokens are issued per turn and never written to disk. |
Requirements
- A dedicated Ubuntu 24.04 machine, amd64. 22.04 works with a warning; other systems are refused.
- Root access over SSH.
- Outbound HTTPS to
app.xpt.ai,registry.xpt.ai, the model providers, and the public internet if your agents need it. - Size it for the work. Each reply defaults to 2 CPUs and 2 GB of memory (4 GB for tasks and jobs), and the runner stops taking work below 5 GB free disk. 4 vCPU / 8 GB / 100 GB is a sensible start for a team.
Laptops and WSL work for development only.
1. Get a runner token
Ask XPT (support@xpt.ai) for a runner token, and say which workspaces the runner should serve and how many replies it may run at once.
The token starts with xptrn_ and is shown once. Keep it like a password: anyone holding it can take work as your runner.
2. Prepare the host
On a fresh machine, as root:
curl -fsSL https://app.xpt.ai/install/runner-host.sh | sudo sh
The script prepares the machine for running untrusted code:
| Area | What it sets up |
|---|---|
| SSH | Key-only login (--ssh-key "ssh-ed25519 …" or --ssh-key-file PATH adds yours) |
| Firewall | ufw with only SSH inbound, and fail2ban |
| Docker | Docker Engine and the compose plugin from Docker's own repository, with live-restore and log rotation |
| Updates | Ubuntu's security updates install unattended, without automatic reboots. Docker, containerd and gVisor are left out and updated by you (see Updates) |
| Kernel | Hardening: pointers and dmesg hidden, unprivileged BPF off |
| Time | UTC with time sync |
It is safe to run again. Add --gvisor to install the gVisor runtime (see below).
3. Install the runner
curl -fsSL https://app.xpt.ai/install/runner.sh | sudo sh -s -- --api-url https://app.xpt.ai
The installer asks for the token at a hidden prompt. You can pass it as --token-file PATH or in the XPT_RUNNER_TOKEN environment variable instead.
It then:
- logs in to
registry.xpt.aiwith the token; - pulls the runner and sandbox images;
- verifies their signatures;
- starts the runner with Docker Compose under
/opt/xpt-runner.
When it prints Installed, the runner appears in XPT within a heartbeat.
| Option | Purpose |
|---|---|
--gvisor |
Run every turn under gVisor (install the runtime with runner-host.sh --gvisor first) |
--version X.Y.Z |
Install a specific version instead of the latest |
--workdir PATH |
Keep working directories somewhere other than /var/lib/xpt-runner |
--env KEY=VALUE |
Pass a setting to the runner, e.g. XPT_RUNNER_AUTO_UPDATE=false |
--uninstall |
Remove the runner. Working directories are left in place for you to delete |
To watch it work:
docker compose -p xpt-runner -f /opt/xpt-runner/compose.yml logs -f
Isolation
Every turn runs in its own container:
- as an unprivileged user with every capability dropped;
- on a read-only system disk;
- with CPU, memory, process and disk limits;
- seeing only its own thread's working directory.
Network fence. By default a turn can reach the internet but not:
- the cloud metadata endpoint;
- the runner's own machine;
- private networks (10/8, 172.16/12, 192.168/16, 100.64/10).
The runner proves the fence at startup and takes no work if it cannot put it up.
gVisor. With --gvisor, each turn runs under gVisor (runsc), a user-space kernel between the agent's code and the host. It adds a strong boundary at a small cost:
| Without gVisor | With gVisor | |
|---|---|---|
pip install of a typical package set |
12.8 s | 13.5 s |
npm install |
10.0 s | 12.1 s |
git clone |
1.06 s | 1.27 s |
We recommend gVisor for a runner that serves more than one organisation.
Settings
How much a runner takes on, and what each reply may use, is set on the XPT side and reaches the runner without a restart:
| Setting | Default |
|---|---|
| Replies at once — discussions / tasks / jobs | 4 / 1 / 1 |
| One discussion reply | 2 CPUs, 2 GB memory, 8 GB scratch disk, stops after 5 min of silence |
| One task or job | 2 CPUs, 4 GB memory, 16 GB scratch disk, stops after 30 min of silence |
| Warm replies — how long a thread's sandbox is kept frozen after a reply | 20 min |
| Warm sandboxes at most | 8 (the one kept longest goes first) |
The limits of a reply are set per runner, so a larger machine can give more. XPT's own runner, for one, gives a discussion reply 4 GB of memory and 15 minutes of silence, and a task 4 CPUs and 8 GB. To change yours, write to support@xpt.ai.
A warm sandbox is frozen: it holds the agent's session in memory (about 60–150 MB) but uses no CPU and takes no reply slot. It lets the thread's next reply start a second or more sooner. Setting the warm time to 0 turns it off.
Updates
Runners update themselves to the version XPT recommends. An update:
- waits for running replies to finish;
- verifies the new images' signatures;
- switches over;
- rolls back by itself if the new version does not come up within three minutes.
To manage updates yourself, install with --env XPT_RUNNER_AUTO_UPDATE=false and rerun the installer with --version when you choose.
The host's own software
Ubuntu's security updates install by themselves. Docker, containerd and gVisor do not: they are what replies run on, and an upgrade restarts Docker underneath them. You update those, at a time you choose:
- Pick a moment when no reply is running — Admin → Runners shows what the runner is doing.
- Run the host script again:
curl -fsSL https://app.xpt.ai/install/runner-host.sh | sudo sh(add--gvisorif you installed with it). It brings every package up to date, Docker included. - The runner notices that Docker restarted and restarts itself within a few minutes;
docker restart xpt-runner-runner-1does it at once. - If the script says a reboot is needed, reboot when it suits you.
Do this about once a month, and sooner when Docker or gVisor publish a security fix.
Housekeeping
- A thread's working directory is kept for 30 days after it was last used, then removed. A copy is also kept in XPT's storage, so a thread can continue on another runner.
- Warm sandboxes are removed when their time runs out, and when the runner restarts.
- The runner stops taking new work when free disk falls below 5 GB, and resumes when space is back.