> ## Documentation Index
> Fetch the complete documentation index at: https://starforge.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Train against an agent environment

> Point a job at a task suite and let the model practise, with a verifier deciding the reward.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf env ls
sf submit my-agent --profile h200:8 --environment alice/calculator-tasks@1.0.0
```

The platform resolves the version, checks you are allowed to use it, and delivers it into the job.
Your training code never holds a path or a URL it chose itself.

<Info>
  This page is about *using* an environment. To write one — the manifest, the tasks, the four
  protocols — see [authoring environments](/en/extend/environments).
</Info>

## What the job receives

Three environment variables, set by the control plane:

| Variable                      | What it is                                                    |
| ----------------------------- | ------------------------------------------------------------- |
| `STARFORGE_ENVIRONMENT_DIR`   | Where the environment is mounted, for a materialised protocol |
| `STARFORGE_ENVIRONMENT_URL`   | Where it answers, for a served or remote protocol             |
| `STARFORGE_ENVIRONMENT_SPLIT` | Which half of the taskset this job may see                    |

<Warning>
  Do not hard-code any of these in an experiment. The path is the platform's to give, and an
  experiment that names one directly bypasses the access check that resolved it.
</Warning>

## The split is not yours to choose

`STARFORGE_ENVIRONMENT_SPLIT` is set from the **operation**: a training job gets `train`, an
evaluation gets `eval`. There is no flag to override it.

That is the whole safety property, and it is worth being blunt about why it is a state rather than a
setting. Held-out data stops being held out the moment choosing it is a convenience. The run that
scores best becomes the one that evaluated on its own training tasks — and nothing downstream can
tell that it did. The number looks excellent, the model is not, and the mistake is invisible in
every artifact the run produced.

Making the operation decide means the leak is not a setting somebody can get wrong. It is a state
the system cannot reach.

An evaluation asking for a split the environment never declared is refused, rather than quietly
served the training tasks.

## The three routes, from a trainer's point of view

<Tabs>
  <Tab title="Served (openenv)">
    The platform runs the environment server. Your trainer drives the episode through openenv-core's
    client — `reset`, `step`, `state` — against `STARFORGE_ENVIRONMENT_URL`.

    TRL, SkyRL, Unsloth and Axolotl consume this natively. Nothing extra to write.
  </Tab>

  <Tab title="Materialised (nemo-gym)">
    The platform writes the environment into the job as the files NeMo Gym expects, at
    `STARFORGE_ENVIRONMENT_DIR`, and Gym launches its own servers from them.

    The environment must declare a harness plugin, because somebody has to run the trajectory loop
    and the platform does not write it.
  </Tab>

  <Tab title="Remote (openenv-remote)">
    Somebody already runs the service. The platform decides whether your job may reach that host and
    hands you the URL. It never proxies the traffic.
  </Tab>
</Tabs>

## Rewards come from the verifier

You do not write a reward function for the environment — the environment declares what decides
completion, and it is always a reference:

| Verifier kind | Who decides                                                                        |
| ------------- | ---------------------------------------------------------------------------------- |
| `rubric`      | The platform judge, scoring against a [rubric](/en/guides/rubrics) your team wrote |
| `plugin`      | An entrypoint in the environment's own plugin                                      |
| `endpoint`    | A service the environment declares                                                 |

A verifier that cannot answer **raises**; it does not report zero. See
[verifiers](/en/extend/verifiers) for why that distinction matters more than it sounds.

## Sandboxes

An environment whose harness runs model-generated code declares `sandbox.required`. On a deployment
with no sandbox provider configured, such a job is **refused at admission** — it does not fall back
to running the code in the training container.

If you need one and get refused, an administrator sets `FORGE_SANDBOX_IMAGE`. There is no default,
deliberately: running model-generated code is not something to enable by accident.

## Confirm it worked

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf job logs
```

The launcher logs the environment it resolved and the route it took:

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
environment: alice/calculator-tasks@1.0.0 (openenv) served at http://127.0.0.1:8931
environment: split=train
```

If the reward stays flat at exactly one value for every rollout, suspect the verifier before the
model — a plugin verifier returning a constant looks identical to a task the model cannot do.

## Next

<Columns cols={2}>
  <Card title="Author an environment" icon="joystick" href="/en/extend/environments" arrow="true">
    The manifest, the taskset, and the four protocols.
  </Card>

  <Card title="Rubrics" icon="ruler" href="/en/guides/rubrics" arrow="true">
    The standard a rubric verifier scores against.
  </Card>
</Columns>
