Environment Bootstrap¶
What is bootstrapping?¶
Everything here is local-first. Bootstrap runs against your machine's Docker daemon, and the tasks it emits also run locally by default (harbor run -a oracle -p ./task). The same task directory is portable to cloud sandboxes (E2B / Modal / Daytona / Runloop) when you opt in via harbor run --env <name> — but you never have to leave your laptop to use any of this.
To turn a GitHub repo into a verifiable RL task, you need a Docker image where that repo builds cleanly and its test suite runs. That image is what the synthesized tasks check out into, apply patches against, and verify with pytest / go test / cargo test / etc.
Different repos have wildly different setup needs:
- Python projects use uv / poetry / pip / conda + system libs (
gcc,libpq, ...) - JS/TS projects use pnpm / yarn / npm / bun, sometimes inside monorepos
- Go projects use modules + occasional build tags + CGO toolchains
- Rust projects pin specific toolchain versions
- Java projects use Maven or Gradle + a specific JDK
- C/C++ projects use CMake / Make / Bazel / Meson + system deps
Hand-writing a Dockerfile per repo is unworkable when the goal is "any repo." So the bootstrap phase does it for you: an LLM agent reads the repo, runs shell commands inside a sandboxed container, iterates until the build + tests succeed, then commits the result as a Docker image. The image is cached locally and reused for every subsequent task generated from that repo.
You only need bootstrap for pipelines that actually run code (pr_runtime, commit_runtime, cve_patches, code_instruct, equivalence_tests). The text-only pr_diff pipeline skips it entirely.
Supported languages¶
The bootstrap agent has built-in presets for these languages — base images, install hints, sanity checks, and known pitfalls are injected into the system prompt automatically:
| Language | Base image | Detected from |
|---|---|---|
| Python | python:3.12-slim | pyproject.toml, setup.py, setup.cfg, requirements.txt |
| Node / TypeScript | node:22-slim | package.json, tsconfig.json, pnpm-lock.yaml, yarn.lock |
| Go | golang:1.23 | go.mod |
| Rust | rust:1-slim | Cargo.toml |
| Java / Kotlin / Scala | eclipse-temurin:21-jdk | pom.xml, build.gradle, build.gradle.kts |
| C / C++ | ubuntu:24.04 | CMakeLists.txt, configure.ac, Makefile, meson.build |
Other languages still work, they just don't get language-specific hints — the agent falls back to a generic Ubuntu base image and figures it out from README.md + repo files.
Override the auto-detected language with --language <name> or --base-image <image> if you need a specific toolchain (e.g. Python 3.11 instead of 3.12, or a CUDA-enabled base).
Pipeline ↔ language compatibility¶
Some pipelines work on any language (they operate on diffs, PRs, commits); others are restricted to Python because they parse Python AST or emit pytest verifiers.
| Pipeline | Languages |
|---|---|
pr_diff, pr_runtime, commit_runtime, cve_patches | any |
code_instruct, equivalence_tests | Python only |
The CLI runs a pre-flight check: if you point a Python-only pipeline at a non-Python repo, generation aborts before bootstrap even starts so you don't burn 5+ minutes finding out. Pass --force-language to skip the check and proceed anyway (the pipeline will likely emit zero tasks; explicit user choice).
Choosing an LLM¶
The bootstrap agent works with any LLM that LiteLLM supports. In practice:
- A strong code-focused model (e.g. a recent Qwen-Coder via Hugging Face Inference Providers) handles clean libraries and CLI tools at the lowest cost.
- A strong general-purpose model (e.g. a recent Claude Sonnet) handles moderately complex repos — compiled extensions, monorepos, non-trivial build systems.
- A flagship reasoning model (e.g. Claude Opus or a frontier GPT) helps with full-stack apps or repos with unusual toolchain choices where the agent needs to debug across multiple layers.
You don't have to pick perfectly. A weaker model will iterate more turns; a stronger model will finish faster but cost more per turn. The cost cap (below) bounds the worst case either way.
The --llm flag accepts a LiteLLM-format string. Some examples:
| Provider | --llm value |
|---|---|
| Anthropic | anthropic/<model-name> |
| OpenAI | openai/<model-name> |
| Hugging Face Inference Providers | huggingface/<repo>:<provider> (e.g. huggingface/<org>/<model>:together) |
| Self-hosted vLLM / Ollama | openai/<your-model> with --llm-endpoint http://... |
Anything LiteLLM supports works, including Bedrock, Vertex, Mistral, and others.
API keys¶
Set the provider's default environment variable; the CLI picks it up automatically.
| Provider | Env var |
|---|---|
| Anthropic | ANTHROPIC_API_KEY |
| OpenAI | OPENAI_API_KEY |
| Hugging Face | HF_TOKEN |
| Other | override via --llm-key-env or YAML config |
Cost guardrail¶
Every bootstrap run is bounded by max_llm_spend_usd (default $5.0). When the running cost reaches the cap, the agent loop aborts cleanly. Set lower for tighter control:
Provider fallback¶
When the primary LLM returns a 5xx, rate-limit, network, or timeout error, the agent loop automatically retries with --llm-fallback. 4xx errors (wrong model id, auth) are NOT retried — those are config bugs you want to see immediately.
repo2rlenv generate \
--repo <owner>/<repo> --pipeline pr_runtime \
--llm "anthropic/<sonnet-model>" \
--llm-fallback "openai/<gpt-model>" \
--out ./datasets/<name>
Fallback chains are capped at 3 levels deep so misconfigured loops don't run forever.
CLI cheatsheet — switching LLMs¶
The --llm flag drives both the bootstrap phase AND any LLM-synthesized pipeline steps. Pick whichever model fits your repo + budget:
# Code-focused open model via Hugging Face Inference Providers
repo2rlenv generate \
--repo <owner>/<repo> \
--pipeline pr_runtime --pipeline-opt limit=10 \
--llm "huggingface/<org>/<model>:<provider>" \
--out ./datasets/<name>
# Claude Sonnet (general-purpose)
repo2rlenv generate \
--repo <owner>/<repo> \
--pipeline pr_runtime --pipeline-opt limit=10 \
--llm "anthropic/<sonnet-model>" \
--out ./datasets/<name>
# Claude Opus / frontier OpenAI for hard apps; raise the cost cap
repo2rlenv generate \
--repo <owner>/<repo> \
--pipeline pr_runtime --pipeline-opt limit=10 \
--llm "anthropic/<opus-model>" \
--bootstrap-opt max_llm_spend_usd=3.0 \
--out ./datasets/<name>
# Self-hosted vLLM or Ollama (OpenAI-compatible endpoint)
repo2rlenv generate \
--repo <owner>/<repo> \
--pipeline pr_runtime --pipeline-opt limit=10 \
--llm "openai/<your-model>" \
--llm-endpoint http://your-vllm-host:8000/v1 \
--out ./datasets/<name>
Bootstrap-only (no synthesis)¶
To build + cache the image without running a pipeline:
repo2rlenv bootstrap \
--repo <owner>/<repo> \
--ref main \
--llm "anthropic/<sonnet-model>" \
--max-iterations 25 \
--max-spend-usd 2.0 \
--image-registry ghcr.io/<your-org>/<repo>-r2e \
--out ./workspace/bootstrap/<repo>/
Forcing a cache miss¶
A previously-cached image is reused on later runs. To force the agent to re-run from scratch (e.g. after a base-image upgrade):
Self-supplied Dockerfile (bypass the agent)¶
For tightly-controlled environments, skip the LLM iteration entirely and build a Dockerfile you wrote:
repo2rlenv generate \
--repo <owner>/<repo> \
--pipeline pr_runtime \
--bootstrap-opt user_dockerfile=./my-Dockerfile \
--out ./datasets/<name>
The agent loop is skipped; we just build your Dockerfile, commit the result, and use it as the bootstrap image.
How bootstrap works internally¶
Bootstrap is a separate phase, run once per (repo, commit) and cached. Inline iteration inside the per-task synthesis loop would be wasteful and slow.
flowchart TD
subgraph "Phase 1 — Bootstrap (once per repo+ref, cached)"
A[Repo URL + ref] --> B[Read CI files,<br/>README, setup.py,<br/>package.json, ...]
B --> C[LLM agent: propose a<br/>shell command]
C --> D[Run inside Docker sandbox]
D --> E{Build + tests<br/>collect cleanly?}
E -- no --> F[Read stderr,<br/>propose next command]
F --> C
E -- yes --> G[docker commit →<br/>pin image_digest]
G --> H[Cache result.json +<br/>transcript + Dockerfile]
end
subgraph "Phase 2 — Synthesis (per task, reuses Phase 1)"
H --> K[For each candidate PR/commit]
K --> L[Fork from bootstrap image]
L --> M[git checkout base commit]
M --> N[Apply patch, run tests]
N --> O[Capture fail-to-pass]
O --> P[Emit Harbor task<br/>with bootstrap image_digest]
end You pay the LLM agent cost once per (repo, commit); every later task generated from that repo reuses the cached image for free.
The agent is implemented as a ReAct-style Thought / Action / Input loop with tools for shell execution, file reading, and a structured SAVE_SETUP call that ends the session by recording the rebuild + test commands the rest of the system uses. Per-language hints (Python / Node / Go / Rust / Java / C-C++) get injected into the agent's system prompt so it doesn't have to rediscover ecosystem conventions on every run.
Cache strategy¶
Bootstrap is expensive (multi-minute, real LLM cost). Reuse aggressively.
The cache key is (repo, commit, base_image, languages_hint). Anything that would produce a different image goes into the key; iteration limits and budget caps don't (they bound the build, not the result).
A successful bootstrap writes to ./workspace/bootstrap/<owner>__<name>/<short_commit>/:
./workspace/bootstrap/<owner>__<name>/<short_commit>/
├── result.json # image_digest, image_tag, rebuild_cmds, test_cmds, ...
├── transcript.jsonl # full agent transcript (for debugging)
└── (Dockerfile generated from the transcript, for reproducibility)
Subsequent calls against the same (repo, commit) hit this cache and skip the agent entirely. If you push the image to a registry via --image-registry, the cache directory is enough for collaborators to pull and reuse — they don't need to re-run bootstrap.
When bootstrap fires¶
There are two invocation patterns:
-
Implicit (the default) —
repo2rlenv generatenotices the pipeline needs a sandbox image and triggersensure_bootstrap()itself. Subsequent runs hit the cache and skip the agent. This is the path the CLI examples above use. -
Explicit — call
repo2rlenv bootstrap ...to pre-warm an image without running synthesis, or to debug a repeatedly-failing build. The cached image is then picked up by any latergeneratecall against the same repo+ref.
Use the explicit form when: - Debugging a build that fails repeatedly (full transcript at ./workspace/bootstrap/<repo>/<sha>/transcript.jsonl) - Pre-warming an image for many subsequent generation runs - Sharing a working image with collaborators (push the env dir + image_digest)
Configuring bootstrap¶
The bootstrap phase is driven by BootstrapSpec. The fields you'll touch most often:
| Field | What it does |
|---|---|
enabled | Toggle the whole phase. Set to False if you supply a pre-built image elsewhere. |
max_iterations | Cap on agent turns (default 20, raise for large repos). |
max_seconds | Wall-clock cap (default 1800s). |
max_llm_spend_usd | Hard cost cap (default $5.0). Loop aborts cleanly when crossed. |
base_image | Override the auto-detected Docker base image. |
user_dockerfile | Path to a Dockerfile that bypasses the agent entirely. |
cache_dir | Where to store cached results (default ./workspace/bootstrap/). |
image_registry | If set, push the built image here so collaborators can pull it. |
languages_hint | Override language auto-detection (e.g. ["python", "rust"]). |
You can set any of these on the CLI via --bootstrap-opt key=value (repeatable) or in YAML config:
# Point at a different cache directory (e.g. reuse a teammate's pre-built images)
repo2rlenv generate ... \
--bootstrap-opt cache_dir=./workspace/bootstrap-shared \
--bootstrap-opt max_iterations=30 \
--bootstrap-opt max_seconds=2400
The flag accepts any field on BootstrapSpec. Booleans, numbers, and JSON values are coerced automatically.
What ships in each generated task¶
Every task built by a sandbox-required pipeline carries the bootstrap provenance in its task.toml:
[metadata.repo2env.bootstrap]
image_digest = "ghcr.io/<your-org>/<repo>-r2e/bootstrap@sha256:..."
bootstrap_commit = "a1b2c3d..."
llm = "anthropic/<model-name>"
iterations = 3
build_time_sec = 247
That's enough for a consumer to rebuild the environment from scratch — the agent's full transcript is stored in the cache directory and the reconstructed Dockerfile lives alongside the image digest.
Edge cases to know about¶
Bootstrap fails after max_iterations¶
The agent couldn't make the repo build within budget. You'll see BootstrapError with the last few lines of stderr and a pointer to the transcript. Common next steps:
- Raise
max_iterations/max_llm_spend_usdand retry. - Use a stronger LLM (see Choosing an LLM).
- Hand the agent a working Dockerfile via
--bootstrap-opt user_dockerfile=./my-Dockerfile.
Repo requires docker-compose (Postgres / Redis / a web service)¶
Bootstrap currently produces a single image. Multi-container apps need a docker-compose setup, which isn't yet supported — for now, supply a user_dockerfile that includes the auxiliary services, or pin to a Harbor backend that supports compose (e.g. Daytona).
Repo requires GPU at build time¶
Some ML kernels (e.g. CUDA extensions) can't build on a CPU sandbox. Run on a GPU-enabled Harbor backend (Modal A100 / H100) — the bootstrap inherits GPU access from SandboxSpec.gpu.
PR introduces a new dependency¶
Each pipeline that generates per-PR tasks defaults to re-installing dependencies after git checkout. So an additive dep introduced by a PR is captured automatically; you don't need to re-bootstrap. If a PR replaces the build system entirely, you'll want a fresh bootstrap for that commit — pass --force-bootstrap.
Stale cached image¶
A bootstrap from months ago may use outdated base images. Re-run with --force-bootstrap to regenerate.
Cost overrun¶
The max_llm_spend_usd cap aborts the agent loop the moment the running total crosses the threshold. No quiet runaway costs.
Architecture-mode pointers (for contributors)¶
- The bootstrap agent + sandbox primitives live under
src/repo2rlenv/bootstrap/. Entry point:ensure_bootstrap(repo, spec, llm). - The agent loop is a ReAct-style Thought / Action / Input parser; tools are
BASH,READ_FILE,LIST_DIR,SAVE_SETUP,GIVE_UP. - Per-language hints live in
bootstrap/presets.pyand get injected into the agent's system prompt — that's where to add new languages or fix common mistakes the agent makes for a given ecosystem. - See
CONTRIBUTING.mdfor how to add tests + the lint/format flow.
See also¶
- SPEC.md — input/output contract
- pipelines/ — per-pipeline docs
- SWE-bench-Live paper — broader context for live, automated dataset curation