Introducing the Arena Client: Reinforcement learning at scale, from your terminal

Jaime Sabal

&

July 14, 2026

Reinforcement learning has become the defining stage of LLM post-training, and more broadly it remains the only general recipe we have for teaching agents to act optimally in sequential decision-making problems. The results speak for themselves, but anyone who has tried to run RL seriously knows that the engineering efforts required to train an agent successfully are often too large to manage. Before a single learning step happens, we must iterate on brittle environments and reward functions, develop robust distributed training setups across remote clusters, checkpoint against failures, and eventually serve the trained model reliably. Even when all of that is set and done, to then achieve optimal performance we must go through tedious hyperparameter optimization procedures that take ages to finish, without the certainty these will prove satisfactory. Very little of this has anything to do with the problem you set out to solve, and yet it is where most of the engineering time goes.

Arena, our managed RLOps platform, exists to take that entire layer off your plate. It runs our evolutionary training pipeline on cloud infrastructure that was designed from the ground up for reinforcement learning workloads.

The Arena Client

The Arena client library, which we are releasing as agilerl-arena on PyPI, serves as a more developer-friendly interface to Arena. Available as both a Python SDK and the arena command-line tool, it was built around the idea that your local machine should be the place where you define and steer experiments, not the place that carries them.

Everything you need to take an experiment from an idea to a served model goes through it. After authenticating through an API key or browser login, the client covers the full RL development pipeline:

  • Uploading and validating custom environments before any training time is spent on them
  • Registering datasets for LLM fine-tuning directly from Hugging Face or from your own files
  • Organising work into projects
  • Submitting training jobs from declarative YAML manifests onto whichever compute tier fits the workload, across any number of nodes.
  • Monitoring training and listing saved checkpoints
  • Deploying trained agents to held-out inference endpoints that can be queried directly from the client

From local development to training on Arena

The bridging role between local development and reliable distributed training is the part we have focused on. A manifest you submit to Arena describes an experiment analogously to how the AgileRL framework does locally - with the same algorithms, network configuration, and evolutionary hyperparameter optimisation settings, meaning the experiment you tested locally is the experiment you submit to the platform. Validation happens before training does, so a malformed environment or a broken reward function surfaces in seconds with actionable insights into what went wrong. The only thing that changes when you move to the cloud is the scale of the underlying compute and the use of our heavily abstracted distributed training setup.

We have implemented trainers in AgileRL v2.8.0 that allow for a seamless transition between local development and submitting jobs to Arena. Given a training manifest, they contain analogous methods to train either on your machine or on cloud infrastructure:

from agilerl import LocalTrainer, ArenaTrainer

# Local training
local_trainer = LocalTrainer.from_manifest("path/to/manifest.yaml")
local_trainer.train()

# Train on selected compute tier
arena_trainer = ArenaTrainer.from_manifest("path/to/manifest.yaml")
arena_trainer.train(
    resource_id="arena_medium",
    num_nodes=4,
    experiment_name="test_experiment"
)

The rest of this post walks through one end-to-end use case as a way of making all of this concrete: fine-tuning a language model to reason with GRPO on the GSM8K dataset, following our tutorial.

A use case: teaching a small model to reason

GRPO (Group Relative Policy Optimization) is an elegant simplification of PPO for language models. Where PPO needs a critic, a second network trained to estimate value, which for an LLM means holding another multi-billion-parameter model in memory and training it in lockstep with the policy, GRPO removes the critic entirely. It samples a group of completions for each prompt and scores every completion relative to the group mean, so the group itself becomes the baseline. This gives a stable training signal at a fraction of the memory cost, which matters a great deal when the policy has billions of parameters.

GRPO requires a reward function to score completions, and that is why reasoning tasks with verifiable answers are problems to which the algorithm is well-suited. GSM8K, the dataset we train on here, is the canonical example: thousands of grade-school math word problems, each with a worked solution whose final line carries the numeric answer after a #### marker. Any dataset with labelled answers is amenable to the same training recipe, whether the verification comes from unit tests run against generated code, exact-match extraction on structured outputs, or a simulator scoring a plan.

The model we fine-tune is Qwen/Qwen2.5-0.5B-Instruct, chosen because it is small enough to train quickly and cheaply while still being capable of showing real learning. Swapping in a larger model later amounts to editing one line of the manifest.

Prerequisites

Everything that follows needs the client installed and an authenticated session. The client ships as its own lightweight package, but can also be installed through an extra dependency of AgileRL:

