Coding Agent Runner
Windshift can run AI coding agents for you. Assign a work item to an agent and Windshift checks out the repository, runs the agent in a throwaway container, pushes the run branch, and opens a draft pull request with the result.
You can run agents two ways:
- On the Windshift server — the quickest setup; agents run on the same host as Windshift.
- On separate runner hosts — offload execution to one or more dedicated machines, so Docker and untrusted code stay off your server and you can scale out.
Remote runners can be installed as a systemd service or run as a Docker container. In both modes the runner starts one fresh agent container per job.
This guide assumes you have already installed and are running Windshift.
Before you start
You need:
- A Docker daemon on whichever host will run agents. Agent jobs run in ephemeral containers.
- A Git connection configured in your workspace, so the agent has a repo to clone and push to.
- An LLM connection exposed to the workspace if the agent should use a managed model connection.
- An agent binding in the workspace. A run starts when a work item is assigned to the bound agent user.
Option A — run agents on the Windshift server
Use this when the Windshift host is allowed to run Docker and you want the simplest deployment. Windshift prepares the repository, starts the agent container locally, and pushes the result branch from the server side.
1. Give Windshift access to Docker
Windshift shells out to the docker CLI, so the server process must reach a
Docker daemon.
Windshift on a host (VM / bare metal): install Docker and make sure the Windshift process user can run
docker.Windshift in a container: mount the host Docker socket so its
dockerCLI talks to the host daemon:volumes: - /var/run/docker.sock:/var/run/docker.sock
2. Configure it
CODING_AGENT_RUNNER_IMAGE=ghcr.io/windshiftapp/windshift-agent:latest
CODING_AGENT_WORKTREE_ROOT=/var/lib/windshift/coding-agentCODING_AGENT_WORKTREE_ROOT is an absolute path where Windshift checks out a
copy of the repo for each run.
If agent containers can't reach the browser-facing BASE_URL (for example when
BASE_URL is localhost), also set the API address they should use. This URL
must end in /api:
CODING_AGENT_WS_API_URL=https://windshift.example.com/apiIf Windshift runs in a container using the host Docker socket, the worktree path must exist on the host and be mounted into the Windshift container at the same absolute path, so the host daemon can mount it into agent containers:
environment: - CODING_AGENT_WORKTREE_ROOT=/var/lib/windshift/coding-agent volumes: - /var/run/docker.sock:/var/run/docker.sock - /var/lib/windshift/coding-agent:/var/lib/windshift/coding-agent
3. Restart Windshift
Agents are enabled on startup once CODING_AGENT_RUNNER_IMAGE is set. Assign a
work item to your agent user to kick off the first run.
Docker Compose example
services:
windshift:
image: ghcr.io/windshiftapp/windshift:latest
environment:
- BASE_URL=https://windshift.example.com
- CODING_AGENT_RUNNER_IMAGE=ghcr.io/windshiftapp/windshift-agent:latest
- CODING_AGENT_WORKTREE_ROOT=/var/lib/windshift/coding-agent
- CODING_AGENT_WS_API_URL=https://windshift.example.com/api
volumes:
- windshift-data:/data
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/windshift/coding-agent:/var/lib/windshift/coding-agent
volumes:
windshift-data:Optional resource limits
CODING_AGENT_DOCKER_BINARY=docker
CODING_AGENT_NETWORK=coding-agent-egress
CODING_AGENT_PIDS_LIMIT=512
CODING_AGENT_MEMORY=4g
CODING_AGENT_CPUS=2| Variable | Default | Description |
|---|---|---|
CODING_AGENT_DOCKER_BINARY |
docker |
Docker CLI to invoke |
CODING_AGENT_NETWORK |
coding-agent-egress |
Docker network for agent containers |
CODING_AGENT_PIDS_LIMIT |
512 |
Max processes per container |
CODING_AGENT_MEMORY |
4g |
Memory and memory-swap limit per container |
CODING_AGENT_CPUS |
2 |
CPU limit per container |
CODING_AGENT_LLM_MODEL |
— | Fallback model id when a binding has no LLM connection |
Windshift uses a Docker network named coding-agent-egress by default; create it
and apply your own egress rules, or set CODING_AGENT_NETWORK=bridge to use
Docker's default network.
Option B — run agents on separate hosts
Use remote runners when you don't want Docker and untrusted code on the Windshift server, or when you want to scale execution across several machines. Each runner host joins a runner pool and pulls work from Windshift. Your Windshift server does not need Docker in this mode.
A remote runner can run either:
- as a Docker container that talks to the host Docker daemon; or
- as a systemd service installed on the host.
In both cases the runner uses the host Docker daemon to start the per-job
windshift-agent containers. The runner itself is not the agent; it claims jobs,
prepares the checkout, starts the agent container, pushes the result branch, and
reports the result.
1. Create a runner pool and registration token
In Windshift, an admin creates a runner pool and mints a registration token
(wsrt_…). Registration tokens are one-time bootstrap secrets. On first start,
the runner exchanges the token for a per-instance credential (wsrc_…) and then
stores/reuses that credential for future restarts.
Use one registration token per runner instance. For immutable deployments you
can also inject an existing per-instance credential with WSRUNNER_CREDENTIAL.
2. Run the runner with Docker
This is the easiest remote-runner setup. The runner container talks to the host Docker daemon through the mounted socket and asks it to start sibling agent containers.
services:
windshift-runner:
image: ghcr.io/windshiftapp/windshift-runner:latest
restart: unless-stopped
environment:
WS_API_URL: https://windshift.example.com/api
WSRUNNER_REGISTRATION_TOKEN: wsrt_your_one_time_token
WSRUNNER_IMAGE: ghcr.io/windshiftapp/windshift-agent:latest
# Optional display name shown in Windshift:
# WSRUNNER_NAME: runner-1
volumes:
# Lets the runner start sibling agent containers on the host daemon.
- /var/run/docker.sock:/var/run/docker.sock
# Must be the same path on host and in the runner container.
- /var/lib/windshift-runner:/var/lib/windshift-runnerThen start it:
docker compose up -dThe agent itself is not a long-running service. The runner starts a fresh,
sandboxed agent container for each job and pulls WSRUNNER_IMAGE on the host the
first time it is needed.
The
/var/lib/windshift-runnermount must resolve to the same absolute path on the host and inside the runner container. The host Docker daemon — not the runner container — bind-mounts each prepared checkout into the agent container as/workspace.
Podman works too. With rootful Podman, point the socket mount at the Podman socket instead:
volumes:
- /run/podman/podman.sock:/var/run/docker.sock
- /var/lib/windshift-runner:/var/lib/windshift-runnerRootless Podman also works but needs extra care: because it remaps user IDs, the
agent's /workspace mount can hit permission issues unless you account for the
mapping. Rootful Podman is the smoother path.
3. Or install the runner as a systemd service
Use the systemd install when you prefer native host processes or do not want the runner itself containerized.
Each host needs:
- Linux with systemd
- Docker
git- Outbound HTTPS to your Windshift server
Download the windshift-runner and windshift-triage binaries plus the runner
installer from your Windshift release, then run:
sudo ./install.sh --bin-dir ./distThe installer sets up the runner as a systemd service, creates its user and
working directory, installs windshift-runner and windshift-triage, and writes
a configuration file at /etc/windshift-runner/runner.env.
Edit /etc/windshift-runner/runner.env:
WS_API_URL=https://windshift.example.com/api
WSRUNNER_REGISTRATION_TOKEN=wsrt_your_one_time_token
WSRUNNER_IMAGE=ghcr.io/windshiftapp/windshift-agent:latestThen pull the agent image and start the service:
docker pull ghcr.io/windshiftapp/windshift-agent:latest
sudo systemctl start windshift-runner
journalctl -u windshift-runner -fYou should see the runner register, persist its per-instance credential, then claim and run jobs assigned to its pool.
Runner configuration reference
| Variable | Required | Default | Purpose |
|---|---|---|---|
WS_API_URL |
✓ | — | Windshift API base URL ending in /api (for example https://host/api). The runner control plane and brokers live here; this is not /rest/api/v1 and not the bare host URL. HTTPS is required unless WSRUNNER_ALLOW_INSECURE=1 is set for development. |
WSRUNNER_REGISTRATION_TOKEN |
first bootstrap | — | One-time pool registration token (wsrt_…). The runner exchanges it for a per-instance credential and does not need it again after the credential is stored. |
WSRUNNER_CREDENTIAL |
optional | — | Inject an existing per-instance runner credential (wsrc_…) instead of registering with a token. Useful for immutable deployments. |
WSRUNNER_CREDENTIAL_FILE |
optional | <cache>/credential |
Path where the runner stores/reuses its per-instance credential. |
WSRUNNER_IMAGE |
✓ | — | Agent container image to run, usually ghcr.io/windshiftapp/windshift-agent:latest. |
WSRUNNER_NAME |
hostname | Name shown for this runner. | |
WSRUNNER_DOCKER |
docker |
Docker-compatible CLI to invoke. | |
WSRUNNER_TRIAGE_BIN |
windshift-triage |
Path to the triage helper used for git prepare/push. | |
WSRUNNER_CACHE_ROOT |
/var/lib/windshift-runner/cache |
Host-local bare-clone cache. Keep it under the same-path bind mount when running the runner in Docker. | |
WSRUNNER_POLL_INTERVAL |
2s |
How often to check for work when idle. | |
WSRUNNER_HEARTBEAT_INTERVAL |
30s |
How often to renew the runner lease. | |
WSRUNNER_INITIAL_PROMPT |
server prompt | Emergency fallback only; normal jobs receive the server-managed prompt in the claim. | |
WSRUNNER_ALLOW_INSECURE |
unset | Set to 1 only for local development to allow a plaintext http:// WS_API_URL. |
Security
- Your SCM and LLM credentials stay on the Windshift server. Remote runners authenticate with a per-instance credential and per-run tokens. Git and LLM access flow through Windshift brokers, which inject the real provider credentials server-side.
- The agent container has no raw provider credentials. It uses short-lived run tokens and broker URLs instead.
- Each run is sandboxed. Agents run as a non-root user in a container with a read-only root filesystem, dropped capabilities, tmpfs scratch space, and the configured Docker network.
- A run can only push its own branch (
agent-runs/run-<id>). The git proxy gates pushes to the run's single granted ref. - Docker access is powerful. On any host that runs agents, the Windshift or runner process can control the Docker daemon, which is effectively root on that host. Run agent hosts as dedicated, disposable machines.
- Registration tokens are single-use. Remove
WSRUNNER_REGISTRATION_TOKENafter first bootstrap if your deployment process allows it; the persisted per-instance credential is what the runner uses on restart.
Troubleshooting
- Runs never start — confirm
CODING_AGENT_RUNNER_IMAGE(Option A) or the pool +WSRUNNER_IMAGE(Option B) is configured, and that the agent image can be pulled by the Docker daemon that starts jobs. - Runner says
WS_API_URL must be https://— use an HTTPS URL ending in/api, or setWSRUNNER_ALLOW_INSECURE=1only for local development. - Agent can't reach Windshift — set
CODING_AGENT_WS_API_URL(Option A) or checkWS_API_URLand outbound HTTPS (Option B).localhostinside a container is not your Windshift server. - Permission denied talking to Docker — the Windshift process (Option A) or runner process/container (Option B) must be able to access the Docker daemon.
- Remote git prep/push fails — confirm
windshift-triageis installed (or present in the runner image),gitis available, and the runner can reachWS_API_URLover HTTPS. - Runner keeps asking for a registration token after restart — check that
WSRUNNER_CREDENTIAL_FILEis writable and persisted. In Docker, keep/var/lib/windshift-runnermounted as a volume.
See Environment Variables
for the full configuration reference, and Coding Agents
for wiring editor agents like Claude Code or Cursor to the ws CLI.