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:

  1. logs in to registry.xpt.ai with the token;
  2. pulls the runner and sandbox images;
  3. verifies their signatures;
  4. 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:

  1. waits for running replies to finish;
  2. verifies the new images' signatures;
  3. switches over;
  4. 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:

  1. Pick a moment when no reply is running — Admin → Runners shows what the runner is doing.
  2. Run the host script again: curl -fsSL https://app.xpt.ai/install/runner-host.sh | sudo sh (add --gvisor if you installed with it). It brings every package up to date, Docker included.
  3. The runner notices that Docker restarted and restarts itself within a few minutes; docker restart xpt-runner-runner-1 does it at once.
  4. 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.