shell and write_file. The model works for as many turns as it needs. A shell check the agent never sees then grades the files it left: reward 1 when the check passes, otherwise 0.
The pattern fits any agent that acts on an environment through tool calls and is judged on the result: your tasks, your tools, and your grader, with the Loops cookbook running the sandboxes, sampling, and training.
Prerequisites
- A Baseten workspace with Loops and its API key. Qwen3.5-4B trains only in the dedicated availability mode, which bills for GPU time.
- A Blaxel workspace and API key.
- uv and Python 3.12 or later.
Install the cookbook
Install the Loops cookbook from PyPI into a uv project. The cookbook needs Python 3.12 or later, installs thebaseten-loops SDK, and provides the loops command. loops runs both recipes; each training config names its recipe with recipe = "rl" or recipe = "sft". Create a project first if you don’t have one:
examples/counting/train.toml, from your project’s directory. To change an example, copy its directory into your project at the same path. A config in your project wins over the installed one, and loops check prints the directory of the config it used.
Create a .env file in your project’s directory and set BASETEN_API_KEY to a workspace API key. The commands load .env from the training config’s directory or the directory you run them from. Never commit .env.
A run uses the serverless availability mode when your workspace has serverless access to the model, and the dedicated mode otherwise. To choose a mode, add BASETEN_LOOPS_SCOPE=serverless or BASETEN_LOOPS_SCOPE=deployment to .env.
Install the Blaxel provider:
.env as BL_WORKSPACE and BL_API_KEY. Without them, check stops with:
Confirm your setup
Check the cookbook’s smallest pattern first. It needs no sandbox, so it confirms your API key and the model before you add Blaxel. From your project’s directory, run the following command:Loops model: Qwen/Qwen3.5-4B; max_seq_len 1024 (enabled up to <limit>). The report ends with:
Read the tasks
examples/tool_agent/tasks.jsonl holds eight tasks. Each line has the files to create, an instruction for the agent, and a shell check that exits 0 when the task is done:
examples/tool_agent/tasks.jsonl
grep paid also counts unpaid orders, so the obvious command gives 5 instead of 3. A group learns only when some attempts succeed and others fail, so these traps are where the learning signal comes from. See size tasks so attempts disagree.
Read the episode
examples/tool_agent/tool_episode.py defines ToolEpisode, one object per attempt. Its three methods are the whole environment.
setup() writes the task’s files into /work in the attempt’s sandbox, before the agent starts:
examples/tool_agent/tool_episode.py
run() is the agent. It sends the conversation and tools to the policy, runs each tool call in the sandbox, and appends the result, until the model replies without a tool call or reaches max_turns. It returns why it stopped, which the run reports as rollout/termination_reason/<reason>:
examples/tool_agent/tool_episode.py
generate() call. Those are the tokens that train.
grade() stops the commands still running in the sandbox, then runs the task’s check. A process the agent detached isn’t stopped, so the check still trusts what the sandbox holds. See close the gaps a policy can exploit:
examples/tool_agent/tool_episode.py
Read the training config
examples/tool_agent/train_blaxel.toml connects the episode, the tasks, and the model. These sections of it shape the run:
examples/tool_agent/train_blaxel.toml
group_size = 16runs each task 16 times per group. The advantage of each attempt is its reward minus the group’s mean.max_group_losses = 1lets a group still train when one sandbox fails.max_steps = 5keeps the first run short. Raise it once the per-group rewards show the tasks are sized well.max_seq_len = 8192bounds the whole conversation, tool results included.ttl = "1h"is a backstop that deletes a sandbox a crashed run abandoned. The run releases each sandbox when its attempt ends.
Run the example
To check the configuration, run the following command. It validates the tasks and options and launches nothing:runs/tool-agent-blaxel/ in your project directory and logs the Loops run ID when it exits. The run ID is also in the run directory’s run.json.
Read the results
Read each update’s metrics in the run directory’ssteps.jsonl:
- Per-group rewards and
discard/groups/no_variance. A task whose 16 attempts always score alike teaches nothing. Make it harder or easier. rollout/termination_reason/*. A risingmax_turnsortruncatedrate means the agent is running out of room rather than solving the task.reward/meanandreward/mean_with_unusable. The first trains; the second includes groups the run rejected.
Shut down the run
The dedicated trainer and sampler keep running afterloops run exits. Deactivate the run when you finish to stop GPU billing without waiting for automatic shutdown. Run the following command with your run directory:
Release sandboxes after a crash
Sandboxes bill until they’re deleted. If the run crashed, release any sandboxes it left with the following command:Adapt it to your tasks
Copy the example from the installed package into your project under a new name, such asmy_agent:
- Tasks: replace
tasks.jsonlwith your own files, instructions, and checks. Keep checks out of the instruction text. - Tools: change
TOOLSandcall()intool_episode.pyto the actions your agent takes in production. - Grader: change
grade()to score what the task produces. Keep it independent of the agent’s own claims.
uv run loops check my_agent/train_blaxel.toml, a short run, and read the per-group rewards before you scale up.
Next steps
- Train a coding agent on Terminal-Bench: A Harbor agent on a real multi-turn benchmark.
- Design RL environments and rewards: Task sizing, graders, and exploits.
- Deploy a checkpoint: Serve the trained adapter.