pip install agilerl-arena
# or
pip install agilerl[arena]

Authentication is a one-time step per machine. If you have an API key, found in the Arena dashboard under Profile Management, export it as an environment variable. Otherwise, arena login opens a browser for an interactive login and persists your credentials locally:

export ARENA_API_KEY="arena_pat..."
# or
arena login


Creating the dataset

Arena's simulation-based workflow begins by validating an environment, where you upload Gym, PettingZoo or LLM environment code and the platform comprehensively checks that it works correctly before allowing training on it.

For dataset-based training with LLMs, that step is replaced with dataset registration, allowing users to either upload a CSV file or specify an existing Hugging Face dataset to train on.

Registering GSM8K takes a single command, with Arena importing the data directly from Hugging Face so there is nothing to download or reformat locally:

arena datasets create gsm8k \
  --category reasoning \
  --hf-dataset openai/gsm8k \
  --hf-config main \
  --hf-split train \
  --column-mapping '{"question": "question", "answer": "answer"}'

The category argument tells Arena what kind of training the data supports, so the platform can check that dataset and algorithm actually fit together before any GPU time is spent, and each category expects its own column mapping:

  • reasoning: question and answer pairs with verifiable answers, for algorithms like GRPO. Expects {"question": "<col>", "answer": "<col>"}.
  • preference: a prompt with a chosen and a rejected completion, for algorithms like DPO. Expects {"prompt": "<col>", "chosen": "<col>", "rejected": "<col>"}.
  • sft: supervised fine-tuning pairs mapping a prompt to a target completion. Expects {"prompt": "<col>", "target": "<col>"}.

GSM8K happens to use the question and answer names already, but an internal dataset with query and resolution columns would register just as easily by mapping those names across.

Rewards as code

A reasoning job on Arena needs a reward function, and the mechanism to define one is as simple as writing a Python file. Arena calls a function named reward with three arguments: the question, the reference answer, and the model's completion, expecting a float in return. That is the entire contract, and everything else about how completions are scored is up to you.

For GSM8K our reward has two parts. A correctness reward parses the answer after the #### marker, extracts the final number from the model's <answer> block, and returns 1.0 when they match, while a format reward returns 1.0 when the completion wraps its work in <think>...</think> and <answer>...</answer> tags, for a maximum combined score of 2.0. What we never do is show the model the format we want or tell it which answer to produce. We only reward it when it happens to do both, and under GRPO's group-relative scoring the completions that reason carefully and present their answer cleanly outscore their siblings, so the model gradually discovers the behaviour we were hoping for from the gradient alone.

Because the reward is arbitrary code, you can craft it for your specific task: shaped partial credit for near-misses, executing generated code against a test suite, penalising verbosity, rewarding correct tool-call syntax, or any weighted combination of these are all just more Python in the same file.

Often, reward bugs can lead to silent failures in RL pipelines, where a subtly broken function burns a night of GPU hours producing garbage. This is another place the Arena’s validation pays off: Arena runs your reward function against sample completions first and gives you feedback, so a function that presents regex issues or poorly defined checks is caught before the run begins rather than discovered in the morning.

Defining experiments through manifests

The training run itself is described by a single YAML manifest covering the algorithm and its hyperparameters, the dataset, the model, and the evolutionary training settings. The environment section simply names the dataset we registered on Arena previously, while the network section names the base model to train from along with a LoRA configuration, so that training updates a small adapter rather than the full weights and remains economical even as the base model grows.

Two entries in the manifest deserve attention. The first is group_size, GRPO's defining parameter, which sets how many completions are sampled per prompt to form the scoring group and is the main parameter trading generation compute against signal quality.

The second is the mutation section, which is where we depart from conventional fine-tuning, because Arena trains a population of agents rather than a single model. Members of the population train in parallel and are periodically evaluated, with the strongest surviving tournament selection while their hyperparameters mutate within ranges you define in the manifest. Learning rates and KL penalties are notoriously fiddly in LLM RL, and sweeping them by hand means paying for a full training run per guess, whereas evolutionary optimisation folds the sweep into the run itself, letting poor settings die out and good ones propagate while training progresses.

algorithm:
  name: GRPO
  batch_size: 6
  beta: 0.001
  lr: 0.000005
  clip_coef: 0.2
  max_grad_norm: 0.1
  update_epochs: 1
  group_size: 8
  temperature: 0.9
  use_vllm: false
  calc_position_embeddings: true
  reduce_memory_peak: false

