Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Standard API for single-agent reinforcement learning environments (Gymnasium). Provides Classic Control, Box2D, Toy Text, MuJoCo, and Atari environments with a unified env.step()/env.reset() interface. For multi-agent RL, use PettingZoo. For algorithm implementations, use stable-baselines3 or CleanRL.
.claude/skills/mkurman-gymnasium/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-10 | ✗→✓ | ▲ Improved | 225% | 0% |
| case-18 | ✓→✗ | ▼ Worse | 130% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 106% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 101% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 98% | 0% |
--|------|---------| | observation | ndarray / dict | Current state observation | | reward | float | Immediate reward | | terminated | bool | Terminal state reached (success/failure) | | truncated | bool | Episode ended by time limit/external signal | | info | dict | Auxiliary diagnostic info |
Critical distinction: terminated means the MDP ended naturally. truncated means it hit a time limit. Both should trigger reset(), but your algorithm should handle them differently (no value bootstrap on terminated).
| Family | Examples | Install | Use Case | |--------|----------|---------|----------| | Classic Control | CartPole, MountainCar, Pendulum, Acrobot | pip install gymnasium | Algorithm debugging, quick tests | | Box2D | LunarLander, BipedalWalker, CarRacing | pip install "gymnasium[box2d]" | Physics-based toy problems | | Toy Text | FrozenLake, Taxi, Blackjack | pip install gymnasium | Discrete RL, teaching | | MuJoCo | HalfCheetah, Hopper, Humanoid, Ant | pip install "gymnasium[mujoco]" | Continuous control benchmarks | | Atari | Breakout, Pong, SpaceInvaders | pip install "gymnasium[atari]" (ALE) | Pixel-based RL, DQN development |
Install all:
bashpip install "gymnasium[all]"
pythonimport gymnasium as gym from gymnasium import spaces env = gym.make("CartPole-v1") print(env.observation_space) # Box([-4.8 ...], [4.8 ...], (4,), float32) print(env.action_space) # Discrete(2) # Check space properties assert isinstance(env.observation_space, spaces.Box) print(env.observation_space.shape) # (4,) print(env.observation_space.dtype) # float32 print(env.observation_space.low) # [-4.8 -inf -0.418 -inf] print(env.observation_space.high) # [4.8 inf 0.418 inf] assert isinstance(env.action_space, spaces.Discrete) print(env.action_space.n) # 2
pythonimport gymnasium as gym from gymnasium import spaces import numpy as np class CustomEnv(gym.Env): metadata = {"render_modes": ["human", "rgb_array"], "render_fps": 30} def __init__(self, render_mode=None, size=5): super().__init__() self.size = size self.observation_space = spaces.Dict({ "agent": spaces.Box(0, size - 1, shape=(2,), dtype=int), "target": spaces.Box(0, size - 1, shape=(2,), dtype=int), }) self.action_space = spaces.Discrete(4) # 0=up, 1=right, 2=down, 3=left self._action_to_direction = { 0: np.array([1, 0]), 1: np.array([0, 1]), 2: np.array([-1, 0]), 3: np.array([0, -1]), } self.render_mode = render_mode def _get_obs(self): return {"agent": self._agent_location, "target": self._target_location} def reset(self, seed=None, options=None): super().reset(seed=seed) self._agent_location = self.np_random.integers(0, self.size, size=2) self._target_location = self._agent_location.copy() while np.array_equal(self._target_location, self._agent_location): self._target_location = self.np_random.integers(0, self.size, size=2) return self._get_obs(), {} def step(self, action): direction = self._action_to_direction[action] self._agent_location = np.clip( self._agent_location + direction, 0, self.size - 1 ) terminated = np.array_equal(self._agent_location, self._target_location) reward = 1 if terminated else -0.01 return self._get_obs(), reward, terminated, False, {} def render(self): if self.render_mode == "human": grid = np.full((self.size, self.size), ".") grid[self._target_location[0], self._target_location[1]] = "T" grid[self._agent_location[0], self._agent_location[1]] = "A" print("\n".join(" ".join(row) for row in grid) + "\n") def close(self): pass
Register and use:
pythongym.register(id="CustomEnv-v0", entry_point=CustomEnv, max_episode_steps=100) env = gym.make("CustomEnv-v0")
pythonfrom gymnasium import wrappers env = gym.make("CartPole-v1") # Normalize observations (running mean/std) env = wrappers.NormalizeObservation(env) # Normalize rewards env = wrappers.NormalizeReward(env, gamma=0.99) # Clip actions to valid range env = wrappers.ClipAction(env) # Rescale actions from [-1,1] to environment bounds env = wrappers.RescaleAction(env, min_action=-1, max_action=1) # Convert to single observation (flatten dict spaces) env = wrappers.FlattenObservation(env) # Resize image observations env = wrappers.ResizeObservation(env, shape=(84, 84)) # Frame stacking (Atari-style) env = wrappers.FrameStackObservation(env, stack_size=4) # Time limit enforcement env = wrappers.TimeLimit(env, max_episode_steps=500) # Record episodes as videos env = wrappers.RecordVideo(env, "videos/", episode_trigger=lambda x: x % 100 == 0) # Transform rewards from gymnasium.wrappers import TransformReward env = TransformReward(env, lambda r: np.clip(r, -1, 1))
pythonfrom gymnasium.vector import SyncVectorEnv, AsyncVectorEnv def make_env(env_id, seed): def _init(): env = gym.make(env_id) env.reset(seed=seed) return env return _init # Synchronous (sequential) envs = SyncVectorEnv([make_env("CartPole-v1", i) for i in range(4)]) obs, _ = envs.reset() obs, rewards, terminateds, truncateds, infos = envs.step(actions) # Asynchronous (parallel processes) envs = AsyncVectorEnv([make_env("CartPole-v1", i) for i in range(8)])
Gymnasium uses semantic versioning: CartPole-v0, CartPole-v1. When the dynamics, reward function, or observation space changes, the version number increments. Always pin environment versions in your experiments for reproducibility.
pythonfrom gymnasium.utils.env_checker import check_env env = gym.make("CartPole-v1") check_env(env, warn=True) # Verifies API compliance
seed in reset() for reproducible experimentsterminated from truncated in value bootstrappingwrappers.RecordVideo for debugging and sharing resultsAsyncVectorEnv for CPU-bound environments, SyncVectorEnv for lightweight onesinfo["terminal_observation"] is available after auto-reset in vectorized envs| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 9,277 | 6,569 | -29% | 1 | 1 | 0% | 1,644 | 3,383 | +106% | 0 | 0 | — |
case-02 | pass→pass | 8,240 | 5,903 | -28% | 1 | 1 | 0% | 1,514 | 3,037 | +101% | 0 | 0 | — |
case-03 | pass→pass | 12,866 | 15,424 | +20% | 1 | 1 | 0% | 2,347 | 4,638 | +98% | 0 | 0 | — |
case-04 | pass→pass | 6,072 | 4,188 | -31% | 1 | 1 | 0% | 1,169 | 2,836 | +143% | 0 | 0 | — |
case-05 | pass→pass | 5,544 | 3,848 | -31% | 1 | 1 | 0% | 982 | 2,802 | +185% | 0 | 0 | — |
case-06 | pass→pass | 3,671 | 4,298 | +17% | 1 | 1 | 0% | 641 | 2,858 | +346% | 0 | 0 | — |
case-07 | pass→pass | 4,731 | 4,005 | -15% | 1 | 1 | 0% | 815 | 2,792 | +243% | 0 | 0 | — |
case-08 | pass→pass | 4,716 | 3,602 | -24% | 1 | 1 | 0% | 811 | 2,667 | +229% | 0 | 0 | — |
case-09 | pass→pass | 3,077 | 2,801 | -9% | 1 | 1 | 0% | 578 | 2,536 | +339% | 0 | 0 | — |
case-10 | fail→pass | 4,707 | 3,054 | -35% | 1 | 1 | 0% | 787 | 2,555 | +225% | 0 | 0 | — |
case-11 | pass→pass | 10,248 | 5,118 | -50% | 1 | 1 | 0% | 1,869 | 2,999 | +60% | 0 | 0 | — |
case-12 | pass→pass | 7,153 | 4,243 | -41% | 1 | 1 | 0% | 1,386 | 2,809 | +103% | 0 | 0 | — |
case-13 | pass→pass | 8,136 | 4,750 | -42% | 1 | 1 | 0% | 1,479 | 2,887 | +95% | 0 | 0 | — |
case-14 | pass→pass | 7,335 | 4,869 | -34% | 1 | 1 | 0% | 1,353 | 2,980 | +120% | 0 | 0 | — |
case-15 | pass→pass | 4,423 | 3,605 | -18% | 1 | 1 | 0% | 761 | 2,670 | +251% | 0 | 0 | — |
case-16 | pass→pass | 4,989 | 4,249 | -15% | 1 | 1 | 0% | 822 | 2,788 | +239% | 0 | 0 | — |
case-17 | pass→pass | 8,213 | 6,378 | -22% | 1 | 1 | 0% | 1,407 | 3,223 | +129% | 0 | 0 | — |
case-18 | pass→fail | 9,513 | 10,342 | +9% | 1 | 1 | 0% | 1,737 | 3,997 | +130% | 0 | 0 | — |
case-19 | pass→pass | 11,966 | 11,287 | -6% | 1 | 1 | 0% | 1,938 | 3,907 | +102% | 0 | 0 | — |
case-20 | pass→pass | 13,213 | 9,498 | -28% | 1 | 1 | 0% | 2,538 | 3,863 | +52% | 0 | 0 | — |
case-21 | pass→pass | 5,532 | 4,264 | -23% | 1 | 1 | 0% | 939 | 2,849 | +203% | 0 | 0 | — |
case-22 | pass→pass | 3,051 | 4,281 | +40% | 1 | 1 | 0% | 552 | 2,827 | +412% | 0 | 0 | — |
case-23 | pass→pass | 3,288 | 3,071 | -7% | 1 | 1 | 0% | 510 | 2,630 | +416% | 0 | 0 | — |
case-24 | pass→pass | 3,582 | 3,472 | -3% | 1 | 1 | 0% | 598 | 2,581 | +332% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of -100 percentage points is the difference between those two pass rates over the 24 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.