Workbench

Make a repo factory-ready

The workbench factory clones your repo into an isolated box, runs a coding agent against a test gate until it passes, and opens a pull request. This page is the one-page contract for repo authors: how the factory decides where your gate runs, and how to make it work.

Your repo must declare a container

The gate always runs inside a container, never on the host box - so your repo must declare one, or the run fails with an error. You give the factory a gate - a shell command that exits 0 when the change is good (pytest, cargo test, bundle exec rspec, ./ci.sh) - and the factory runs it inside the environment your repo declares. It looks, in order:

  1. .devcontainer/devcontainer.json (or .devcontainer.json) - the image key, or a build.dockerfile (+ optional build.context). Parsed leniently (comments and trailing commas allowed). Features and lifecycle hooks are not honored in v1.
  2. A root Dockerfile - built and the gate runs in it.
  3. A compose file (compose.yaml, compose.yml, docker-compose.yml, or docker-compose.yaml) with a service named exactly test - the gate runs via docker compose run --build --rm test, so your service's own dependencies come up too.

If a repo declares none of these, the run stops with "no container environment declared" - it never falls back to the host. To force a mode (or point at a pre-built image), pass the env input: image:<ref> | dockerfile[:<path>] | compose:<service>[:<file>].

If you already have a hand-built environment, point at it directly with env: "image:<registry ref>" (or a devcontainer image) - the factory pulls and runs it, no build. For a complex setup (Docker-in-Docker, Kubernetes, anything needing privileged or a mounted socket), use compose mode: put a test service in your compose file that declares exactly what it needs (privileged: true, volumes, the docker socket, dependency services), and the factory runs that service as you wrote it - it does not override your service's config. That is the clean home for a day-or-two, human-built environment.

The five rules

  1. Layer your Dockerfile for cache. Copy your dependency manifests, install, then copy the source:

    FROM python:3.12-slim
    WORKDIR /workdir
    COPY requirements.txt .
    RUN pip install -r requirements.txt
    COPY . .

    The factory rebuilds your image on every attempt (the agent's edits have to reach it), so a well-layered Dockerfile means every rebuild after the first reuses cached layers instead of reinstalling dependencies. A naive COPY . . before install reinstalls every dependency on every agent edit.

  2. Your image needs a shell. In-container gates run with sh -c. Alpine-based and normal images are fine; scratch/distroless images (no shell) are unsupported. If your gate uses bash-only syntax, make sure your image provides bash.

  3. Your test service (compose mode) mounts or copies the repo, and is rebuilt each attempt. The factory runs the service the way your CI does - it does not remount the workdir - so the service must already see your code (a bind mount, or COPY at build with --build, which the factory passes).

  4. .gitignore your gate's byproducts. The factory commits every non-ignored file in the workdir. Ignore __pycache__/, target/, node_modules/, coverage files, and anything your gate writes, or they land in the PR.

  5. Mind who writes into the workdir. Image and Dockerfile gates run as the box user, so the agent can keep editing files the gate wrote. A compose service runs as whatever user: it declares (root by default); if your test service writes into a bind-mounted repo, set user: on it or your byproducts land root-owned. A gate that genuinely needs in-container root needs a repo-side arrangement.

  6. Put tool caches somewhere the box user can write. Because image/Dockerfile gates run as a non-root user with HOME=/tmp, a tool that caches under the image's root home breaks. One example: mvn caches to /root/.m2, but the gate reads /tmp/.m2 (empty). Point the tool at a user-writable cache - e.g. Maven with -Dmaven.repo.local=/tmp/.m2/repository, or ENV MAVEN_OPTS / a .mvn/maven.config in the repo - and (rule 4) .gitignore it. The same applies to gradle, Go's module cache, etc.

What the factory provides

  • Docker + the compose plugin on the box - your repo brings the language toolchain.
  • The agent runs on the box (it needs git and the coding-agent CLI); the gate runs in your environment. The agent is told the exact in-container command so it can self-verify.
  • A cold base-image pull happens once, at clone, off the metered agent path. A broken environment build comes back as fixable feedback (with the build log), so fixing the Dockerfile itself can be part of making the tests run again.

Timeouts

The environment build and the gate carry separate budgets: build_timeout_ms and gate_timeout_ms (both default 600000, i.e. 10 min, clamped 1s..30min). Raise build_timeout_ms for a heavy image build - note the cold base-image pull happens once at clone time on the default budget, so a very large first image may want a bigger default there too. If your coding-agent timeout plus a large build plus the gate could exceed the workflow engine's default node timeout (3600s), add a timeout attr to the loop-body node.

Next

On this page