{"id":56116,"plugin_id":"plugins_6abf4e8b66808191af07692fd78a9110","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-10-26T18:06:39.478Z","digest":"f48fdbd43247ff4e69921ddeeff878b7e71e8ccdbf2ae38d77fdab8a359b603c","against":null,"payload":{"description":"Create valohai.yaml step definitions for ML projects. Use this skill when a user wants to define a new step in valohai.yaml, configure Docker images, set up commands, define parameters/inputs for a step, or create a complete valohai.yaml from scratch for their ML project. Triggers on mentions of valohai.yaml, step definition, YAML configuration, Docker image selection, or creating Valohai steps.","included_files":[],"name":"valohai-yaml-step","skill_md_contents":"---\nname: valohai-yaml-step\ndescription: Create valohai.yaml step definitions for ML projects. Use this skill when a user wants to define a new step in valohai.yaml, configure Docker images, set up commands, define parameters/inputs for a step, or create a complete valohai.yaml from scratch for their ML project. Triggers on mentions of valohai.yaml, step definition, YAML configuration, Docker image selection, or creating Valohai steps.\nlicense: Apache-2.0\nmetadata:\n  author: valohai\n  version: \"1.0\"\n  compatibility: Requires Python. Designed for Claude Code or similar AI coding agents.\n---\n\n# Create Valohai YAML Steps\n\nCreate and configure `valohai.yaml` step definitions that describe how ML code should run on Valohai. A step defines the Docker image, commands, parameters, and inputs for an execution.\n\n## Philosophy\n\nThe `valohai.yaml` file is the single source of truth for your ML workflows. It lives in the repository root and is version-controlled alongside your code. Your code stays framework-agnostic and portable - all platform configuration is in this file.\n\n## Step-by-Step Instructions\n\n### Design Principle: Steps Should Run Independently\n\n**IMPORTANT**: Every step should be runnable on its own, without relying on a pipeline to provide its inputs. Whenever possible, set a `default` value for inputs so users can test and debug individual steps via `vh execution run step-name --adhoc` without needing to run the full pipeline first. Pipeline edges override these defaults at runtime.\n\nLook for default values in the existing code, README, documentation, config files, or data paths already referenced in the project. Only add a default if you can find a real, meaningful value. Do not invent placeholder URLs or dummy paths.\n\n### 1. Analyze the Project\n\nBefore creating the YAML, understand the project:\n\n- **What scripts exist?** (train.py, preprocess.py, evaluate.py, infer.py)\n- **What framework?** (PyTorch, TensorFlow, scikit-learn, XGBoost, HuggingFace, etc.)\n- **What Python version?** (Check pyproject.toml, setup.py, .python-version)\n- **What dependencies?** (requirements.txt, pyproject.toml, conda environment)\n- **Does it need GPU?** (CUDA imports, torch.cuda, tf.config)\n- **What are the inputs?** (datasets, pretrained models, config files)\n- **What are the outputs?** (trained models, metrics, plots, predictions)\n- **What are the configurable values?** (hyperparameters, paths, flags)\n\n### 2. Choose the Docker Image\n\nSelect an appropriate base image:\n\n| Framework | CPU Image | GPU Image |\n|-----------|-----------|-----------|\n| Python only | `python:3.11` | N/A |\n| TensorFlow | `tensorflow/tensorflow:2.15.0` | `tensorflow/tensorflow:2.15.0-gpu` |\n| PyTorch | `pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime` | Same (includes CUDA) |\n| HuggingFace | `huggingface/transformers-pytorch-gpu:latest` | Same |\n| scikit-learn | `python:3.11` | N/A |\n| R | `r-base:4.3.0` | N/A |\n| Custom | Your own Docker image from a registry | Your own Docker image |\n\n**Guidelines:**\n- The Docker image is the execution environment, not your code container\n- Don't include your code or data in the image\n- Pin version tags for reproducibility (avoid `latest`)\n- If using GPU, choose an image with the right CUDA version\n\n### 3. Install Dependencies in Commands\n\n**IMPORTANT**: The Docker image is a clean base environment. Your project's dependencies (from `requirements.txt`, `pyproject.toml`, `environment.yml`, etc.) are NOT pre-installed. You must install them as part of the step commands BEFORE running your script, otherwise you'll get `ModuleNotFoundError`.\n\nLook for dependency files in the project:\n\n| File Found | Install Command |\n|------------|----------------|\n| `requirements.txt` | `pip install -r requirements.txt` |\n| `pyproject.toml` (with pip) | `pip install .` or `pip install -e .` |\n| `pyproject.toml` (with uv) | `pip install uv && uv pip install --system .` |\n| `setup.py` | `pip install .` |\n| `environment.yml` (conda) | `conda env update -n base -f environment.yml` |\n| `Pipfile` | `pip install pipenv && pipenv install` |\n| Inline dependencies | `pip install torch numpy pandas ...` |\n\nAlways place install commands before any Python script execution in the `command` list.\n\n### 4. Keep Commands Simple — Use Helper Scripts\n\n**IMPORTANT**: Step commands in `valohai.yaml` must be simple and human-readable. Never put bash loops, inline Python one-liners, complex piped commands, or multi-line logic directly in the `command` list.\n\nIf a step needs non-trivial setup or orchestration logic (e.g., downloading extra resources, converting formats, running multiple sub-steps), **create a helper Python script** in a `valohai/` folder and call it from the command:\n\n```yaml\n# WRONG - complex logic crammed into commands\ncommand:\n  - pip install -r requirements.txt\n  - for f in /valohai/inputs/data/*.tar; do tar xf \"$f\" -C /tmp/data; done\n  - python -c \"import os; [os.rename(f, f.lower()) for f in os.listdir('/tmp/data')]\"\n  - python train.py {parameters}\n\n# CORRECT - clean commands, logic lives in a helper script\ncommand:\n  - pip install -r requirements.txt\n  - python valohai/prepare_data.py\n  - python train.py {parameters}\n```\n\nThe helper script `valohai/prepare_data.py` contains the extraction and renaming logic. This keeps the YAML readable, the logic testable, and avoids shell quoting nightmares.\n\n**Rule of thumb**: If a command line is longer than ~80 characters or uses shell control flow (loops, conditionals, subshells), move it to a helper script.\n\n### 5. Create the Step Definition\n\n#### Minimal Step\n\n```yaml\n- step:\n    name: train-model\n    image: python:3.11\n    command:\n      - pip install -r requirements.txt\n      - python train.py\n```\n\n#### Complete Step with All Options\n\n```yaml\n- step:\n    name: train-model\n    description: Train a classification model on the prepared dataset\n    category: Training\n    image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime\n    environment: aws-eu-west-1-p3-2xlarge\n    command:\n      - pip install -r requirements.txt\n      - python train.py {parameters}\n    parameters:\n      - name: epochs\n        type: integer\n        default: 50\n        description: Number of training epochs\n      - name: learning_rate\n        type: float\n        default: 0.001\n        min: 0.00001\n        max: 0.1\n        description: Initial learning rate\n      - name: batch_size\n        type: integer\n        default: 32\n        choices: [8, 16, 32, 64, 128, 256]\n      - name: model_type\n        type: string\n        default: resnet50\n        choices: [resnet18, resnet50, resnet101, efficientnet_b0]\n      - name: use_augmentation\n        type: flag\n        default: true\n        pass-true-as: --use_augmentation=True\n        pass-false-as: --use_augmentation=False\n    inputs:\n      - name: training-data\n        default: s3://my-bucket/datasets/train/\n      - name: validation-data\n        default: s3://my-bucket/datasets/val/\n      - name: pretrained-model\n        optional: true\n    time-limit: 2h\n    no-output-timeout: 30m\n```\n\n### 5. Step Properties Reference\n\n#### Required Properties\n\n| Property | Description |\n|----------|-------------|\n| `name` | Unique step identifier. Used in CLI: `vh exec run <name>` |\n| `image` | Docker image for the execution environment |\n| `command` | Command(s) to run. String or list of strings |\n\n#### Optional Properties\n\n| Property | Description |\n|----------|-------------|\n| `description` | Help text shown in UI |\n| `category` | Group steps in UI (e.g., \"Training\", \"Preprocessing\") |\n| `environment` | Default compute environment (ID or name substring) |\n| `parameters` | Configurable values (see parameter migration skill) |\n| `inputs` | Data files to download before execution |\n| `time-limit` | Max execution time (`\"2h\"`, `\"1h 30m\"`, or seconds) |\n| `no-output-timeout` | Kill if no stdout for this duration |\n| `environment-variables` | Environment variables with defaults |\n| `environment-variable-groups` | Groups of env vars to include |\n| `stop-condition` | Auto-stop condition based on metrics (e.g., `accuracy >= 0.99`) |\n| `upload-store` | Override default output data store |\n| `resources` | Kubernetes resource requests/limits |\n\n### 6. Multi-Step YAML\n\nMost ML projects have multiple steps:\n\n```yaml\n# Preprocessing step\n- step:\n    name: preprocess-data\n    image: python:3.11\n    command:\n      - pip install pandas numpy scikit-learn\n      - python preprocess.py {parameters}\n    parameters:\n      - name: test_split\n        type: float\n        default: 0.2\n      - name: random_seed\n        type: integer\n        default: 42\n    inputs:\n      - name: raw-data\n        default: s3://bucket/raw-data.csv\n\n# Training step\n- step:\n    name: train-model\n    image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime\n    command:\n      - pip install -r requirements.txt\n      - python train.py {parameters}\n    parameters:\n      - name: epochs\n        type: integer\n        default: 50\n      - name: learning_rate\n        type: float\n        default: 0.001\n      - name: batch_size\n        type: integer\n        default: 32\n    inputs:\n      - name: dataset\n        default: s3://bucket/processed/\n      - name: pretrained-model\n        optional: true\n\n# Evaluation step\n- step:\n    name: evaluate-model\n    image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime\n    command:\n      - pip install -r requirements.txt\n      - python evaluate.py\n    inputs:\n      - name: model\n        default: datum://latest-trained-model\n      - name: test-data\n        default: s3://bucket/test/\n\n# Batch inference step\n- step:\n    name: batch-inference\n    image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime\n    command:\n      - pip install -r requirements.txt\n      - python inference.py\n    inputs:\n      - name: model\n        default: datum://production-model\n      - name: data\n        default: s3://bucket/inference-data/\n```\n\n### 7. Command Patterns\n\n```yaml\n# Single command\ncommand: python train.py\n\n# Multiple commands (run sequentially)\ncommand:\n  - pip install -r requirements.txt\n  - python preprocess.py\n  - python train.py {parameters}\n\n# PARAMETER SYNTAX - uses COLONS, not brackets or parentheses\n#\n# {parameters}              → all params as --name=value\n# {parameter:name}          → one param as --name=value\n# {parameter-value:name}    → one param as raw value only\n#\n# WRONG: {parameter-value[name]} {parameter[name]} {parameter(name)}\n# RIGHT: {parameter-value:name}  {parameter:name}  (always use colons!)\n\n# {parameters} - expands ALL parameters with --name=value format (preferred)\ncommand:\n  - python train.py {parameters}\n# Expands to: python train.py --epochs=50 --learning_rate=0.001\n\n# {parameter:name} - ONE parameter as --name=value (standalone, never after --)\ncommand:\n  - python train.py --model /valohai/inputs/model/*.onnx {parameter:conf} {parameter:iou}\n# Expands to: python train.py --model /valohai/inputs/model/*.onnx --conf=0.5 --iou=0.7\n\n# {parameter-value:name} - ONE parameter as RAW VALUE ONLY (no --name= prefix)\ncommand:\n  - python train.py --threshold {parameter-value:threshold}\n# Expands to: python train.py --threshold 0.5\n\n# WRONG - never combine --flag={parameter:name}, it double-wraps the argument\n# --conf={parameter:conf} expands to --conf=--conf=0.5 which BREAKS argparse\n\n# Install from different sources\ncommand:\n  - DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libgl1-mesa-glx\n  - pip install -r requirements.txt\n  - python train.py\n\n# Conditional commands\ncommand:\n  - pip install -r requirements.txt\n  - python preprocess.py && python train.py {parameters}\n```\n\n**IMPORTANT: Non-interactive commands and shell isolation.**\n\nEach YAML list item runs in a **separate shell**. This means `export` on one line does NOT carry over to the next. Chain commands that share environment on the same line:\n\n```yaml\n# WRONG - export doesn't carry to the next line (separate shells)\ncommand:\n  - export DEBIAN_FRONTEND=noninteractive\n  - apt-get update && apt-get install -y tzdata   # WILL STILL PROMPT\n\n# CORRECT - chain export on the same line so it applies to both commands\ncommand:\n  - DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata\n  - pip install -r requirements.txt\n  - python train.py {parameters}\n```\n\nAlways use `-y` (or `--yes`) and `DEBIAN_FRONTEND=noninteractive` with `apt-get install`. Executions run unattended - any interactive prompt will hang the execution indefinitely.\n\n### 8. Environment Variables\n\n```yaml\n- step:\n    name: train-model\n    image: python:3.11\n    command:\n      - python train.py\n    environment-variables:\n      - name: WANDB_MODE\n        default: disabled\n        description: Disable W&B logging on Valohai\n      - name: HF_HOME\n        default: /valohai/inputs/hf-cache/\n    environment-variable-groups:\n      - secret-api-keys-group-uuid\n```\n\n### 9. Resource Configuration (Kubernetes)\n\n```yaml\n- step:\n    name: train-large-model\n    image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime\n    command:\n      - python train.py\n    resources:\n      cpu:\n        min: 4\n        max: 8\n      memory:\n        min: 16384  # MB\n        max: 32768\n      devices:\n        nvidia.com/gpu: 1\n```\n\n## Common Patterns by Project Type\n\n### Computer Vision\n\n```yaml\n- step:\n    name: train-detector\n    image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime\n    command:\n      - pip install ultralytics\n      - python train.py {parameters}\n    parameters:\n      - name: epochs\n        type: integer\n        default: 100\n      - name: img_size\n        type: integer\n        default: 640\n      - name: batch_size\n        type: integer\n        default: 16\n    inputs:\n      - name: dataset\n        keep-directories: suffix\n```\n\n### NLP/LLM Fine-tuning\n\n```yaml\n- step:\n    name: finetune-llm\n    image: huggingface/transformers-pytorch-gpu:latest\n    command:\n      - pip install peft bitsandbytes\n      - python finetune.py {parameters}\n    parameters:\n      - name: model_name\n        type: string\n        default: meta-llama/Llama-2-7b-hf\n      - name: lora_r\n        type: integer\n        default: 16\n      - name: learning_rate\n        type: float\n        default: 0.0002\n      - name: num_train_epochs\n        type: integer\n        default: 3\n    inputs:\n      - name: training-data\n    environment-variables:\n      - name: HF_TOKEN\n        optional: false\n```\n\n### Data Pipeline\n\n```yaml\n- step:\n    name: etl-pipeline\n    image: python:3.11\n    command:\n      - pip install pandas pyarrow\n      - python etl.py {parameters}\n    parameters:\n      - name: date_from\n        type: string\n        default: \"2024-01-01\"\n      - name: date_to\n        type: string\n        default: \"2024-12-31\"\n    inputs:\n      - name: raw-data\n        default: s3://data-lake/raw/\n        keep-directories: suffix\n```\n\n## Validation\n\n**IMPORTANT**: Always run `vh lint` after creating or modifying `valohai.yaml`. This validates the YAML syntax, checks parameter types, verifies input/output definitions, and catches common mistakes before you attempt to run anything.\n\n```shell\nvh lint\n```\n\nIf `vh lint` reports errors, fix them before proceeding. Common issues:\n- Indentation errors (YAML is whitespace-sensitive)\n- Missing required fields (`name`, `image`, `command`)\n- Invalid parameter types (must be `integer`, `float`, `string`, or `flag`)\n- Duplicate step or parameter names\n- Invalid `pass-as` format strings\n\nOnce lint passes, test with a run:\n\n```shell\nvh execution run step-name --adhoc --watch\n```\n\n## Edge Cases\n\n- The `valohai.yaml` file must be in the repository root\n- Step names must be unique within the YAML file\n- Step names can contain letters, numbers, spaces, hyphens, and parentheses\n- If `{parameters}` is not in the command, parameters are appended to the last command\n- Multiple YAML files can be used via imports (see Valohai docs on multi-file YAML)\n- Commands run sequentially; if one fails, subsequent commands don't execute\n- The working directory inside the container is `/valohai/repository/`\n- Each YAML command list item runs in a separate shell - `export` on one line won't carry to the next\n- Always use non-interactive flags (`-y`, `DEBIAN_FRONTEND=noninteractive`) for package installs - executions have no TTY\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}