environment:
  name: gsm8k

training:
  max_steps: 100_000
  evo_steps: 5
  pop_size: 2
  eval_loop: 1
  evaluation_interval: 5

mutation:
  mutation_sd: 0.1
  rand_seed: 42
  probabilities:
    no_mut: 0.1
    arch_mut: 0.0
    new_layer: 0.0
    params_mut: 0.0
    act_mut: 0.0
    rl_hp_mut: 0.6
  rl_hp_selection:
    lr:
      min: 0.0000001
      max: 0.00001
    beta:
      min: 0.0001
      max: 0.01
  tournament_selection:
    tournament_size: 2
    elitism: true

network:
  pretrained_model_name_or_path: Qwen/Qwen2.5-0.5B-Instruct
  max_context_length: 512
  lora_config:
    lora_r: 16
    lora_alpha: 64
    target_modules:
      - q_proj
      - k_proj
      - v_proj
      - o_proj
    lora_dropout: 0.05
    task_type: CAUSAL_LM


Submitting, monitoring, deploying

Before getting started with training, we must create a project to work on in Arena. This can be done simply with one command: arena projects create 'GSM8K Tutorial'. Then, from a manifest and a reward file, we can submit a training job with one command:

arena experiments submit gsm8k_grpo.yaml \
  --reward-file reward.py \
  --resource-id arena-medium \
  --num-nodes 2 \
  --project 'GSM8K Tutorial' \
  --experiment-name gsm8k-grpo

After a user submits a training job to Arena, the manifest is validated internally along with the uploaded reward function, GPU nodes are provisioned on the selected compute tier, a population of agents is distributed across them, and training begins. Compute tiers can be browsed with arena resources list, each priced in credits per node-hour, and the --num-nodes flag scales the job horizontally in a way that suits population-based training particularly well, since more nodes simply means more of the population training at once rather than a training script that needs rewriting. The experiment definition and the compute it runs on are independent choices (subject to resource usage constraints), which is exactly what lets the same submission grow from this tutorial's half-billion-parameter model into considerably heavier configurations.

While the job runs, everything streams back to the Arena dashboard, where you can watch reward curves climb across the population and see which mutated hyperparameters are pulling ahead. The client mirrors all of it programmatically, downloading the run's metrics as a CSV for whatever analysis stack you prefer and listing the checkpoints captured throughout training:

arena experiments metrics gsm8k-grpo
arena experiments checkpoints gsm8k-grpo

Keeping every checkpoint matters more with RL than other ML training, since reward curves are not monotonic and the best model of a run is frequently not the final one. The choice of which checkpoint to ship becomes a decision you make afterwards with the data in front of you.

Deployment, which is usually where a second infrastructure project begins, is the last step the client absorbs. One more command puts the best checkpoint of the run behind a managed inference endpoint, with the option to pin a specific checkpoint when the best one by reward is not the one you want:

arena agent deploy gsm8k-grpo

From that point the model is a named deployment you can query straight from the terminal:

arena agent generate gsm8k-grpo \
  --prompt "Weng earns \$12 an hour for babysitting. Yesterday, she just did 50 minutes of babysitting. How much did she earn?"

Completions stream back token by token as they are generated, and the Python SDK exposes the same operations, along with batched prompts, when you need them programmatically.

Beyond GSM8K

The walkthrough presented in this blog covers one specific example of using RL for reasoning fine-tuning, but the client abstracts the workflow to a wide range of RL problems - from classic RL simulation environments, to preference or multi-turn fine-tuning tasks. The functionality that the client offers is meant to be place-in for any RL problem, and the insights it provides every step of the way allow users to iterate on complex problems faster than ever. We have written an analogous tutorial to train PPO on a custom air-traffic-control Gymnasium environment, uploading and validating the environment code instead of registering a dataset.

Reinforcement learning workloads are wildly heterogeneous, but their operational lifecycle is remarkably consistent: define and validate the task, define the reward, train at scale, keep what wins, and serve it. The Arena client standardises that lifecycle so the parts you spend your time on are the ones that are specific to you.

We will keep extending the capabilities of Arena and the client library in the coming months, with the idea that RL should be fast and accessible to everyone, no matter what problem you are working on. If you are training agents and fighting the infrastructure instead of the problem, get in touch. We built Arena so you do not have to.