LegoFlow

Blockscurator

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.

CommandStagePurpose
/curator:setupBootstrap (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:checkPreflightValidates config, the pinned repo, GitHub tokens, a live LLM completion, and the Docker daemon. Read-only, and required before generating tasks.
/curator:collect-prsStage 1 — collectCollects qualifying GitHub PRs into artifacts/collected_prs/{lang}_pr_ids.txt. Runs as a long-lived background job.
/curator:create-tasksStage 2 — generateTurns collected PR IDs into tasks and verifies each with NOP/Oracle checks, recording verified IDs in verifiable_tasks.txt. Does not collect PRs.
/curator:dashboardDataset analyticsRegenerates and publishes the dataset databoard — per-task difficulty scores and semantic tags across the curated datasets. It analyzes datasets, not run progress.
/curator:runAdapterForwards 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.

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 IDs

Track 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:setup and /curator:check read runtime_info.input.llm_api to build the environment and probe the endpoint.
  • /curator:collect-prs reads runtime_info.input.pr_collection for scope and filters.
  • /curator:create-tasks reads runtime_info.input.languages for 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: 4010

Mode 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:

FieldFill with
api_keyLeave as human in git; supply the real key via the environment or an ignored .env.
api_base_urlThe OpenAI-compatible endpoint, including the /v1 suffix.
pr_modelModel for PR evaluation + instruction generation. Prefer a non-reasoning, JSON-reliable model.
task_modelModel for the Claude Code path. In openai_proxy mode any claude-* alias works — the proxy maps it to pr_model.
cc_provider_modenative when the provider already speaks the Anthropic Messages API, openai_proxy otherwise.
anthropic_base_urlnative: the provider's Anthropic root, without /v1. openai_proxy: the local proxy URL.
cc_proxy_portopenai_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 SKIP

It 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:

SymptomCauseWhat the probe prints
HTTP 404 no route /chat/completionsapi_base_url missing the /v1 suffixapi_base_url: https://host → https://host/v1
HTTP 200 but no text in the replyprovider/ prefix on a model the raw upstream doesn't knowmodel: openai/Qwen3.6-35B-A3B → Qwen3.6-35B-A3B
no route /v1/chat/completions/chat/completions/chat/completions pasted into api_base_urlthe 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 language
  • timeout: 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.yaml schema and meta_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.create through the cross-provider env, so a misconfigured cc_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.txt

This is a separate, long-running background job. Wait for it to finish before running /curator:create-tasks.

/curator:create-tasks

Three modes:

ModeWhen to useWhat it does
smokeFirst run, quick verifyGenerates 1 task from sample PR file (takes ~5 minutes)
single-languageTesting one languageRuns scripts/create_<lang>.sh for specified language
fullProductionRuns 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_bar

Output: 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.sh

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

  1. config.yaml LLM endpoint, cc_provider_mode, and credentials are correct
  2. ✅ GitHub tokens are fresh (repos/swegen/gh_token.txt or GITHUB_TOKENS)
  3. ✅ A smoke pass produced a task (/curator:create-tasks → "smoke")
  4. ✅ 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.

On this page