skills/pufferlib/references/environments.md
Research snapshot: 2026-07-23.
A current single-agent Gymnasium environment defines observation_space and
action_space, then implements:
def reset(self, *, seed=None, options=None):
super().reset(seed=seed)
return observation, info
def step(self, action):
return observation, reward, terminated, truncated, info
Contract requirements:
observation must be contained in observation_space after reset and every
step, with the documented shape and dtype.action must be contained in action_space.reward is a finite scalar for ordinary single-agent tasks.terminated means the task's MDP reached a terminal state.truncated means an external limit ended the episode, commonly a time limit.info is a dictionary; never hide the only termination signal in it.reset() after either terminated or truncated.reset(seed=...). Seed the action space
separately when sampled actions must be reproducible.close().Do not collapse terminated and truncated during learning. A time-limit
truncation can still permit value bootstrapping; a true terminal state does not.
Run the local contract tool before involving PufferLib:
python3 scripts/env_contract_validator.py
It validates only the bundled synthetic environment. It intentionally has no module-path option, so it cannot dynamically import an untrusted package.
For a native Python PufferEnv, assign these attributes before calling
super().__init__(buf):
import gymnasium
import numpy as np
import pufferlib
class ReviewedEnv(pufferlib.PufferEnv):
def __init__(self, buf=None, seed=0):
self.single_observation_space = gymnasium.spaces.Box(
low=-1.0, high=1.0, shape=(4,), dtype=np.float32
)
self.single_action_space = gymnasium.spaces.Discrete(3)
self.num_agents = 2
super().__init__(buf)
The stable base accepts a Box observation space and Discrete, MultiDiscrete, or Box action space. It allocates or attaches:
observationsactionsrewardsterminalstruncationsmasksNative methods operate on those buffers:
def reset(self, seed=None):
# update self.observations in place
return self.observations, []
def step(self, actions):
# update all buffers in place
return (
self.observations,
self.rewards,
self.terminals,
self.truncations,
[],
)
The infos value for native Puffer environments is a list of dictionaries.
PufferLib's native interface expects vector rows for agents, even when there is
one agent. Native environments handle their own resets; clear rewards,
terminals, truncations, masks, and partially written observations explicitly.
Never leave a previous step's buffer values in place.
For A = num_agents and single observation shape S:
(A, *S)(A,)(A,)(A,)(A,)AValidate the exact allocated action shape rather than assuming (A,), especially
for MultiDiscrete and Box actions.
PufferLib 3.0 uses explicit adapters:
import pufferlib.emulation
wrapped = pufferlib.emulation.GymnasiumPufferEnv(reviewed_gymnasium_instance)
or:
wrapped = pufferlib.emulation.PettingZooPufferEnv(reviewed_parallel_instance)
There is no supported 3.0 pufferlib.emulate(...) convenience function matching
the old skill examples. Pass either an env instance or an env_creator
callable according to the class signature; do not pass both.
The Gymnasium adapter:
The PettingZoo adapter:
possible_agents as the fixed slot set;Validate heterogeneous-agent spaces before use. The adapter derives its single spaces from the first possible agent, so environments with incompatible spaces need an explicit reviewed transformation.
Stable emulation supports Box, Discrete, MultiDiscrete, Tuple, and Dict patterns through a packed NumPy dtype. This is byte-layout conversion, not semantic feature engineering. Check:
The 4.0 default branch focuses on first-party C environments. It no longer provides the 3.0 Python emulation/vector modules. The official starting points are:
ocean/squared: commented single-agent templateocean/target: commented multi-agent templateA binding defines compile-time metadata such as:
#define OBS_SIZE 121
#define NUM_ATNS 1
#define ACT_SIZES {5}
#define OBS_TENSOR_T ByteTensor
#define Env Squared
#include "vecenv.h"
The environment struct must include pointers for observations, actions,
rewards, and terminals, plus num_agents and a log struct. It implements
c_reset, c_step, c_render, and c_close; binding.c supplies my_init
and my_log.
Security and correctness rules:
OBS_SIZE, tensor dtype, action branch count/sizes, and actual writes.c_step may reset immediately after marking a terminal. Record this autoreset
behavior when interpreting terminal observations.
An environment package may execute arbitrary Python/native code and may fetch assets at import, build, reset, or render time. Before execution:
An entry in Ocean/config is not a blanket security, quality, or licensing approval.