Guides
Train an open-weight model on a twin
A website-plane twin environment (twe_…) is backed by an exportable world: seeded SQLite state, executable tools, tasks with executable VCode verifiers. A training kit packages that world in the layout blobfish train consumes, plus verifier-passed SFT rows. The server can also queue native company training on a worker you connect. The worker owns the model files, training compute and private execution evidence.
Train and verify on your company worker
A ready company export includes company identity, starting state, declared tools, tasks and verifiers. The worker compiles and qualifies it, freezes independent training and evaluation cases, trains through Codex, compares base/SFT/RL results and reloads the exported model for a fresh workflow replay.
On your company machine, use Python 3.12, uv and npm to install the worker, the tested Codex 0.153.3 harness and a small open-weight model. The installer includes the complete company runtime:
uv tool install --python 3.12 --reinstall --with-executables-from fleet-training --with-executables-from huggingface-hub --with-requirements https://blobfish.ai/sdk/company/22e964dba24028eef851130638400798d43bd40982b8a79e281fb9830e7c5ee4/requirements.txt 'blobfish-cli[company]'
npm install --global @openai/codex@0.153.3
hf download Qwen/Qwen2.5-0.5B-Instruct
blobfish company worker --helpIn the environment’s Train panel, choose Create worker connection and run the printed command. The panel updates when the worker connects. Select an advertised model, then choose Train and verify. Defaults are five SFT epochs and four RL steps with two attempts per group, at most 30 active minutes and 200 model calls on your worker.
curl -fsS -X POST "https://blobfish.ai/api/v1/twin/environments/$ENV_ID/training-jobs" \
-H "X-API-Key: $BLOBFISH_API_KEY" -H "Content-Type: application/json" \
-d '{"launch":"worker","base_model":"Qwen/Qwen2.5-0.5B-Instruct"}'
# 202: queued; GET the returned job to observe native progress and measured results.Use DELETE …/training-jobs/<job_id> to cancel. Evidence and cancellation remain available after the environment’s serving lease expires. A kit alone never marks the post-trained-agent check complete. A completed worker job may report a failed replay; a successful replay still does not establish company acceptance or live-service parity.
Portal source transfers are bounded to 1 MiB. For a larger company export, keep the original file on the company machine and use the same native pipeline directly:
blobfish company inspect --source ./world.json
blobfish company train --source ./world.json --base-model Qwen/Qwen2.5-0.5B-Instruct --job-store ./company-jobs --runMCP schemas alone cannot supply company policies or realistic starting states. A source with one independent state stops before training. The worker records actual optimizer updates and held-out coverage, and reports RL’s benefit relative to SFT separately. Its exported package uses the pinned Codex harness in shadow mode.
Choose a base
Every id below is a Hugging Face repo id the fleet_training trainers accept verbatim; the same list is served by GET /api/v1/models under open_weight. The default for generic kits is Qwen/Qwen3-8B. Native workers advertise the specific bases they can run. VRAM is an approximation for LoRA SFT/GRPO at bf16 with a 4k context.
Base (--base) | Params | Min VRAM (LoRA) | License | local-mlx mirror |
|---|---|---|---|---|
Qwen/Qwen3-0.6BSmoke-test the loop in minutes. | 0.6B | 4 GB | Apache-2.0 | mlx-community/Qwen3-0.6B-4bit |
Qwen/Qwen3-1.7B | 1.7B | 8 GB | Apache-2.0 | mlx-community/Qwen3-1.7B-4bit |
Qwen/Qwen3-4B | 4B | 12 GB | Apache-2.0 | mlx-community/Qwen3-4B-4bit |
Qwen/Qwen3-8B · recommendedThe base every fleet_training result in this repo was measured on. | 8B | 20 GB | Apache-2.0 | mlx-community/Qwen3-8B-4bit |
Qwen/Qwen3-14B | 14B | 36 GB | Apache-2.0 | mlx-community/Qwen3-14B-4bit |
Qwen/Qwen3-32B | 32B | 80 GB | Apache-2.0 | — |
Qwen/Qwen3-30B-A3B3B active parameters; fast inference, MoE-aware trainer required. | 30B | 80 GB | Apache-2.0 | — |
Qwen/Qwen2.5-7B-Instruct | 7B | 20 GB | Apache-2.0 | mlx-community/Qwen2.5-7B-Instruct-4bit |
Qwen/Qwen2.5-Coder-7B-Instruct | 7B | 20 GB | Apache-2.0 | — |
meta-llama/Llama-3.1-8B-Instruct | 8B | 20 GB | Llama 3.1 Community | mlx-community/Meta-Llama-3.1-8B-Instruct-4bit |
meta-llama/Llama-3.2-3B-Instruct | 3B | 10 GB | Llama 3.2 Community | — |
google/gemma-3-4b-it | 4B | 12 GB | Gemma | — |
google/gemma-3-12b-it | 12B | 32 GB | Gemma | — |
openai/gpt-oss-20bMoE; 3.6B active parameters. | 20B | 48 GB | Apache-2.0 | — |
deepseek-ai/DeepSeek-R1-Distill-Qwen-7B | 7B | 20 GB | MIT | — |
mistralai/Mistral-7B-Instruct-v0.3 | 7B | 20 GB | Apache-2.0 | — |
HuggingFaceTB/SmolLM3-3B | 3B | 10 GB | Apache-2.0 | — |
Build the kit
From the environment console (/twin/<env_id>#train) or the API. Defaults: base_model Qwen/Qwen3-8B, method grpo, target local-mlx, lora_rank 16, steps 100 (GRPO), epochs 8 (SFT), seed 42. A base outside the catalog is a 400 that lists the accepted ids.
$ curl -sS -X POST https://blobfish.ai/api/v1/twin/environments/$ENV_ID/training-jobs \
-H "X-API-Key: $BLOBFISH_API_KEY" -H "Content-Type: application/json" \
-d '{"base_model":"Qwen/Qwen3-8B","method":"grpo","target":"local-mlx","lora_rank":16,"steps":100}'
# 201 → {job_id, status:"kit_ready", launch:"manual", tasks_count, tasks_minted, sft_rows, sft_source, kit{download_url, sha256, bytes}, commands{…}}A downloaded-kit record has status kit_ready and launch manual. GET …/training-jobs lists kits and worker jobs, GET …/training-jobs/<job_id> reads one, and GET …/training-jobs/<job_id>/kit streams the archive (its sha256 is in the record and the X-Content-SHA256 header).
Run blobfish train
uv tool install --with "blobfish-sdk @ https://blobfish.ai/sdk/blobfish_sdk-0.6.2-py3-none-any.whl" "blobfish-cli @ https://blobfish.ai/sdk/blobfish_cli-0.1.13-py3-none-any.whl"
curl -fsSL -H "X-API-Key: $BLOBFISH_API_KEY" -o kit.tar.gz \
https://blobfish.ai/api/v1/twin/environments/$ENV_ID/training-jobs/$JOB_ID/kit && tar -xzf kit.tar.gz
# validate the kit and write <out>/plan.json — trains nothing, provisions nothing
blobfish train --world ./$ENV_ID-train-kit --data ./$ENV_ID-train-kit/sft.jsonl --base Qwen/Qwen3-8B --method grpo --target local-mlx --out ./train-out --dry-run
# train on this machine (Apple Silicon: local-mlx; GPU: local-torch)
blobfish train --world ./$ENV_ID-train-kit --data ./$ENV_ID-train-kit/sft.jsonl --base Qwen/Qwen3-8B --method grpo --target local-mlx --out ./train-out
# PAID: RunPod GPU pod — requires RUNPOD_API_KEY; the CLI refuses to spend without --confirm-spend; GRPO only
blobfish train --world ./$ENV_ID-train-kit --data ./$ENV_ID-train-kit/sft.jsonl --base Qwen/Qwen3-8B --method grpo --target runpod --out ./train-out --confirm-spendThe job record’s commandsblock carries these exact lines with your job’s config filled in. --dry-run validates sft.jsonl and writes plan.json only. --target runpod costs real money: it needs RUNPOD_API_KEY in your shell, is gated by --confirm-spend, and runs GRPO only — run SFT locally first and warm-start from the adapter.
blobfish train is a factory verb: unlike the twin verbs, it needs a blobfishai checkout on the machine (export BLOBFISH_HOME=<checkout>) so the fleet_training trainers (MLX / torch + peft) import. The kit itself is self-contained.
What the kit contains
| File | Contents |
|---|---|
environment.db / seed.db | pristine SQLite state materialized from the world’s own seeded rows (identical bytes; the loader forks episodes from seed.db) |
tools.json / tools_combined.py | every executable tool: Python source, JSON schema, read/write tables |
tasks.json | tasks with instruction, vcode, required_tools, expected state changes / answer keys |
world.json | the full sandbox world document (tables, tools, tasks, verifiers) — the format customers already hold |
sft.jsonl | OpenAI-messages rows in the exact blobfish distill-data shape, one per verifier-passed trajectory, with metadata.source |
kit.json / README.md | manifest (sources, counts, per-file sha256) and the commands above |
The directory is a packaged world: blobfish info ./<kit> loads it, and blobfish train --world ./<kit> trains on it.
Honesty: where tasks and SFT rows come from
- Minted tasks.A service twin ships with no tasks. When the world has none, the kit mints a bounded set (up to 12 candidates) deterministically with the programmatic task generator and admits only those that pass the executable discrimination gate the creation pipeline uses: the reference chain must pass the task’s own VCode, and null, shortcut, wrong-row and wrong-value probes must fail. The record reports
tasks_mintedandtasks_rejected. Minted tasks live in the kit only — the environment’s world is never modified (tasks_persisted: false). - SFT rows are executed, never written.
sft_sourcenames the source:verified_world_trajectorieswhen the world already holds VCode-verified trajectories, otherwisereference_chain_replay— the task’s deterministic reference chain is executed against an isolated copy of the seed database, the real observations are recorded, and the row is kept only if the task’s VCode passes that exact trace. Assistant turns are the executed tool calls; the final answer is the task’s own answer key when it has one, else a templated completion line —metadata.final_answer_sourcesays which. No model text is generated or invented. Replayed rows carrymetadata.arguments_source: reference_chain_arg_builder: their tool-call arguments are the deterministic reference chain’s (anchor-row values and ids threaded from prior reads), not arguments parsed from the prompt’s wording. - Nothing is launched.
launchis alwaysmanual. The server never starts a trainer, never calls RunPod, never stores a RunPod key.target: runpodonly changes the printed command. - Cluster-plane environments are not trainable.
tenv_…environments expose provider MCPs through a gateway and carry no exportable world; the API answers409 plane_not_trainablerather than building a half-kit.
Next
Twins quickstart · Local testing · Custom scenarios · Digital twins