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:
.devcontainer/devcontainer.json(or.devcontainer.json) - theimagekey, or abuild.dockerfile(+ optionalbuild.context). Parsed leniently (comments and trailing commas allowed). Features and lifecycle hooks are not honored in v1.- A root
Dockerfile- built and the gate runs in it. - A compose file (
compose.yaml,compose.yml,docker-compose.yml, ordocker-compose.yaml) with a service named exactlytest- the gate runs viadocker 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
-
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 . .beforeinstallreinstalls every dependency on every agent edit. -
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. -
Your
testservice (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, orCOPYat build with--build, which the factory passes). -
.gitignoreyour 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. -
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 yourtestservice writes into a bind-mounted repo, setuser:on it or your byproducts land root-owned. A gate that genuinely needs in-container root needs a repo-side arrangement. -
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:mvncaches 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, orENV MAVEN_OPTS/ a.mvn/maven.configin the repo - and (rule 4).gitignoreit. 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
- Run the factory on cloud boxes - where the box itself comes from.
- Run coding work in an isolated factory - how a full run is sequenced.
- - exact tool inputs.