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 docker CLI 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-agent

CODING_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/api

If 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-runner

Then start it:

docker compose up -d

The 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-runner mount 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-runner

Rootless 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 ./dist

The 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:latest

Then 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 -f

You 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_TOKEN after 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 set WSRUNNER_ALLOW_INSECURE=1 only for local development.
  • Agent can't reach Windshift — set CODING_AGENT_WS_API_URL (Option A) or check WS_API_URL and outbound HTTPS (Option B). localhost inside 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-triage is installed (or present in the runner image), git is available, and the runner can reach WS_API_URL over HTTPS.
  • Runner keeps asking for a registration token after restart — check that WSRUNNER_CREDENTIAL_FILE is writable and persisted. In Docker, keep /var/lib/windshift-runner mounted 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.