--- name: datagen-launch description: >- Launch a datagen (trace-generation) job on an HPC cluster (Jupiter/Leonardo/Perlmutter) via the OpenThoughts-Agent `hpc.launch --job_type datagen` entrypoint — the cluster-AGNOSTIC general flow: extract tasks from a parquet, then submit a managed vLLM-serve + Harbor/Daytona trace run that uploads the trajectories to an HF repo. Use when asked to start a datagen / trace-generation job, generate agent traces from a task dataset, or advance / start a row in the MiniMax-M2.7 datagen tracker. For the Iris TPU path use **datagen-launch-iris** instead; per-cluster particulars (ssh, paths, conda env, which vllm-serve config matches the cluster's GPUs, JSC pre-download) live in `.agents/ops//`. --- # datagen-launch A datagen job stands up a managed vLLM endpoint serving a teacher model, runs Harbor agent rollouts against a set of tasks in Daytona sandboxes, and uploads the resulting trajectories to an HF dataset repo. There is **no model checkpoint** — the artifact is the trace dataset. This skill is **cluster-AGNOSTIC** — the shared flow, flags, and gotchas. Defer everything cluster-specific (ssh / login node, repo path, conda env, JSC login-node pre-download, which `datagen_config` (vllm-serve) YAML matches the cluster's GPUs and the model) to `.agents/ops//`. Do NOT inline cluster-specific values here. Sibling skills: **datagen-launch-iris** (Iris TPU path), **datagen-job-cleanup** (post-run upload + disk cleanup), **datagen-reduce-dataset-snapshots** (shrink a task dataset's Daytona snapshot count under cap). ## Required info (ask if missing) 1. **Dataset / tasks** — an HF parquet repo id to extract tasks from (e.g. `DCAgent/r2egym_sandboxes`), or an already-extracted local tasks dir. 2. **Teacher model + operating point** — determines the `datagen_config` (vllm-serve) YAML and the `trace_harbor_config` context-length YAML. Pick the cluster-matched config from `.agents/ops//`; don't change configs unless asked. 3. **Target HF repo** — `--trace_target_repo` (default `penfever/` or `DCAgent2/` org per the series). ## The 2-step flow **Step 1 — extract tasks from the parquet** (writes one task dir per row): ```bash python -m scripts.datagen.extract_tasks_from_parquet \ --parquet \ --output_dir \ --on_exist overwrite ``` **Step 2 — submit the datagen job:** > **🚧 SUBMIT FROM THE REPO DIR WITH `DCFT` SET — the sbatch WORKDIR guard hard-fails otherwise.** > Before launching/relaunching, `cd && export DCFT=$PWD` (on Jupiter: `/e/scratch/jureap59/feuer1/OpenThoughts-Agent`). The generated `universal_tracegen.sbatch` / `universal_taskgen.sbatch` resolve `WORKDIR` from `DCFT_PRIVATE → DCFT → $PWD`; submitted from `$HOME`/a scratch subdir with `DCFT` unset, the guard detects the wrong dir (missing `hpc/shell_utils/triton_cache.sh` marker) and **`exit 1`s immediately** with `FATAL: WORKDIR=... is not the OpenThoughts-Agent repo root`. If you see that FATAL, `cd` to the repo, `export DCFT=$PWD`, resubmit. ```bash python -m hpc.launch \ --job_type datagen \ --datagen_config .yaml \ --trace_harbor_config hpc/harbor_yaml/datagen/.yaml \ --tasks_input_path \ --trace_target_repo \ --daytona_api_key "$DAYTONA_DATA_API_KEY" \ --num_nodes 1 \ --trace-n-concurrent N ``` ## CRITICAL gotchas (get these wrong → silent total failure) - **`--daytona_api_key "$DAYTONA_DATA_API_KEY"` is MANDATORY.** Must be the datagen-org Daytona key, NOT the default RL-org key. The RL-org key **rejects declarative sandbox builds**, so every trial instant-fails (job looks "running" but produces zero real trajectories). Source the key from your secrets env (see CLAUDE.md "Datagen Daytona Key (CRITICAL)" / `.agents/ops//`). Do NOT echo the value. (Exact env-var name `DAYTONA_DATA_API_KEY` — verify against your local secrets env / ops doc.) - **harbor_config needs `auto_snapshot: true`.** Attaches the prebuilt Daytona snapshot instead of doing a slow per-trial declarative build. Use a `hpc/harbor_yaml/datagen/.yaml` that sets it. - **Do NOT export `DAYTONA_TARGET`.** Leaving it set misroutes the run. - **Per-job effective concurrency caps at ~100–128** (per-job ceiling, not aggregate contention). `--trace-n-concurrent` (and the config's `seqs` / `max_num_seqs`) above ~128 is wasted. Parallel datagen *jobs* are fine; over-fanning a single job is not. - **Daytona snapshot org cap is a HARD, server-side limit** (e.g. 40 or 60 per org depending on org). Never raise the cap or convert its `ValueError`/`SnapshotCapExceeded` into a warning. If a dataset overflows: clean **stale/MISSING** snapshots, shrink the dataset's snapshot footprint with **datagen-reduce-dataset-snapshots**, or reuse a dataset whose snapshots already exist (0 new). If still blocked, ask the user. Snapshots are keyed by sandbox **environment**, not dataset — they're shared, so do NOT reclaim them per-run. **Clean stale at the cap (autonomous):** `python scripts/daytona/daytona_snapshot_manager.py --api-key-env DAYTONA_DATA_API_KEY --delete-stale --yes` — at the stale threshold defined in `.agents/projects/daytona/daytona.md` § "How to clean stale snapshots" (GT — don't restate the value) (audit-only without `--delete-stale`; deletes only idle/unprotected `harbor__*` envs). Full procedure + caveats → `.agents/projects/daytona/daytona.md`. - **FD-exhaustion / libuv SIGABRT at ~1h on boundary-hugging datasets:** `model_info.max_input_tokens` does nothing when `enable_summarize=false` (our RL/trace default) — nothing truncates the growing multi-turn prompt → `VLLMValidationError: 32769 input tokens` overflow. Lowering the token budget is INERT. Real levers: (a) drop `n_concurrent_trials` (e.g. 900→500), then (b) disable litellm's async logging worker on the hosted_vllm path (its `clear_queue exceeded max_time` backlog leaks FDs ∝ overflow_rate × concurrency). - Reward-realness caveat: LLM-judge datasets can score all-0.0 if the judge API key is invalid/unplumbed — inspect the reward distribution at consolidation, not just avg turns. ## Chunking (long datasets) For large task sets, split into chunked sub-jobs so each gets its own vLLM endpoint, walltime, and HF `_chunk{N}` repo (avoids one giant job timing out and stranding traces, keeps `trace_jobs/` inode use bounded per job): - `--chunk_size ` → ~`ceil(num_tasks / S)` chunks, each a separate job + serve + `_chunk{N}` HF repo. - `--chunk_array_max ` → at most M chunks run concurrently (rolling `afterany` dependency chain). - Alternatively, a manual `--dependency "afterany:"` chain serializes individually-launched jobs. - Route `trace_jobs/` off inode-tight scratch via `--experiments_dir ` where required by the cluster (see `.agents/ops//`); without it datagen can write thousands of per-trial dirs per chunk onto a quota-tight filesystem. - Respect the cluster's QOS max walltime via `--time_limit` (see ops doc). - The snapshot org cap still applies across chunks — see the HARD-cap gotcha above. - **Curator sharded datagen: pass `--save-every 700`** (5th positional arg), not 200 → ~4 checkpoints/12h-job instead of ~14, each checkpoint has a serial GPU-wasting pause. ## Verify it's running After submit, confirm the job placed and is producing **real** trajectories (a served `/v1/models` healthcheck alone does NOT prove generation works): - Check the job state (`squeue`/`sacct` per `.agents/ops//`). - Tail the run's `_vllm.log` for a successful serve, then confirm trial dirs under `/trace_jobs//` are accumulating multi-step trajectories (avg turns > 1), not 1-turn exception stubs. The real-vs-failed check is detailed in **datagen-job-cleanup** step 2. ## MiniMax-M2.7 tracker workflow (the "datagen #N" queue) The MiniMax-M2.7 131k series is driven by a **canonical tracker** that is the source of truth — follow its **Launch Command** and **Process Notes EXACTLY**, not the ad-hoc command grab-bag. (Tracker path lives in the `reference-minimax-datagen-tracker` memory note; verify the current path there.) Key conventions: - One dataset in flight at a time (datasets strictly sequential). - Per-dataset autonomous N→N+1 loop: extract → launch chunks → wait for ALL chunks to complete → consolidate the `_chunk{N}` repos into the final `-…-traces` repo (`scripts/datagen/join_hf_repos.py`) → verify (row count == sum of chunks, non-empty, realness: avg turns > 1, sane exception rate) → **DELETE the `_chunk{N}` repos** (only after the consolidated repo is verified) → clean local exp/task dirs (detached `rm`) → launch dataset N+1. Advance WITHOUT blocking (on chunk-completion / on the cron). - **Oversized pre-check:** before extracting a row, query the HF dataset-viewer `/size` API (`https://datasets-server.huggingface.co/size?dataset=`); skip rows >~10k rows (each oversized extraction burns ~90–100k GPFS inodes). ## On completion - Upload + verify traces and free disk with **datagen-job-cleanup** (handles the TIMEOUT-stranded-traces case, the one-level `trace_jobs/` nesting, the real-vs-failed sanity check, and safe disk cleanup). - If a dataset's snapshot footprint blocks future launches, shrink it with **datagen-reduce-dataset-snapshots**. - **Traces → tool-calling SFT:** to turn the generated traces into SFT-ready rows (`role: tool` + structured `tool_calls`, not the lossy default `conversations` shape), use `harbor traces export --sft-format` → `.agents/projects/harbor/ops.md` § "SFT-ready traces with tool calling — `harbor traces export --sft-format`". For byte-exact-from-served-tokens SFT instead, the literal path is § "Literal-token trace datasets" in the same doc. ## Guardrails - NEVER launch with the wrong (RL-org) Daytona key — it silently zeroes the run. - NEVER raise a Daytona snapshot org cap or downgrade its error to a warning. - NEVER change experimental configs / hparams mid-series; flag and propose a separate run instead. - Keep cluster-specific values (paths, env, configs, ssh) in `.agents/ops//`, not inlined here.