Skip to main content
Evals and reinforcement learning for agents run the same loop: put an agent in an environment, let it act, and grade the result. To measure a model or train one, you run that loop many times per task and keep every trajectory with its reward. That takes an environment that starts from the same state every time, a grader the agent can’t tamper with, and a place to collect the results. This guide builds all three on Archil:
  • Environment: a stopped sandbox with a small task suite installed, kept as a template. Each rollout forks it, so every agent starts on an identical, isolated machine. The fork is the environment’s reset().
  • Grader: hidden checks that enter the fork only after the agent finishes, and turn the result into a reward.
  • Dataset: one JSONL record per rollout, appended to an Archil disk, then scored where it lives with a serverless command.
Parallel agents races forks and keeps the best one. Here you keep every rollout, pass or fail: the output is a graded trajectory dataset for pass@k reports, rejection sampling, or RL fine-tuning.

Prerequisites

Export your Archil API key, a region where sandboxes are available (aws-us-east-1, aws-us-west-2, or aws-eu-west-1), and an AI Gateway key for the model:
Install the dependencies. The TypeScript agent uses the AI SDK; the Python agent calls the same model through the gateway’s OpenAI-compatible API.

Define the tasks

The suite has three small, deterministic tasks: implement an expression evaluator from a spec, write a report from a messy CSV, and fix a buggy parser. This script runs once inside the template and creates a directory per task under /work, each with a TASK.md for the agent and the starting files.
setup.sh
The grader stays on your machine, so the agent can’t read the checks or edit them to earn a reward it didn’t. Like most real evals, it tests cases the spec implies but doesn’t list: Python number literals such as 1e3, and a CSV the agent never saw. It prints how many checks passed for one task.
grade.py

Build the environment template

Create the disk for the trajectories, then a sandbox that runs setup.sh. The tasks live on the sandbox’s root filesystem, which every fork copies, and the template doesn’t mount the disk: the orchestrator writes to it through the API. Once the tasks are in place, the template is stopped, so it doesn’t use compute while it waits.

Run the rollouts

The rollout script runs each task twice, at most four rollouts at a time. Each rollout:
  1. Forks the template. A fork of a stopped sandbox comes up running, with a copy of its root filesystem, in about a second.
  2. Runs an agent with a bash tool bound to its fork, capped at 10 steps.
  3. Uploads grade.py, runs it, and uses the fraction of checks passed as the reward.
  4. Appends one JSONL record to trajectories/<run>.jsonl on the disk: task, sample, model, reward, steps, token usage, timings, and every message and tool call, with tool output truncated to 2,000 characters.
  5. Deletes the fork, in a finally block so a failed rollout doesn’t leave a sandbox behind.
Concurrent appends to an existing file are safe, but two first appends that both create the file conflict, so the script creates the file before the rollouts start.
Rollouts finish in any order. Most of the run time is the agents working; each fork_ms in the dataset was around a second. One run looked like this:

Score the run

The dataset is plain JSONL on the disk, so you can score it without downloading it. This jq program groups the records by task and computes the number solved (every check passed), pass@1, pass@2, and the mean reward. pass@2 uses the unbiased estimator, so it needs at least two samples per task and stays correct when you collect more.
score.jq
The score script uploads the program next to the data and runs it with disk.exec, in a short-lived VM with the disk mounted. It scores every run on the disk, so samples from later runs add to the totals.
Results vary from run to run. In this one, every duration and sales-report rollout passed all its checks, including the CSV the agent never saw. Both calc rollouts passed 12 of 13 checks: they handled precedence and .5 but rejected 1_000, a number Python accepts. pass@1 counts those as failures, while the mean reward shows they were close. That partial credit is what lets RL tell a near miss from a wrong answer, and the trajectories show exactly where each agent went wrong.

Scale it up

  • More samples: raise SAMPLES and CONCURRENCY. Each fork is an independent machine, so rollouts don’t share state no matter how many run at once.
  • More tasks: add directories to setup.sh and checks to grade.py. Build a new template, such as rl-env-template-v2, instead of changing the old one, so earlier results stay reproducible.
  • Use the data: the records are ordinary JSONL. Filter for reward == 1 to build a fine-tuning set, compare models by grouping on .model as well as .task, or read the files from any S3 client through the S3-compatible API.

Clean up

Rollout forks delete themselves, so the template and the disk are what’s left. The script also deletes forks left behind by an interrupted run; they have to go before the template they came from.

Next steps

Parallel agents

Race several agents on forks of one sandbox and keep the best result.

Persistent sandboxes

Lifecycle, services, network access, and forking in detail.

Serverless execution

Run commands directly on a disk without managing a sandbox.

S3-compatible API

Append to objects and read them from any S3 client.