Skip to content

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:

One-time local setup
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 --help

In 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.

Queue through the API after connecting the 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:

Direct company export
blobfish company inspect --source ./world.json
blobfish company train --source ./world.json --base-model Qwen/Qwen2.5-0.5B-Instruct --job-store ./company-jobs --run

MCP 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)ParamsMin VRAM (LoRA)Licenselocal-mlx mirror
Qwen/Qwen3-0.6B
Smoke-test the loop in minutes.
0.6B4 GBApache-2.0mlx-community/Qwen3-0.6B-4bit
Qwen/Qwen3-1.7B1.7B8 GBApache-2.0mlx-community/Qwen3-1.7B-4bit
Qwen/Qwen3-4B4B12 GBApache-2.0mlx-community/Qwen3-4B-4bit
Qwen/Qwen3-8B · recommended
The base every fleet_training result in this repo was measured on.
8B20 GBApache-2.0mlx-community/Qwen3-8B-4bit
Qwen/Qwen3-14B14B36 GBApache-2.0mlx-community/Qwen3-14B-4bit
Qwen/Qwen3-32B32B80 GBApache-2.0
Qwen/Qwen3-30B-A3B
3B active parameters; fast inference, MoE-aware trainer required.
30B80 GBApache-2.0
Qwen/Qwen2.5-7B-Instruct7B20 GBApache-2.0mlx-community/Qwen2.5-7B-Instruct-4bit
Qwen/Qwen2.5-Coder-7B-Instruct7B20 GBApache-2.0
meta-llama/Llama-3.1-8B-Instruct8B20 GBLlama 3.1 Communitymlx-community/Meta-Llama-3.1-8B-Instruct-4bit
meta-llama/Llama-3.2-3B-Instruct3B10 GBLlama 3.2 Community
google/gemma-3-4b-it4B12 GBGemma
google/gemma-3-12b-it12B32 GBGemma
openai/gpt-oss-20b
MoE; 3.6B active parameters.
20B48 GBApache-2.0
deepseek-ai/DeepSeek-R1-Distill-Qwen-7B7B20 GBMIT
mistralai/Mistral-7B-Instruct-v0.37B20 GBApache-2.0
HuggingFaceTB/SmolLM3-3B3B10 GBApache-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.

POST training-jobs
$ 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

download, validate, 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-spend

The 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

FileContents
environment.db / seed.dbpristine SQLite state materialized from the world’s own seeded rows (identical bytes; the loader forks episodes from seed.db)
tools.json / tools_combined.pyevery executable tool: Python source, JSON schema, read/write tables
tasks.jsontasks with instruction, vcode, required_tools, expected state changes / answer keys
world.jsonthe full sandbox world document (tables, tools, tasks, verifiers) — the format customers already hold
sft.jsonlOpenAI-messages rows in the exact blobfish distill-data shape, one per verifier-passed trajectory, with metadata.source
kit.json / README.mdmanifest (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

  1. 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_minted and tasks_rejected. Minted tasks live in the kit only — the environment’s world is never modified (tasks_persisted: false).
  2. SFT rows are executed, never written. sft_source names the source: verified_world_trajectories when the world already holds VCode-verified trajectories, otherwise reference_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_source says which. No model text is generated or invented. Replayed rows carry metadata.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.
  3. Nothing is launched. launch is always manual. The server never starts a trainer, never calls RunPod, never stores a RunPod key. target: runpod only changes the printed command.
  4. Cluster-plane environments are not trainable. tenv_… environments expose provider MCPs through a gateway and carry no exportable world; the API answers 409 plane_not_trainable rather than building a half-kit.

Next

Twins quickstart · Local testing · Custom scenarios · Digital twins