Run Generation
SWE task generation is driven by Curator's Claude plugin. Each slash command
runs its own preflight, loads configuration from config.yaml, and orchestrates
one stage of the workflow, so you operate the block through commands rather than
raw scripts.
Plugin commands
Curator ships six slash commands. Launch Claude from inside blocks/curator/
so the block-local plugin loads. Five commands drive the workflow you run by
hand; /curator:run is a compatibility adapter for generic block runners.
| Command | Stage | Purpose |
|---|---|---|
/curator:setup | Bootstrap (once) | Initializes the repos/swegen/ submodule, builds the virtualenv, installs swegen, and sets the LLM, GitHub, and Docker environment variables. Safe to re-run. |
/curator:check | Preflight | Validates config, the pinned repo, GitHub tokens, a live LLM completion, and the Docker daemon. Read-only, and required before generating tasks. |
/curator:collect-prs | Stage 1 — collect | Collects qualifying GitHub PRs into artifacts/collected_prs/{lang}_pr_ids.txt. Runs as a long-lived background job. |
/curator:create-tasks | Stage 2 — generate | Turns collected PR IDs into tasks and verifies each with NOP/Oracle checks, recording verified IDs in verifiable_tasks.txt. Does not collect PRs. |
/curator:dashboard | Dataset analytics | Regenerates and publishes the dataset databoard — per-task difficulty scores and semantic tags across the curated datasets. It analyzes datasets, not run progress. |
/curator:run | Adapter | Forwards its arguments to /curator:create-tasks so every block exposes a /<block>:run entry point. Operate Curator through the specific commands above; this exists for generic runners. |
Every command is documented in full under Command reference.
Recommended workflow
The simplest way: one prompt
The simplest way to run Curator is to paste a prompt like the one below into a
Claude session started inside blocks/curator/ — you don't even need to fill
in config.yaml. The agent fills the environment, runs preflight, and chains
the plugins for you. Just give it three things: your LLM endpoint + key,
where your GitHub tokens live, and how many verifiable tasks you want.
Run the whole curator pipeline through its plugins and build me at least <N> verifiable SWE task(s), python only.
LLM: base_url <BASE_URL>, model <MODEL>, key <API_KEY>. GitHub tokens are in the file <TOKEN_FILE>.
Go through the stages in order: /curator:setup → /curator:check → /curator:collect-prs →
/curator:create-tasks (--max-pr 5 is enough for a pipeline check) → /curator:dashboard.The agent writes your key into a gitignored .env (never config.yaml) and
reports the verified task at the end.
You can also do it by hand: edit config.yaml first, then run the commands one
by one — that gives you precise control over every parameter and lets you review
each stage's output before moving on.
Run the stages by hand
From a Claude session inside blocks/curator/, run the stages in order.
Bootstrap and preflight are one-time; collection and generation are the two
pipeline stages:
/curator:setup # once: init swegen submodule, build venv, install swegen
/curator:check # preflight: config, GitHub tokens, LLM endpoint, Docker
/curator:collect-prs # stage 1: collect PR IDs; wait for the background job to finish
/curator:create-tasks # stage 2: generate and verify tasks from collected PR IDsTrack run progress through the batch logs, verifiable_tasks.txt, and
artifacts/index.yaml — not through /curator:dashboard, which analyzes
finished datasets rather than live runs (see Dashboard).
Each command reads its parameters from config.yaml:
/curator:setupand/curator:checkreadruntime_info.input.llm_apito build the environment and probe the endpoint./curator:collect-prsreadsruntime_info.input.pr_collectionfor scope and filters./curator:create-tasksreadsruntime_info.input.languagesfor per-language timeouts and concurrency.
Edit config.yaml, then re-run the command.
Configuration Guide
All generation parameters live in config.yaml under runtime_info.input.
LLM API Configuration
Two provider modes are supported:
Mode 1: Native Anthropic-compatible endpoint (recommended)
llm_api:
api_key: <YOUR_API_KEY>
api_base_url: https://<your-anthropic-gateway>/v1 # OpenAI-compatible endpoint
pr_model: claude-opus-4-6
task_model: claude-opus-4-6
cc_provider_mode: native
anthropic_base_url: https://<your-anthropic-gateway> # Anthropic-compatible endpoint (no /v1)Mode 2: OpenAI-compatible model with local proxy
llm_api:
api_key: <YOUR_API_KEY>
api_base_url: https://your-openai-endpoint/v1
pr_model: Qwen3.6-35B-A3B
task_model: claude-sonnet-4-6 # proxy alias
cc_provider_mode: openai_proxy
anthropic_base_url: http://127.0.0.1:4010 # local LiteLLM proxy
cc_proxy_port: 4010Mode native is simpler: both PR evaluation and task completion use the same
provider. Mode openai_proxy is for OpenAI-only models: it starts a local
LiteLLM proxy to translate Anthropic calls to OpenAI format. Both shapes have
been validated end-to-end (NOP=0 / Oracle=1).
Field-by-field:
| Field | Fill with |
|---|---|
api_key | Leave as human in git; supply the real key via the environment or an ignored .env. |
api_base_url | The OpenAI-compatible endpoint, including the /v1 suffix. |
pr_model | Model for PR evaluation + instruction generation. Prefer a non-reasoning, JSON-reliable model. |
task_model | Model for the Claude Code path. In openai_proxy mode any claude-* alias works — the proxy maps it to pr_model. |
cc_provider_mode | native when the provider already speaks the Anthropic Messages API, openai_proxy otherwise. |
anthropic_base_url | native: the provider's Anthropic root, without /v1. openai_proxy: the local proxy URL. |
cc_proxy_port | openai_proxy only; ignored in native mode. |
Never commit real API keys or endpoints to config.yaml — not in the values
and not in comments. config.yaml is git-tracked; the root case
tests/cases/09_no_secrets_in_configs.sh fails the build if either shows up.
Use environment variables or .env files instead.
Endpoint verification
A reachable endpoint is not a working one. GET /models is answered from local
gateway config, so it can return 200 while every completion fails; and a
reasoning model can spend its whole budget on reasoning and return empty
content, which looks like success to any check that only asserts HTTP 200.
bash scripts/probe_llm_endpoint.sh # 0 PASS, 1 FAIL, 77 SKIPIt sends a real completion, retries transient failures, and requires a
non-empty text reply. In native mode it also probes the Anthropic
Messages path, because that is the half that writes verifiable_tasks.txt.
When the configured shape fails it retries the neighbouring shapes and names the edit to make — this catches the three mistakes that account for most setup failures:
| Symptom | Cause | What the probe prints |
|---|---|---|
HTTP 404 no route /chat/completions | api_base_url missing the /v1 suffix | api_base_url: https://host → https://host/v1 |
HTTP 200 but no text in the reply | provider/ prefix on a model the raw upstream doesn't know | model: openai/Qwen3.6-35B-A3B → Qwen3.6-35B-A3B |
no route /v1/chat/completions/chat/completions | /chat/completions pasted into api_base_url | the trimmed base URL |
anthropic_base_url is the mirror-image trap: in native mode it wants the
provider's Anthropic root without /v1 (the probe appends /v1/messages
itself); in openai_proxy mode it wants the local proxy URL.
PR collection and per-language parameters
PR collection (pr_collection: languages, repo_num, max_prs_per_repo,
filters) and per-language generation (languages.<lang>.params: timeout,
cc_timeout, n_concurrent) are configured in config.yaml and documented
inline there. Edit that file, then re-run the command.
Example config.yaml blocks (click to expand)
pr_collection:
enabled: true
languages: [c, cpp, go, java, javascript, typescript, python, rust]
repo_num: 100 # repos per language
max_prs_per_repo: 50
output_dir: artifacts/collected_prs
token_limit: 32 # first N combined file + env GitHub tokens (0 = use all)
filters:
min_stars: 30
min_merged_prs: 5
min_language_percentage: 0.4
max_days_since_push: 1095
min_issue_body_length: 10
min_files_changed: 1
max_files_changed: 25
max_lines_changed: 1500
languages:
py: { enabled: true, params: { timeout: 3200, cc_timeout: 2400, n_concurrent: 20 } }
js: { enabled: true, params: { timeout: 3200, cc_timeout: 2400, n_concurrent: 16 } }
ts: { enabled: true, params: { timeout: 3600, cc_timeout: 3000, n_concurrent: 16 } }
# …one row per languagetimeout: whole PR case timeout (seconds)cc_timeout: Claude Code task completion timeout (seconds)n_concurrent: parallel PR workers per language
GitHub Tokens
Place tokens in repos/swegen/gh_token.txt (one per line) or set
GITHUB_TOKEN / GITHUB_TOKENS environment variables. The collector combines
all sources.
Command Reference
/curator:setup
One-time bootstrap for a fresh clone. It initializes the repos/swegen/
submodule (git submodule update --init), creates and activates the block
Python venv, installs swegen in editable mode (pip install -e repos/swegen/), and ensures the environment the other commands depend on is
present: the cross-provider LLM variables (OPENAI_API_KEY /
OPENAI_API_BASE_URL / OPENAI_MODEL, mirrored to the ANTHROPIC_* names),
GITHUB_TOKENS / GITHUB_TOKEN, DOCKER_HOST, and CLAUDE_CONFIG_DIR —
prompting for anything missing. It is idempotent: re-run it freely; it only
fills gaps and never overwrites secrets or existing runtime outputs. Run this
before /curator:check.
/curator:check
Read-only preflight — mandatory before /curator:create-tasks. It answers
"is this block ready to collect PRs, generate tasks, and run Harbor
validation?" in a single consolidated report (it does not stop at the first
failure). It:
- validates the
config.yamlschema andmeta_info.name == "curator" - confirms
repos/swegen/is checked out at the pinned commit - pings the GitHub API once per token (
GET /rate_limit) - sends a real
chat.completions.createthrough the cross-provider env, so a misconfiguredcc_provider_mode(native vs openai_proxy) is caught here rather than silently failing on the first task - verifies the Docker daemon is reachable (
docker info) - optionally runs a Harbor NOP/Oracle smoke against a known verified task
(expects
reward=0/reward=1)
It changes nothing in the workspace, but the LLM probe consumes a small number of tokens.
/curator:collect-prs
Stage 1 of the pipeline. Runs background PR collection for the configured
languages, wrapping repos/swegen/tools/collect_prs_wo_image.py driven by
config.yaml -> runtime_info.input.pr_collection (languages, repo scale, and
the filter thresholds). It combines tokens from repos/swegen/gh_token.txt
with GITHUB_TOKENS / GITHUB_TOKEN; tokens never come from config.yaml.
Outputs:
artifacts/collected_prs/{language}_pr_ids.txtThis is a separate, long-running background job. Wait for it to finish before
running /curator:create-tasks.
/curator:create-tasks
Three modes:
| Mode | When to use | What it does |
|---|---|---|
| smoke | First run, quick verify | Generates 1 task from sample PR file (takes ~5 minutes) |
| single-language | Testing one language | Runs scripts/create_<lang>.sh for specified language |
| full | Production | Runs all 8 languages in background, outputs to artifacts/swe_tasks/ |
The plugin runs preflight checks automatically:
- Config validation
- GitHub token availability
- LLM endpoint connectivity
- Docker daemon access
/curator:dashboard
Regenerates the dataset analytics databoard and, optionally, deploys it to
Cloudflare Pages. It scores every task's difficulty and assigns the four
semantic tags [language, area, topic, bug_class] with the canonical tagger
(repos/swegen/tools/tag_task_metadata.py), then renders a single-page
comparison across the curated datasets, published at swe-databoard.pages.dev.
This command analyzes finished datasets, not live run progress. To follow a
running generation, read the batch logs, verifiable_tasks.txt, and
artifacts/index.yaml. See Dashboard for the scoring and
tagging methodology.
/curator:run
A compatibility adapter that forwards its full request and arguments to
/curator:create-tasks unchanged, so every block offers the same
/<block>:run entry point. It adds no preflight or reporting of its own —
create-tasks owns all of that. Prefer the specific commands for day-to-day
Curator work; this adapter mainly serves generic block runners.
Note: /root:run curator does not go through this adapter. Root targeting
runs Curator's all-language scripts/start.sh directly.
Advanced: Manual Commands
For debugging or custom workflows, you can run the underlying commands directly. These are what the plugin wraps:
Manual PR collection (click to expand)
# Full collection using config
bash scripts/collect_all_bg.sh
# Single language override
LANGUAGES=python bash scripts/collect_all_bg.sh
# Direct collector call for smoke test
source scripts/load_runtime_env.sh && load_runtime_env
python repos/swegen/tools/collect_prs_wo_image.py \
--languages python \
--repo_num 2 \
--max_prs_per_repo 10 \
--output_dir ./artifacts/collected_prs \
--disable_progress_barOutput: artifacts/collected_prs/python_pr_ids.txt
Manual task generation (click to expand)
# Single task for validation
swegen create \
--input-ids-file artifacts/collected_prs/python_pr_ids.txt \
--max-pr 1 \
--n-concurrent 1 \
--output artifacts/swe_tasks/py-cc \
--state-dir artifacts/state/swegen-py \
--timeout 2400 \
--cc-timeout 1800 \
--no-require-issue \
--min-source-files 1 \
--max-source-files 10
# Production: single language
bash scripts/create_py.sh
# Production: all languages
bash scripts/create_all_bg.shThe --timeout/--cc-timeout above are small illustrative values for a quick
single-task check. For real runs the production scripts read per-language values
from config.yaml (languages.<lang>.params, e.g. py 3200/2400) — you do
not set them by hand. --timeout is the overall per-case budget and must stay
=
--cc-timeout(the inner Claude-Code session).
Production Checklist
Before a large-scale run, confirm the essentials that /curator:check does not
decide for you:
- ✅
config.yamlLLM endpoint,cc_provider_mode, and credentials are correct - ✅ GitHub tokens are fresh (
repos/swegen/gh_token.txtorGITHUB_TOKENS) - ✅ A smoke pass produced a task (
/curator:create-tasks→ "smoke") - ✅ Docker is running and there is enough disk for state and artifacts
Then collect (/curator:collect-prs), wait for it to finish, and generate
(/curator:create-tasks → "full"). Track progress via the batch logs,
verifiable_tasks.txt, and artifacts/index.yaml; open the
Dashboard afterward to review difficulty and tag composition.