Skip to content

The environment layer

The problem. Everything else in this package needs an environment, but almost nothing about it. The reward system needs a per-step record; the configuration service needs steps labelled; the harness needs to step something and read what came back; the metrics need none of it, because they read a recorded trace. If each had its own idea of what an environment is, adding a second one would mean satisfying four contracts instead of one — and the whole claim of this layer is that a simulator it was never written for runs through it unchanged.

The shape here. One contract in two tiers, and the machinery that is the same whichever environment you brought. Tier 1 is gymnasium.Env and nothing more — it supplies its own reward, and gets configuration scheduling, tracing, the metrics, the harness and export; nothing is subclassed, nothing is registered. Tier 2 adds description: a per-step record in info["trace"], its own labels in info["regime"], and describe() — and gains the composable reward system. The record is optional on purpose: requiring one would exclude most existing benchmarks, which only produce a scalar; requiring none would reduce the reward system to shaping a number somebody else computed.

What you implement: usually nothing — a factory returning your gymnasium.Env, selected by dotted path. L2 builds one in a page; L3 instruments it to Tier 2; skyfall-crl conformance --env checks either tier.

Driving several worlds at once

PersistentVectorEnv steps N independent environments together and never resets one when it reports done. Gymnasium's own vector environments auto-reset, which is right when a reset is cheap and wrong when it is not: for a world that must be torn down and rebuilt, an automatic reset throws away the run — and the financial position its reward is computed from.

from skyfall_crl.env import PersistentVectorEnv

envs = PersistentVectorEnv([make_env for _ in range(4)])   # factories, one world each
observations, infos = envs.reset(seed=1)
observations, rewards, terminated, truncated, infos = envs.step(actions)

Truncation is reported per environment and resetting is left to the caller. It drives any gymnasium.Env, so it serves a task-sequence benchmark as well as a live deployment.

The configuration service is re-exported here (ConstantRegime, RegimeProvider) because the environment is where a run reaches for it — but it is shared with the harness and the metrics rather than owned here: Configuration shifts.

Adapters

An adapter for a particular substrate is a subpackage of this layer. The MORPHEUS adapter is the one that ships, at skyfall_crl.env.morpheus. An adapter does not have to live in this package at all, and usually should not: an environment is selected by dotted path in a configuration document, so yours lives in your own repository and runs through everything here — which is exactly how AI2's DiscoveryWorld is driven in Adoption cost, with no edit to any module in this package.

Limits

The layer names no substrate, and its guarantees are structural, not semantic: it cannot know whether your reward is informative (L1 shows what an uninformative one measures as), whether your labels match what actually shifted, or whether an episode should have ended. Those are the environment author's calls; the conformance checker verifies the contract, not the science.


API: every public symbol, with signatures — The environment layer — API reference.