name: transformerlab-cli description: Transformer Lab CLI for managing ML training tasks, jobs, compute providers, models, and datasets. Use when the user needs to check job status, stream logs, download artifacts, queue training tasks, upload or edit tasks, manage compute providers, list or create models, upload or download datasets, publish job outputs, run autonomous experiment loops (autoresearch), or interact with Transformer Lab programmatically. Triggers include "check job status", "download results", "queue a task", "upload a task", "edit a task", "list providers", "add provider", "configure provider", "stream logs", "what's running", "monitor training", "add a task", "check provider health", "list models", "create model", "upload dataset", "download dataset", "publish model", "publish dataset", "run autoresearch", "optimize X in a loop", "set up autoresearch", "/lab-autoresearch". allowed-tools: Bash(lab *), Bash(curl lab.cloud), Bash(curl localhost:8338), WebFetch(domain:lab.cloud)
Transformer Lab CLI
Use the lab CLI to interact with Transformer Lab programmatically — managing tasks, jobs, compute providers, models, datasets, and server configuration from the terminal.
Official Documentation
The canonical Transformer Lab documentation is published at https://lab.cloud. When in doubt about a feature, schema, or SDK call, fetch the relevant page rather than guessing.
- Documentation index (LLM-friendly): https://lab.cloud/llms.txt — start here to discover all available pages.
task.yamlstructure: https://lab.cloud/for-teams/running-a-task/task-yaml-structure.md — full schema reference for every field intask.yaml(resources, sweeps, parameters, envs, etc.).- Lab SDK (Python): https://lab.cloud/for-teams/lab-sdk.md — how to use the optional
transformerlabSDK inside a task'smain.py(or any Python script) —lab.init(),lab.log(),lab.update_progress(),lab.get_config(),lab.save_artifact(),lab.finish(),lab.error().
Use WebFetch to load these directly when working on related code — the task.yaml and Lab SDK pages are the source of truth and may contain newer fields than this skill captures.
Installation
uv tool install transformerlab-cli
# or
pip install transformerlab-cli
Verify: lab version
First-Time Setup & Authentication
If the CLI returns Missing required configuration keys: team_id, user_email (or any other auth/config error), do NOT ask the user for an API key. Instead, tell them to run:
lab login
This launches the interactive login flow in their terminal. Wait for them to complete it, then retry the original command. Never prompt the user to paste an API key into the conversation.
The CLI only supports API key authentication. There is no --email or --password flag. To connect:
# Step 1: Set the server (if not using default localhost)
lab config set server https://your-server-url
# Step 2: Login with an API key
lab login --api-key YOUR_API_KEY --server https://your-server-url
# Step 3: Set the current experiment.
# Use `lab experiment list` to see existing experiments.
# If yours doesn't exist yet, run `lab experiment create your_experiment_name` first.
# `lab experiment set-default <id>` is a convenience equivalent to this command.
lab config set current_experiment your_experiment_name
# Step 4: Verify connectivity
lab status
login validates the key and automatically configures server, team_id, user_email, and team_name.
Getting an API key: If lab status fails with auth errors, stop and ask the user to provide an API key. Do NOT attempt to create API keys programmatically by logging in with email/password. API keys are created in the Transformer Lab web UI under team settings. The user must provide the key to you.
Verifying You're Connected to the Right Server
After login, always verify:
lab config # Shows server URL, team, user, experiment
lab status # Shows server version and connectivity
lab whoami # Confirms authenticated user and team
If lab status returns errors but curl -s https://SERVER/ returns 200, the issue is likely auth — re-run lab login.
Critical: --format Flag Placement
The --format flag is a root-level option and MUST come immediately after lab, before any subcommand:
# CORRECT
lab --format json job list
lab --format json task info 42
# WRONG — will be ignored or cause an error
lab job list --format json
Core Workflow
The standard pattern for working with Transformer Lab:
# 1. Check server is up
lab status
# 2. List available tasks
lab task list
# 3. Queue a task on a compute provider
# NOTE: --no-interactive silently picks the DEFAULT provider (Local).
# To pick a specific provider, run interactively (see "Selecting a provider" below).
# ALWAYS pass --description/-m with a markdown note describing the iteration
# (see "Always write a run description" below).
lab task queue TASK_ID --no-interactive -m "Testing lr=3e-5 after loss plateaued at 2.1"
# 4. Monitor the job (three log streams — see "Job logs: three real commands" below)
lab job list --running
lab job task-logs JOB_ID --follow # Lab SDK output (lab.log, progress) — start here
lab job machine-logs JOB_ID --follow # Raw process stdout+stderr from the remote node
lab job request-logs JOB_ID --follow # Provider launch/provisioning logs (e.g. SkyPilot)
# 5. Download results
lab job artifacts JOB_ID
lab job download JOB_ID --file "*.csv" -o ./results
# 6. (Optional) Export the experiment's job runs chart as a PNG, or share it publicly
lab job chart -o runs.png
lab job chart --share # enable public sharing and print the public link
Creating Tasks
Scaffold a new task with lab task init
When the user asks to create, initialize, or start a new task, always use lab task init rather than writing task.yaml / main.py by hand. It scaffolds both files with sensible defaults in the current directory so the user has a working starting point.
mkdir my-task && cd my-task
lab task init # writes task.yaml + main.py with defaults (skips existing files)
lab task init --interactive # prompts for name, CPUs, memory, setup, and run command
- Default mode is non-interactive. It creates
task.yaml(withname,resources: {cpus: 2, memory: 4}, andrun: python main.py) and a startermain.py. Existing files are skipped, not overwritten. --interactivewrites onlytask.yaml(nomain.py) and prompts for each field. In this modetask.yamlwill prompt before overwrite.- After init, edit
main.py, customizetask.yaml, then runlab task add .to create it on the server.
task.yaml Structure
Full schema reference: https://lab.cloud/for-teams/running-a-task/task-yaml-structure.md — fetch this page when adding any field not shown below or when validating an unfamiliar task.yaml.
name: my-task # Required — task identifier
resources: # Optional but recommended
cpus: 2 # CPU count (integer or string)
memory: 4 # RAM in GB (integer or string, NOT "4Gi")
disk_space: 100 # Storage in GB
accelerators: "H100:8" # GPU spec as "TYPE:COUNT"
num_nodes: 2 # For distributed training
compute_provider: my-provider # Target provider name
setup: | # Optional — runs before main task
pip install -r requirements.txt # The `transformerlab` SDK is preinstalled — do NOT add `pip install transformerlab`
run: python main.py # Required — main entry point
envs: # Optional environment variables
HF_TOKEN: "${HF_TOKEN}"
parameters: # Optional — accessible via lab.get_config()
learning_rate: 0.001
batch_size: 32
sweeps: # Optional — hyperparameter sweep
sweep_config:
learning_rate: ["1e-5", "3e-5"]
sweep_metric: "eval/loss"
lower_is_better: true
minutes_requested: 60 # Optional time limit
github_repo_url: https://... # Optional — clone from git
github_repo_dir: path/in/repo
github_repo_branch: main
Important: memory and disk_space are plain numbers in GB (e.g., 4, 16), NOT Kubernetes-style strings like 4Gi. The schema accepts both but the canonical format is plain integers.
Validation
lab task add automatically validates task.yaml against the server schema before creating the task. You can also validate directly with lab task validate [path/to/task.yaml] (defaults to ./task.yaml), including JSON output via lab --format json task validate.
To validate without creating, use lab task add ./my-task --dry-run.
Editing an existing task
Use lab task edit to update an existing task on the server. Three modes:
# Interactive editor flow (opens $EDITOR with current task.yaml)
lab task edit TASK_ID
# Replace ONLY task.yaml (leaves main.py and other attachments untouched)
lab task edit TASK_ID --from-file ./task.yaml --no-interactive
# Replace task.yaml AND attachments (main.py, configs, etc.) from a directory
lab task edit TASK_ID --from-dir ./my-task --no-interactive
Choosing between --from-file and --from-dir:
--from-file <task.yaml>only updates the YAML. Use it when you're tweaking parameters or theruncommand and the rest of the task directory is unchanged.--from-dir <directory>zips and uploads the whole directory (must containtask.yaml), replacing the task's files server-side. Use it when you've also modifiedmain.pyor any sibling files. If the task'srunreferences a script (e.g.python main.py), reuploading just the YAML will leave the old script in place — use--from-dirto keep them in sync.
--from-file and --from-dir are mutually exclusive. lab task edit also supports --no-interactive, --dry-run (with --from-dir), and --timeout.
Uploading extra files to an existing task
Use lab task upload to upload files or directories into an existing task:
# Upload a single file
lab task upload TASK_ID ./tokenizer.json
# Upload a directory
lab task upload TASK_ID ./prompts --no-interactive
lab task upload requires both TASK_ID and PATH.
Lab SDK Quick Reference
Full SDK docs: https://lab.cloud/for-teams/lab-sdk.md — fetch this page when using the SDK from a Python script (inside or outside a task), or when you need a method not covered below.
Tasks use the Lab SDK (transformerlab PyPI package). Import pattern:
from lab import lab
lab.init() # Required — connects to the job
lab.log("message") # Write to job output log
lab.update_progress(50) # Set progress 0-100
config = lab.get_config() # Read parameters from task.yaml
lab.save_artifact("metrics.json") # Save a generic artifact
lab.save_artifact("eval_results.csv", name="eval_results.csv", type="evals") # Save eval results
lab.finish(message="Done!") # success, no score
lab.finish(message="Done!", score={"accuracy": 0.78}) # success with metric(s)
lab.finish(score={"accuracy": 0.78, "f1": 0.83}) # multiple metrics
lab.error(message="Something went wrong") # Mark job as FAILED
Common mistakes:
lab.finish()has NOstatusparameter — justmessage. For failures, uselab.error().score=takes a dict of named metrics, not a scalar. Usescore={"accuracy": 0.78}, neverscore=0.78. The dict populatesjob_data.score, visible inlab job list(Score column) andlab job info, and is read by sweep / autoresearch flows.- Use
lab.save_artifact(..., type="evals")for file-based eval outputs so they appear in eval results metadata, not as generic artifacts. - Always call
lab.init()before any other SDK call. - Always call
lab.finish()orlab.error()at the end — otherwise the job stays in RUNNING state.
Example: Minimal Hello World Task
task.yaml:
name: hello-world
run: python main.py # No `setup:` needed — the `transformerlab` SDK is preinstalled in the task environment
resources:
cpus: 2
memory: 4
main.py:
import time
from lab import lab
lab.init()
lab.log("Hello from Transformer Lab!")
lab.update_progress(25)
time.sleep(3)
lab.log("Working...")
lab.update_progress(75)
time.sleep(2)
lab.log("Done!")
lab.update_progress(100)
lab.finish(message="Hello world complete!")
Add it:
lab task add ./hello-world-task --no-interactive
Managing Experiments
Use lab experiment commands to list, create, delete, tag, and set the default experiment. Experiments are the container for tasks and jobs — lab task / lab job / lab notes commands operate against the current experiment (the one stored in ~/.lab/config.json as current_experiment) unless you override it per-command with --experiment/-e (see "Scoping a single command to an experiment" below).
# List all experiments. The current default is marked with `*`.
lab experiment list
lab --format json experiment list
# Create a new experiment
lab experiment create my-experiment
# Create and immediately set as the default
lab experiment create my-experiment --set-default
# Delete an experiment (`--no-interactive` to skip confirmation)
lab experiment delete my-experiment --no-interactive
# Switch which experiment is the default. This writes to ~/.lab/config.json.
lab experiment set-default my-experiment
Scoping a single command to an experiment: --experiment/-e
lab task, lab job, and lab notes subcommands all accept a per-command --experiment/-e <id_or_name> override. When present it takes precedence over the global current_experiment; when absent the command falls back to the global default. This is the only way to target an experiment without mutating shared state:
lab task queue TASK_ID -e exp-a --no-interactive -m "..." # queues into exp-a
lab --format json job list -e exp-a --score-metric eval/loss
lab notes append "..." -e exp-a
Why this matters — concurrent work across experiments. lab experiment set-default and lab config set current_experiment both write the single, shared current_experiment key in ~/.lab/config.json. If two workflows are operating on two different experiments at the same time (e.g. several autoresearch sessions, or a fan-out across model families), whoever calls set-default last wins, and any command that didn't pass -e silently lands in the wrong experiment. To run experiments in parallel safely:
- Pass
-e <exp>on everytask/job/notescommand for that workstream. Treat it as mandatory, not optional — a single omitted-eleaks into the global default. - Never call
lab experiment set-default(orlab config set current_experiment) mid-flight while other experiments are in flight — it yanks the default out from under any command that forgot its-e. - There is no environment variable (e.g.
LAB_EXPERIMENT) and no per-session config file to scope this once for a whole session — the override is strictly per-command. Plan for passing-eon each call.
For a single, sequential workflow it's fine to set the default once with set-default and omit -e. The -e discipline only becomes load-bearing when more than one experiment is active concurrently.
Tagging experiments
Experiments support free-form tags for organizing and filtering. lab experiment create does NOT accept tags — there is no --tag flag on create. Tags are managed after creation with the dedicated lab experiment tag subcommands, and you filter by them with lab experiment list --tag.
# Add one or more tags to an experiment (by name or id). Both args required.
lab experiment tag add my-experiment fine-tuning llama
# Remove one or more tags from an experiment
lab experiment tag remove my-experiment llama
# List every distinct tag across experiments you can read
lab experiment tags
# Filter experiments by tag. Repeat --tag to AND multiple tags (client-side filter).
lab experiment list --tag fine-tuning
lab experiment list --tag fine-tuning --tag llama # must have BOTH tags
lab --format json experiment list --tag llama # JSON output includes a `tags` field
To create-and-tag in one flow, run create then tag add:
lab experiment create my-experiment --set-default
lab experiment tag add my-experiment fine-tuning llama
Tags are how you regroup experiments that were run in parallel. When you fan out across several experiments at once (each scoped via -e, see above), give them all one shared campaign tag at creation time so you can find and rank them together afterward:
# Tag every experiment in a campaign with the same label...
lab experiment tag add exp-a my-campaign
lab experiment tag add exp-b my-campaign
# ...then list the whole fleet in one shot.
lab --format json experiment list --tag my-campaign
lab experiment set-default vs lab config set current_experiment
Both write the same key (current_experiment) to ~/.lab/config.json. Differences:
lab experiment set-default <id>validates that the experiment exists on the server before writing. Prefer this when scripting — it fails fast on a typo.lab config set current_experiment <id>is a raw config write and does not validate. Useful when bootstrapping a config (e.g. before the server is reachable) or when you've already confirmed the experiment exists.
Finding an experiment ID by name
lab experiment list (and the JSON form) is the only sanctioned way to discover experiment IDs. Do not fall back to curl /experiment/ — even when you only have a name and need the ID, this CLI surface covers it:
# Get just the ID for a given name
lab --format json experiment list | jq -r '.experiments[] | select(.name=="my-experiment") | .id'
lab --format json experiment list returns:
{
"current_experiment": "my-experiment",
"experiments": [
{"id": "my-experiment", "name": "my-experiment", "config": {}}
]
}
Experiment Notes
Each experiment has a single shared markdown document — the "experiment notes" — that persists on the server. It's separate from per-job -m/--description notes: those describe one run, the experiment notes describe the experiment as a whole (hypothesis, running findings, decisions, links to key job IDs).
# Render the notes (markdown, with formatting)
lab notes show
lab notes show --raw # plain markdown, no rendering — better for piping/grepping
# Edit interactively in $EDITOR (defaults to nano if unset)
lab notes edit
# Append a single line, non-interactive — best for agents
lab notes append "2026-05-05: switched provider from local to skypilot; queue depth was >12"
# Enable public sharing for the notes and print the public link (instead of the notes)
lab notes show --share
All commands operate on the current experiment (require_current_experiment — set it first via lab experiment set-default). There is no --format json output for the notes content itself; show returns the raw string and append/edit write it back.
lab notes show --share enables public sharing for the experiment notes and prints the public link instead of the notes. Anyone with the link can view the live notes (no login). It reuses the experiment's existing active link if one exists; only when sharing is off does it mint a new one, asking for confirmation first unless --no-interactive / --format json is set. With --format json it prints {"url": ..., "token": ..., "created_at": ...}. Requires the /experiment/{id}/share/notes endpoints — on older servers the CLI reports "This server does not support public sharing".
When agents should use it
Experiment notes are the right place for context that future conversations (or teammates) will need but that doesn't belong on a single job:
- Running hypothesis log — append after each batch of runs: "Tried lr ∈ {1e-5, 3e-5, 5e-5}; 3e-5 wins on eval/loss. Next: try larger batch."
- Decisions and their rationale — "Dropped baseline-v1 from comparison; tokenizer mismatch made eval scores incomparable."
- Pointers — "Best run so far: job 7f21abcd, score 0.83. Worst: 9c12, diverged at step 500."
- Open questions / next steps — so the next session picks up where this one stopped.
Default flow for agents working in an experiment:
- At the start of a session, run
lab notes show --rawto load prior context. - After each meaningful action (queueing a sweep, finding a winning config, hitting a blocker),
lab notes append "..."with a dated one-liner. Always lead with today's date (e.g.2026-05-05:) so the log stays chronological. - Use
lab notes editonly when the user asks to reorganize or rewrite — appending is safer because it never destroys prior content.
Don't duplicate per-run details (those go in lab task queue -m). Don't use experiment notes as a TODO list for the conversation — that's what plans/tasks are for. Keep entries terse and specific.
Managing Models
Use lab model commands to list, inspect, create, edit, and delete model groups on the server. Models are organized as groups — each group can contain multiple versions (e.g. v1, v2, …).
# List all model groups
lab --format json model list
# Get details for a specific model (by group_id or group_name)
lab --format json model info GROUP_ID
# Register a new model (e.g. a HuggingFace model ID) — creates a new group with version v1
lab --format json model create my-hf-model-id --name "My Fine-tuned Model" --description "SFT on custom data"
# Edit a model group's name or description
lab model edit GROUP_ID --name "New Name" --description "Updated description"
# Delete a model group and all its versions (--yes to skip confirmation)
lab model delete GROUP_ID --yes
Adding a new version to an existing model group
Re-run lab model create with the same --name as an existing group. The server resolves the group by name and appends a new version with an auto-incremented label (v2, v3, …). The latest tag (or whatever you pass via --tag) is moved to the new version automatically.
# Adds version v2 to "My Fine-tuned Model" (assuming v1 already exists).
# The server scans existing v\d+ labels and picks the next one.
lab --format json model create my-hf-model-id-v2 --name "My Fine-tuned Model" --description "Retrained with more data"
# Pin a specific version label instead of auto-incrementing.
# Labels are free-form strings — server only auto-increments the v\d+ pattern.
# Collisions within a group are rejected with an error.
lab model create my-hf-model-id-exp --name "My Fine-tuned Model" --version-label "experimental-2026-05"
The version label is the human-readable identifier in lab model info output. The internal id (UUID) remains unique per version regardless of label.
Uploading model files
# Upload local files/directories to a model on the server.
# Creates the model if it doesn't exist; MODEL_ID is what you'll use
# in subsequent lab model commands.
lab model upload MODEL_ID ./path/to/model-dir
# Multiple paths in one call
lab model upload MODEL_ID ./tokenizer.json ./config.json
# Overwrite server-side files that already exist
lab model upload MODEL_ID ./path/to/model-dir --force
The server runs a finalize step at the end of upload. Finalize fails with cannot finalize: no config.json present. Upload one first. unless the upload includes a config.json at the root with at least an architectures field. Minimal example:
{
"model_type": "fake",
"architectures": ["LlamaForCausalLM"],
"hidden_size": 4096
}
For real models this is the standard HuggingFace config.json. The server records architectures[0] as the model architecture.
Re-running lab model upload against the same MODEL_ID skips files that already exist on the server and exits with code 2 (skipped some, did not fail). Use --force to overwrite.
Downloading model files
# Download a previously-uploaded model to <dest>/<MODEL_ID>/
lab model download MODEL_ID ./local-models
The server streams every file in the model directory; the destination directory is created if missing, and files land under <dest>/<MODEL_ID>/.
Publishing a model from a job
After a training job completes, publish its output model to the registry:
# Interactive — prompts for model name, group, mode, tag
lab job publish model JOB_ID
# Non-interactive (for agents) — all options explicit
lab --format json job publish model JOB_ID MODEL_NAME --group "my-model-group" --mode new --tag latest --description "Trained with lr=3e-5"
Managing Datasets
Use lab dataset commands to list, inspect, upload, download, edit, and delete datasets. Like models, datasets are organized as groups with versions.
# List all dataset groups
lab --format json dataset list
# Get details for a specific dataset (by group_id or group_name)
lab --format json dataset info GROUP_ID
# Edit a dataset group's name or description
lab dataset edit GROUP_ID --name "New Name" --description "Updated description"
# Delete a dataset group and all its versions (--yes to skip confirmation)
lab dataset delete GROUP_ID --yes
Uploading dataset files
# Upload local files/directories to a dataset on the server.
# Creates the dataset if it doesn't exist; DATASET_ID is what you'll use
# in subsequent lab dataset commands.
lab dataset upload DATASET_ID ./train.jsonl ./eval.jsonl
# Upload a directory (preserves relative paths under DATASET_ID/)
lab dataset upload DATASET_ID ./my-dataset-dir
# Overwrite server-side files that already exist
lab dataset upload DATASET_ID ./train.jsonl --force
Re-running lab dataset upload against the same DATASET_ID skips files that already exist on the server and exits with code 2 (skipped some, did not fail). Use --force to overwrite. Adding a brand-new file to an existing dataset is a normal upload (no conflict, exit 0) — just include the new path in the command.
The first successful upload registers the dataset in the asset_versions registry as v1/latest, which is what makes it appear in lab dataset list, lab dataset info, and the Dataset Registry UI. Re-uploads (with or without --force) do not spawn a new version — the registry stays at v1/latest and points at the same on-disk directory. To explicitly create a new version, use lab job publish dataset from a job that produced new outputs.
Downloading dataset files
# Download a previously-uploaded dataset to <dest>/<DATASET_ID>/
lab dataset download DATASET_ID ./local-datasets
Both DATASET_ID and DEST_DIR are required. The server streams every file in the dataset directory; the destination is created if missing, and files land under <dest>/<DATASET_ID>/ (including a server-generated index.json).
Note: lab dataset download no longer pulls from HuggingFace Hub — it only downloads a dataset that already lives on the server. To get a HuggingFace dataset onto the server, either (a) upload it via lab dataset upload after fetching it locally, or (b) reference it from inside a task's code using datasets.load_dataset("user/repo").
Publishing a dataset from a job
After a job produces output datasets, publish them to the registry:
# Interactive — prompts for dataset name, group, mode, tag
lab job publish dataset JOB_ID
# Non-interactive (for agents) — all options explicit
lab --format json job publish dataset JOB_ID DATASET_NAME --group "my-dataset-group" --mode new --tag latest --description "Generated eval set"
Dataset workflow for tasks
When a task needs a specific dataset, ensure it exists on the server before queuing:
# 1. Check if the dataset already exists
lab --format json dataset list
# 2. If not, upload local files (creates the dataset)
lab dataset upload my-dataset ./train.jsonl ./eval.jsonl
# 3. Reference the dataset in task.yaml parameters
# The task code uses lab.get_config()["dataset_id"] to access it
# 4. Queue the task
lab task queue TASK_ID --no-interactive -m "Training on my-dataset"
Managing Providers
Use lab provider commands to list, inspect, add, configure, enable/disable, and health-check compute providers. Providers are the backends that actually run jobs (Local, SkyPilot clusters, Slurm clusters, RunPod).
# List providers (omit --include-disabled by default — only active ones show)
lab --format json provider list
lab --format json provider list --include-disabled
# Show details for one provider
lab --format json provider info PROVIDER_ID
# Health check (verifies the CLI can reach the provider's backend)
lab --format json provider check PROVIDER_ID
# Toggle availability without deleting
lab provider enable PROVIDER_ID
lab provider disable PROVIDER_ID
# Update fields (config is MERGED with existing — pass only the keys you change)
lab provider update PROVIDER_ID --name "new-name"
lab provider update PROVIDER_ID --config '{"api_token": "new-token"}'
lab provider update PROVIDER_ID --credentials-file ./rotated-secrets.json # merge secrets from file, keep them out of argv
lab provider update PROVIDER_ID --enabled # or --disabled
lab provider update PROVIDER_ID --default # mark as the team default (or --no-default to clear)
# Delete (use --no-interactive to skip the confirm prompt)
lab provider delete PROVIDER_ID --no-interactive
When to add a provider
Default to listing first. Before adding anything, run lab provider list to see what already exists. Most servers ship with a local provider already configured. Only add a new provider when:
- The user explicitly asks to add/configure a specific backend (Slurm, SkyPilot, RunPod, dstack, AWS, GCP, Azure).
lab provider listshows none of the existing providers match the resources the user needs (e.g. they want H100s and onlylocalis registered).- A
task queueattempt failed with "No compute providers available".
Do NOT add a provider speculatively. Adding one writes credentials/URLs to the server and may fail health checks until configured correctly. If it's unclear which provider type the user wants, ask.
Adding a provider non-interactively
provider add requires --name, --type, and --config (a JSON string) when run with --no-interactive. The config schema depends on the type:
| Type | Required/optional config fields |
|---|---|
local |
{} — no config needed |
skypilot |
server_url, api_token |
slurm |
mode (ssh or rest), then either ssh_host + ssh_user + ssh_key_path + ssh_port, or rest_url + api_token |
runpod |
api_key, plus optional api_base_url, default_gpu_type, default_region, default_template_id, default_network_volume_id |
dstack |
server_url, api_token, dstack_project |
aws |
region. Provide AWS access keys via --credentials-file PATH pointing at a JSON file with aws_access_key_id + aws_secret_access_key (uploaded to the API host's ~/.aws/credentials) |
gcp |
region, optional zone. Must also pass --credentials-file PATH pointing at your service account JSON key file |
azure |
azure_subscription_id, azure_tenant_id, azure_client_id, azure_client_secret, azure_location |
nebius |
optional infra config parent_id (project id; required unless subnet_id set), subnet_id, default_platform, default_preset, boot_image_family, disk_size_gib. Provide the service-account key pair via --credentials-file PATH pointing at a JSON file with service_account_id + public_key_id + private_key (uploaded via the dedicated /nebius/credentials endpoint) |
--credentials-file for secrets (preferred)
provider add and provider update both accept --credentials-file PATH to keep secrets out of argv (and therefore out of shell history and ps listings on shared hosts). The file shape depends on --type:
aws: JSON object withaws_access_key_id+aws_secret_access_key. Uploaded via the dedicated AWS credentials endpoint; remaining keys (if any) merge into--config.gcp: the raw service account JSON key file itself (the file you'd otherwise pass togcloud auth activate-service-account --key-file=...). Uploaded via the dedicated GCP credentials endpoint.- everything else (
skypilot,runpod,dstack,azure,slurmREST,vastai): a flat JSON object whose fields merge on top of--config. File values win on conflict.
chmod 600 the file, source it from a secret manager / CI vault, and delete it after the lab provider add call. Prefer this over embedding api_token / azure_client_secret / etc. inside --config whenever you're scripting.
# Local (rare — usually pre-installed)
lab provider add --no-interactive --name local --type local --config '{}'
# SkyPilot
lab provider add --no-interactive --name my-skypilot --type skypilot \
--config '{"server_url": "https://sky.example.com", "api_token": "TOKEN"}'
# Slurm over SSH
lab provider add --no-interactive --name my-slurm --type slurm \
--config '{"mode": "ssh", "ssh_host": "cluster.example.com", "ssh_user": "ali", "ssh_key_path": "~/.ssh/id_rsa", "ssh_port": "22"}'
# Slurm over REST
lab provider add --no-interactive --name my-slurm --type slurm \
--config '{"mode": "rest", "rest_url": "https://slurm.example.com/api", "api_token": "TOKEN"}'
# RunPod
lab provider add --no-interactive --name my-runpod --type runpod \
--config '{"api_key": "RUNPOD_KEY", "default_gpu_type": "NVIDIA H100"}'
# dstack
lab provider add --no-interactive --name my-dstack --type dstack \
--config '{"server_url": "http://0.0.0.0:3000", "api_token": "TOKEN", "dstack_project": "main"}'
# AWS — credentials live in a JSON file: {"aws_access_key_id": "...", "aws_secret_access_key": "..."}
lab provider add --no-interactive --name my-aws --type aws \
--config '{"region": "us-east-1"}' \
--credentials-file ./aws-creds.json
# GCP — point --credentials-file at your raw service account JSON key file
lab provider add --no-interactive --name my-gcp --type gcp \
--config '{"region": "us-central1"}' \
--credentials-file ~/.config/gcloud/sa-key.json
# Azure — secrets can live in --credentials-file (preferred) instead of --config
# azure-secrets.json: {"azure_client_secret": "REDACTED"}
lab provider add --no-interactive --name my-azure --type azure \
--config '{"azure_subscription_id": "sub", "azure_tenant_id": "tenant", "azure_client_id": "client", "azure_location": "eastus"}' \
--credentials-file ./azure-secrets.json
# Nebius — service-account key pair lives in --credentials-file (uploaded via /nebius/credentials)
# nebius-creds.json: {"service_account_id": "serviceaccount-...", "public_key_id": "publickey-...", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"}
lab provider add --no-interactive --name my-nebius --type nebius \
--config '{"parent_id": "project-123"}' \
--credentials-file ./nebius-creds.json
# SkyPilot / RunPod / dstack — same pattern: put api_token / api_key in --credentials-file
# skypilot-secrets.json: {"api_token": "TOKEN"}
lab provider add --no-interactive --name my-skypilot-prod --type skypilot \
--config '{"server_url": "https://sky.example.com"}' \
--credentials-file ./skypilot-secrets.json
provider add automatically runs a health check after creation, so a successful add already confirms connectivity. Re-run lab provider check PROVIDER_ID before queuing if you're using an existing provider (credentials may have rotated, the backend may be down) or after a provider update that changed config. If a check fails, fix the config with lab provider update rather than deleting and re-adding.
Don't ask the user for credentials in chat
Provider configs (api_token, api_key, ssh_key_path) contain secrets. If the user has not provided them already, ask them to either run lab provider add interactively themselves (the CLI prompts for each field privately) or to paste the values from a secure source. Don't request the user paste raw keys into a multi-message conversation.
Also note that secrets passed inside --config (e.g. api_token, azure_client_secret) appear in shell history and ps listings. For scripted / CI flows, prefer --credentials-file PATH (see above) — it keeps secrets out of argv entirely. For ad-hoc runs, the interactive flow is fine, or have the user prefix the command with a space under HISTCONTROL=ignorespace.
Agent-Specific Rules
- NEVER call the REST API as a workaround for the CLI. The CLI is the supported interface — don't reach for
curlbecause a CLI command appears missing or broken. Runlab <command> --helpfirst and check this skill. Reading the API source underapi/transformerlab/(routers, services) when debugging a CLI failure is fine and often necessary; the rule is against substitutingcurlforlab, not against understanding what the server does. - Always run
lab <command> --helpbefore assuming a flag exists. Don't guess--provider,--gpu, etc. The CLI's flag surface is small and changes; verify before invoking. - Use
--format jsonwhen you need to parse output, but be prepared to fall back to pretty output parsing if it doesn't work --no-interactiveontask queuesilently uses the DEFAULT provider (Local). There is no--providerflag. To target a specific provider, you must drive the interactive prompts (see "Selecting a provider" below).task addhas no--yesflag — pipeecho "y"to confirm:echo "y" | lab task add ./my-task- Skip confirmation on destructive commands: use
--no-interactiveforprovider delete,job delete, andjob delete-all; use--yes/-yformodel delete/dataset delete(the flag names differ — verify with--help) - Never run
lab job monitorwhen operating as an AI agent. It launches an interactive Textual TUI that blocks automation and can hang unattended runs; uselab job list,lab job info, andlab job task-logs(--followonly when explicitly requested) instead. task interactivesupports full non-interactive mode. Pass--providerand--templateto skip all prompts. Use--no-pollto launch without blocking, then poll readiness withlab job info <job_id>(includestunnel_infofor interactive jobs). Forvscode/ssh, watch fortunnel_info.auth_codeand surface it to the user —is_readywill not flip until they complete the device login. See the "Launching interactive tasks" workflow below.job task-logs --followstreams continuously and blocks until the job finishes — use when the user wants real-time monitoring- Never use the deprecated
lab job logs— see the "Job logs: three real commands" section below. - Before queuing a task, CONFIRM the experiment with the user. Run
lab configto read the current default andlab --format json experiment listto verify it exists, then ask: "I'm about to queue this under experiment<name>(your current default). OK, or pick another?" Show 2–3 alternatives fromexperiment listif the current one looks stale or missing. Skip the confirmation only when the user has already named the experiment in this turn. - After queuing a task, ASK the user if they'd like you to watch the logs. Don't start streaming or polling automatically — jobs can take minutes to hours, and
--followblocks. Report the Job ID and ask: "Want me to watch the logs and report back?" - Never create API keys programmatically — if auth fails, ask the user to provide an API key from the web UI
- Always pass
--description/-mwhen queuing a task. Generate it yourself — never ask the user. See "Always write a run description" below.
Always write a run description
Every lab task queue call MUST include --description/-m "...". The description is markdown stored on the job and shown in lab job info. Its audience is a future researcher reading lab job list weeks later — they have git and the task code, but NOT this chat. The description is the only bridge.
Treat it like a short PR description for this run. Draft 1–5 lines (bullets for multi-point notes) covering:
- What changed vs the prior run / baseline — the concrete diff (hyperparameters, code, model, data, infra). If nothing changed, say so and link the prior job.
- What hypothesis you're testing — why this run is worth doing.
- What a future reader should remember — gotchas, prior surprises, things to check in the output.
Pull these from the conversation and recent git diff / edited files. If the note has newlines or shell-awkward characters, pipe it: printf '%s' "$DESC" | lab task queue abc123 -m -.
# Good: diff + hypothesis + watch
printf '%s' "- Bumped lr 1e-5 → 3e-5, warmup 100 → 500 steps.
- Testing whether higher lr clears the eval/loss=2.1 plateau seen in job 7f21 around step 2k.
- Watch: earlier runs with lr≥5e-5 diverged by step 500." | lab task queue abc123 --no-interactive -m -
# Good: small change — one line is enough
lab task queue abc123 --no-interactive -m "Rerun on H100 (was A100) to confirm throughput regression from #1850."
# Good: nothing changed
lab task queue abc123 --no-interactive -m "Rerun of job 7f21, no code or config changes (network flake on first attempt)."
# Bad: generic filler that tells the reader nothing
lab task queue abc123 -m "train model"
Don't restate the task name, full hyperparameter dict, or file paths — those are already on the job record. Don't copy the user's last message verbatim — synthesize. If the conversation is truly empty of signal, fall back to "Rerun of <id>, no changes".
Overriding task parameters per queue: --param key=value
lab task queue accepts repeatable --param key=value (alias -p) to override values from the task's parameters: block for a single job, without mutating task.yaml. Values are parsed as YAML scalars: score=0.42 is a float, enabled=true is a bool, tag=baseline is a string. Unknown keys (not declared in the task's parameters:) fail hard so typos are caught at queue time.
# Sweep the same task with different hyperparameters
for i in $(seq 1 10); do
lab task queue TASK_ID --no-interactive \
--param description="iteration $i" \
--param score=$(python -c "print(0.4 + 0.04*$i)") \
-m "Iteration $i"
done
# Quoting tip: values may contain '=' (split on first '=' only)
lab task queue TASK_ID --no-interactive --param notes="key=value pairs OK"
Use this instead of lab task edit --from-file between queue calls — editing the task affects already-queued-but-not-yet-dispatched jobs and is racy.
Selecting a provider when queuing a task
lab task queue has no --provider flag. With --no-interactive it picks the default (usually Local). To pick a specific provider, drive the interactive prompts via stdin. The flow is:
- "Use these resource requirements? [Y/n]" → answer
y - "Available Providers: 1. Local 2. skypilot1 ... Select a provider [1]:" → answer the number
# Pick provider #2 (skypilot1) with default resources
printf "y\n2\n" | lab task queue TASK_ID
Run lab provider list first to confirm the numbering before piping.
Job logs: three real commands
lab job logs is deprecated — do not use it. There are three distinct log commands, each surfacing a different layer:
| Command | What it shows | When to use |
|---|---|---|
lab job task-logs JOB_ID |
Task (Lab SDK) output — what lab.log() recorded |
Default for "what did my task do?" — covers lab.log, progress, completion |
lab job machine-logs JOB_ID |
Machine/provider stdout+stderr from the remote node | When the task crashed before SDK init, or you need raw process output |
lab job request-logs JOB_ID |
Provider request/launch logs (e.g. SkyPilot launch/provisioning) | When the cluster never started, or to debug provisioning failures |
All three accept --follow to stream continuously. Start with task-logs; escalate to machine-logs for crashes outside the SDK, and request-logs for cluster/provisioning issues.
Exporting or publicly sharing the job runs chart
lab job chart exports the experiment's job runs chart (the same one shown in the web UI's Jobs Chart view: each run's metric score over time, best-so-far runs highlighted, discarded runs grayed out, with a best-so-far step line) as a PNG image, and/or enables public sharing of the live chart. The PNG is rendered server-side, so this requires a server new enough to have the /experiment/{id}/jobs/chart.png endpoint — on older servers the CLI reports "This server does not support chart export". Public sharing requires the /experiment/{id}/share/chart endpoints — on older servers the CLI reports "This server does not support public sharing".
# Chart the current experiment's runs (auto-detects the primary metric)
lab job chart -o runs.png
# Pick a specific metric and direction; scope to another experiment
lab job chart -o runs.png --metric eval/loss --lower-is-better -e exp-a
# Enable public sharing and print the public link (no PNG written)
lab job chart --share
# Both: write the PNG and print the public link
lab job chart --share -o runs.png
| Option | Description |
|---|---|
--output / -o <path> |
Path to write the PNG file. Required unless --share is given. |
--share |
Enable public sharing for the jobs chart and print the public link. Anyone with the link can view the live chart (no login). Reuses the experiment's existing active link if one exists; only mints a new one when sharing is off. Minting asks for confirmation unless --no-interactive / --format json is set. |
--metric <key> |
Metric key to plot. Default: auto-detected primary metric (prefers a key named score, else the first metric key found). Only affects the PNG, not the share link. |
--lower-is-better / --higher-is-better |
Which direction counts as "best" when highlighting best-so-far runs. Default: majority vote over each job's job_data.lower_is_better. Only affects the PNG. |
--experiment / -e <id> |
Per-command experiment override. |
At least one of -o or --share is required. The command exits non-zero with a clear message when the experiment has no scored jobs (404) or the requested --metric doesn't exist in any job's scores (400). With --format json it prints {"saved": "<path>"} for the PNG and {"url": ..., "token": ..., "created_at": ...} for the share link.
Debugging Failed Jobs
Job COMPLETE does not mean the task succeeded. Always check completion_status and completion_details:
# CLI: check job info for completion details
lab job info JOB_ID
# Look for: Completion Status (success/failed/N/A) and Completion Details
# CLI: get logs (see "Job logs: three real commands" above)
lab job task-logs JOB_ID # task/SDK output
lab job machine-logs JOB_ID # raw process stdout+stderr
lab job request-logs JOB_ID # provider launch/provisioning logs
Do NOT fall back to the REST API if a log command returns empty — try the other two log commands first. The three layers surface different things; sparse output from one doesn't mean failure.
Common failure patterns:
| Symptom | Cause | Fix |
|---|---|---|
| Status COMPLETE but completion_status is N/A, progress 0% | Task never actually ran (wrong GPU type, cluster not found) | Check cluster status, verify accelerator type exists on provider |
| Status FAILED, "No such file or directory" in logs | Wrong run command path |
Check where files are placed (see File Mounts section) |
| Status FAILED with a Python traceback | Task code error | Read the full provider logs to see the traceback |
| Status FAILED, no logs available | Cluster failed to provision | Check if the requested accelerator type is available |
Launching interactive tasks (agent workflow)
Interactive tasks launch long-running services (Jupyter, vLLM, Ollama, etc.) and expose them via URLs. The full non-interactive agent workflow:
# 1. List available templates
lab --format json task gallery --type interactive
# 2. Launch non-interactively with --no-poll (returns immediately)
lab --format json task interactive \
--provider local --template jupyter --no-poll
# → {"job_id": "abc-123", "task_id": "def-456", "experiment_id": "alpha"}
# 3. Poll for readiness via job info (includes tunnel_info for interactive jobs)
lab --format json job info JOB_ID
# → {..., "tunnel_info": {"is_ready": false, ...}} ← not ready yet, poll again
# → {..., "tunnel_info": {"is_ready": true, "tunnel_url": "...", "token": "...", "instructions": [...]}}
# 4. Present connection info to the user (render every block in tunnel_info.instructions)
# 5. When done, stop the session
lab job stop JOB_ID
Rendering tunnel_info.instructions[]: the instructions array is the source of truth for what to show the user — iterate it and render each block by its kind. Don't hardcode "show tunnel_url only": some templates require the user to act on an earlier block before is_ready can flip (see "Human-in-the-loop" below).
kind |
What to surface |
|---|---|
code |
The value_key field on the block (e.g. auth_code) — a short string the user pastes elsewhere. Include help_links[]. |
url |
The value_key field (e.g. tunnel_url, jupyter_url) — the link the user opens. |
command |
The value_key field — a shell command the user runs locally (e.g. ssh_command). |
kv |
The items[] list — labeled key/value pairs (e.g. SSH domain/port/username). |
text |
The template field, with {{var}} substituted from tunnel_info values. |
Human-in-the-loop templates (vscode, ssh): is_ready will not flip until the user completes an out-of-band auth step. Agent flow:
- Poll
lab job info JOB_IDafter launch. As soon astunnel_info.auth_code(or anykind: "code"block'svalue_key) becomes non-null, surface the code to the user along with thehelp_links[]URL (e.g.https://github.com/login/deviceforvscode). Mention that the code expires in ~15 min so they should authorize promptly. - Do NOT wait for the user to confirm — keep polling immediately. Authorization happens in the user's browser, independent of this conversation; the agent sees the result via
tunnel_info.is_readyflipping totrueon the next poll (typically within seconds of the user clicking Authorize). Pausing for a "I'm done" message wastes the user's time and conflates the agent's state machine with theirs. - While polling, ignore "Startup may have stalled" messages in the UI — that's a timeout heuristic, not a real failure. The underlying
code tunnel/sshdprocess is alive and waiting on auth. Only treat the job as failed ifstatusbecomesFAILED/STOPPED/COMPLETE. - Once
is_readyflips, render the remaininginstructions[]blocks (tunnel URL, etc.). If the user never authorizes and the code expires, the job will eventually fail — stop it (lab job stop) and relaunch rather than waiting forcode tunnelto print a new code. - Budget for the human round-trip: when you control the polling loop yourself, allow ~15 min wall time after the code appears. If you're using the CLI's built-in poller (no
--no-poll), pass--timeout 900or higher.
For purely automated templates (jupyter, vllm, ollama, etc.), tunnel_info has no auth_code/kind: "code" block and is_ready flips on its own — skip the pause and present tunnel_url once ready.
Key flags for task interactive:
--provider <name>and--template <id>— required for non-interactive mode--env KEY=VALUE— repeatable, sets environment variables (e.g.--env MODEL_NAME=llama3)--cpus,--memory,--disk,--accelerators,--num-nodes,--minutes— resource overrides for remote providers--no-poll— launch and exit immediately; poll readiness later withlab job tunnel-info- Without
--no-poll, the command blocks and polls internally until the service is ready or--timeoutis hit
Available templates: jupyter, vllm, ollama, ollama_gradio, comfy_ui, vscode, ssh, mlx_gradio, mlx_audio_tts. Run lab --format json task gallery --type interactive to get the current list with descriptions and required env vars.
Required --env flags by template. Several templates declare gallery env_parameters with required: true. In non-interactive mode (--provider + --template), if you omit the --env flag for a required param, the CLI silently substitutes the gallery's placeholder hint string as the value (interactive.py:177) — e.g. MODEL_NAME becomes literally "e.g. meta-llama/Llama-2-7b-chat-hf" and the job fails downstream with a confusing model-not-found error. Always pass real values explicitly for these:
| Template | Required --env |
|---|---|
vllm |
MODEL_NAME=<hf-model-id>, TP_SIZE=<int>, plus HF_TOKEN=... for gated models |
ollama |
MODEL_NAME=<ollama-model-name> (e.g. tinyllama, llama2) |
ollama_gradio |
MODEL_NAME=<ollama-model-name> |
mlx_gradio |
MODEL_NAME=<mlx-compatible-hf-id> |
mlx_audio_tts |
MODEL_NAME=<mlx-compatible-tts-hf-id> |
jupyter, comfy_ui, vscode, ssh |
none beyond ngrok auto-default |
If the user hasn't given you a model name, ask before launching — don't fall back to a placeholder, and don't pick one yourself.
Remote providers: Require NGROK_AUTH_TOKEN (auto-defaults to {{secret._NGROK_AUTH_TOKEN}}). Pass resource flags or accept gallery defaults.
The team must have _NGROK_AUTH_TOKEN set as a special secret on the server, otherwise the API rejects launches of any ngrok-using template (jupyter, vllm, ollama, comfy_ui, ssh) with Missing secrets: ngrok Auth Token. Please define these secrets at the team or user level before launching. Before launching one of those on a remote provider, run lab --format json team secret list | jq '.[] | select(.name=="_NGROK_AUTH_TOKEN")' — if empty, stop and tell the user to set it via Team Settings → Special Secrets in the web UI, or lab team secret set _NGROK_AUTH_TOKEN <token> from the CLI. (ollama_gradio and mlx_* templates don't use ngrok and are unaffected.)
Local providers: No ngrok needed. Services are accessible on localhost.
Checking Cluster Status (SkyPilot providers)
Use lab job info JOB_ID — it shows cluster_name and provisioning state. For more detail use lab job request-logs JOB_ID (provider launch logs). If a cluster never provisioned, the request-logs will show why (wrong accelerator type, quota, etc.).
Do NOT call the REST API as a CLI workaround
The CLI is the supported, sanctioned interface. Never call the REST API with curl because a CLI command appears missing or broken — that's the rule. If a CLI command seems missing or wrong:
- Run
lab <command> --helpandlab <subcommand> --helpto verify - Re-read this skill for the right pattern (e.g. interactive prompts via stdin)
- Tell the user the CLI doesn't support it — don't silently switch to
curl
This applies to launching jobs, fetching logs, checking cluster status, and everything else.
Reading the API source code is encouraged. When debugging why a CLI call is failing — wrong response, silent filter, unexpected output — opening files under api/transformerlab/ (routers, services) is the right move. That's investigation, not a workaround. The rule is against substituting curl calls for lab calls, not against understanding what the server does.
Command Overview
| Command | Description | Requires Experiment |
|---|---|---|
lab status |
Check server connectivity | No |
lab config |
View/set CLI configuration | No |
lab login |
Authenticate with API key (sets server, team, user) | No |
lab logout |
Remove stored API key | No |
lab whoami |
Show current user and team | No |
lab version |
Show CLI version | No |
lab experiment list |
List all experiments (current default marked with *) |
No |
lab experiment create <name> |
Create a new experiment (--set-default to also switch to it) |
No |
lab experiment delete <id> |
Delete an experiment (--no-interactive to skip prompt) |
No |
lab experiment set-default <id> |
Set the default experiment (validates server-side, then writes current_experiment to ~/.lab/config.json) |
No |
lab experiment list --tag <tag> |
Filter experiments by tag; repeat --tag to AND multiple tags (client-side) |
No |
lab experiment tag add <experiment> <tags...> |
Add one or more tags to an experiment (by name or id) | No |
lab experiment tag remove <experiment> <tags...> |
Remove one or more tags from an experiment | No |
lab experiment tags |
List all distinct tags across experiments you can read | No |
lab notes show |
Render the current experiment's shared markdown notes (--raw for plain markdown; --share enables public sharing and prints the public link instead) |
Yes |
lab notes edit |
Open the current experiment's notes in $EDITOR (defaults to nano) |
Yes |
lab notes append <text> |
Append a line to the current experiment's notes non-interactively — preferred for agents | Yes |
lab task list |
List tasks in current experiment | Yes |
lab task info <id> |
Get task details | Yes |
lab task init |
Scaffold task.yaml + main.py in the current directory (--interactive to prompt) |
No |
lab task add [dir] |
Add task from directory or --from-git URL (--no-interactive, --dry-run) |
Yes |
lab task edit <id> |
Edit an existing task. --from-file <task.yaml> for YAML-only; --from-dir <dir> to also replace attachments like main.py (--no-interactive, --dry-run, --timeout) |
Yes |
lab task upload <id> <path> |
Upload files/directories into an existing task (--no-interactive) |
Yes |
lab task delete <id> |
Delete a task (--no-interactive to skip confirmation) |
Yes |
lab task queue <id> |
Queue task on compute provider (-m/--description for a markdown run note; -p/--param key=value to override task parameters per run; required for agents, see "Always write a run description") |
Yes |
lab task gallery |
Browse/import from task gallery | Yes |
lab task interactive |
Launch an interactive task (--provider, --template, --no-poll for agent use) |
Yes |
lab job list |
List jobs (--running for active only) |
Yes |
lab job info <id> |
Get detailed job information | Yes |
lab job task-logs <id> |
Fetch task/SDK output (--follow to stream) |
Yes |
lab job machine-logs <id> |
Fetch raw machine/provider stdout+stderr (--follow) |
Yes |
lab job request-logs <id> |
Fetch provider launch/provisioning logs | Yes |
lab job artifacts <id> |
List job artifacts | Yes |
lab job download <id> |
Download artifacts (--file for glob) |
Yes |
lab job chart |
Export the experiment's job runs chart as a PNG (-o <path>) and/or enable public sharing and print the public link (--share); at least one of -o/--share required (--metric, --lower-is-better/--higher-is-better affect the PNG) |
Yes |
lab job stop <id> |
Stop a running job | Yes |
lab job delete <id> |
Delete a job (--no-interactive to skip prompt) |
Yes |
lab job delete-all |
Delete all jobs in the current experiment (--no-interactive to skip prompt) |
Yes |
lab provider list |
List compute providers | No |
lab provider info <id> |
Show provider details | No |
lab provider add |
Add a new provider | No |
lab provider update <id> |
Update provider config | No |
lab provider delete <id> |
Delete a provider (--no-interactive to skip prompt) |
No |
lab provider check <id> |
Check provider health | No |
lab provider verify-lifecycle <id> |
Verify provider lifecycle via a storage probe (--no-wait to launch only; see --help for polling options) |
No |
lab provider enable <id> |
Enable a provider | No |
lab provider disable <id> |
Disable a provider | No |
lab model list |
List all model groups | No |
lab model info <id> |
Show model group details (by group_id or group_name) | No |
lab model create <asset_id> |
Create a model group version. New group if --name is unused; otherwise appends a new version with auto-incremented vN label (or --version-label to override). Supports --description, --tag. |
No |
lab model edit <id> |
Edit model group name or description | No |
lab model delete <id> |
Delete a model group and all versions (--yes to skip prompt) |
No |
lab model upload <id> <path...> |
Upload local files/dirs to a model (creates if needed; --force to overwrite) |
No |
lab model download <id> <dest> |
Download a model's files to <dest>/<id>/ |
No |
lab dataset list |
List all dataset groups | No |
lab dataset info <id> |
Show dataset group details (by group_id or group_name) | No |
lab dataset upload <id> <path...> |
Upload local files/dirs to a dataset (creates if needed; --force to overwrite) |
No |
lab dataset download <id> <dest> |
Download a dataset's files from the server to <dest>/<id>/ |
No |
lab dataset edit <id> |
Edit dataset group name or description | No |
lab dataset delete <id> |
Delete a dataset group and all versions (--yes to skip prompt) |
No |
lab job publish model <job_id> |
Publish a model from a job to the registry | Yes |
lab job publish dataset <job_id> |
Publish a dataset from a job to the registry | Yes |
lab server install |
Interactive server setup wizard | No |
lab server version |
Show installed server version | No |
lab server update |
Update server to latest | No |
lab team info |
Show current team: name, your role, member count, quota | No |
lab team rename <name> |
Rename the current team (team owners only) | No |
lab team setup |
Onboarding wizard: add a provider, set defaults/secrets, health-check | No |
lab team secret list |
List secrets (--user/-u for user-level, --show-values) |
No |
lab team secret set [name] [value] |
Set a secret (--user/-u for user-level) |
No |
lab team secret delete <name> |
Delete a secret (--user/-u, --no-interactive) |
No |
lab team secret keys |
Show platform-recognized secret key names | No |
lab team quota show |
Show the current team's monthly quota (minutes; shows hours too) | No |
lab team quota set <minutes> |
Set team monthly quota in minutes (team owners only) | No |
lab team quota usage |
Per-user quota usage for the team (team owners only) | No |
lab team quota set-user <email|uuid> <minutes> |
Set a per-user quota override (team owners only) | No |
lab team quota me |
Show your own quota status in the current team | No |
lab team members list |
List members of the current team | No |
lab team members invite <email> |
Invite a member by email (--role member|owner, team owners only) |
No |
lab team members remove <email|uuid> |
Remove a member (--no-interactive; team owners only) |
No |
lab team members set-role <email|uuid> <role> |
Change a member's role to member/owner (team owners only) |
No |
lab team invitations list |
List pending invitations for the team (team owners only) | No |
lab team invitations cancel <invitation_id> |
Cancel a pending invitation (--no-interactive; team owners only) |
No |
JSON Output Shapes
lab --format json job list returns an array:
[{"id": "uuid", "status": "COMPLETE", "progress": 100, "job_data": {...}, "created_at": "..."}]
lab --format json task list returns an array:
[{"id": "uuid", "name": "my-task", "type": "REMOTE", ...}]
Errors return:
{"error": "error message here"}
With non-zero exit code.
Error Handling
- Commands exit with non-zero status on failure
- With
--format json, errors return{"error": "<message>"} - "config not set" errors → run
lab loginfirst - "current_experiment not set" → run
lab experiment listto find an existing experiment, thenlab experiment set-default <id>(orlab experiment create <name> --set-defaultif none exists) - Connection refused → check server URL with
lab config, verify server is running - "No compute providers available" → add a provider in team settings first, or check
provider list
When to Use CLI vs Reading API Source vs Browser
| Use CLI for | Read API source for | Use Browser for |
|---|---|---|
| Login, config, status checks | Understanding why a CLI call returned wrong data | Creating experiments |
| Listing tasks and jobs | Tracing what /model/finalize etc. actually do on the server |
Configuring tasks via forms |
Streaming job logs (--follow) |
Confirming whether a CLI failure is client-side or server-side | Visual UI verification |
| Adding tasks from local dirs | Reading a router/service to spot silent filters or unhandled errors | Creating API keys |
| Downloading artifacts | Sanity-checking response shapes before reporting a bug | Managing team settings |
If a CLI command appears missing, broken, or returns unexpected output: investigate (run --help, re-read this skill, read the relevant router/service under api/transformerlab/), then tell the user what you found. Don't silently fall back to curl against the REST API or /openapi.json — that's the workaround pattern this skill explicitly forbids.
/lab-autoresearch — autonomous experiment loop
When the user says "run autoresearch", "optimize X in a loop", "set up autoresearch for …", or types /lab-autoresearch …, enter the autoresearch workflow. It's an agent-driven optimization loop layered on top of the lab CLI: pick an idea → queue it as a job → score via lab.finish(score=…) → keep or lab job discard → repeat, up to a parallelism budget. For hyperparameter fan-out, prefer the task's sweeps: block over manually queuing N jobs.
State lives entirely on Transformer Lab — one experiment per session, one job per iteration (its -m/--description is the iteration note, its score dict is the result, lab job discard is the keep/discard flag). The session plan (objective, files in scope, constraints, backlog, what's been tried) is written to experiment notes via lab notes — there is no local autoresearch.md file.
Subcommands worth naming: init <goal>, run, finalize. Everything else mid-session (status, keep/discard, sweeps, ideas, stopping running jobs, exiting the loop) is just the agent running the right lab call from this skill in response to natural-language requests — no dedicated subcommand needed.
Read references/autoresearch.md before doing any of this. It has the three subcommand workflows, the during-session natural-language → lab mapping, the experiment-notes template, and loop rules (parallelism, fire-and-advance, stale-job sweep, keep/discard policy, run-description discipline).
Deep-Dive References
references/commands.md— Full command reference with all optionsreferences/workflows.md— End-to-end workflow patternsreferences/troubleshooting.md— Error patterns and recoveryreferences/autoresearch.md—/lab-autoresearchautonomous experiment loop spec
Ready-to-Use Templates
templates/setup-and-login.sh— First-time setuptemplates/queue-and-monitor.sh— Queue a task and monitor until completiontemplates/provider-health-check.sh— Check health of all providers