Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Multi-agent reinforcement learning environment API (PettingZoo). Standard API for multi-agent RL extending Gymnasium with Agent Environment Cycle (AEC) and Parallel APIs. Includes Atari, Butterfly, Classic, MPE, and SISL environments. For single-agent RL, use Gymnasium. For algorithm implementations, use stable-baselines3 or CleanRL.
.claude/skills/mkurman-pettingzoo/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 105% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 164% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 120% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 334% | 0% |
-----|-----|----------| | Turn-based games (card, board games) | ✅ Best fit | ❌ Not appropriate | | Simultaneous action (robotics, MPE) | ⚠️ Works but awkward | ✅ Best fit | | Compatible with CleanRL | ✅ Via wrappers | ❌ Needs conversion | | Compatible with SB3 | ❌ Not directly | ❌ Needs conversion |
python# Iterate over agents in turn order for agent in env.agent_iter(): # Get the observation/reward for the CURRENT agent observation, reward, termination, truncation, info = env.last() # Check if the agent is done if termination or truncation: action = None else: action = policy(observation, agent) # Submit action — this steps the environment AND advances to next agent env.step(action) # After loop: check which agents are still active print(env.agents) # List of active agents
| Category | Environments | API Style | Description | |----------|-------------|-----------|-------------| | MPE | simple_spread, simple_adversary, simple_tag, simple_world_comm | Parallel | Multi-agent particle environments, cooperative/competitive | | Atari | pong, space_invaders, surround, tennis, warlords | Parallel | Multi-agent versions of classic Atari games | | Butterfly | pistonball, cooperative_pong, knights_archers_zombies | Parallel | Cooperative multi-agent games | | Classic | chess, go, rps, backgammon, texas_holdem, tictactoe | AEC | Classic board and card games | | SISL | waterworld, pursuit | Parallel | Multi-agent control tasks |
List all available:
pythonfrom pettingzoo.utils import all_modules print(all_modules)
pythonfrom pettingzoo.utils import wrappers # AEC → Parallel conversion from pettingzoo.utils.conversions import aec_to_parallel parallel_env = aec_to_parallel(aec_env) # Parallel → AEC conversion from pettingzoo.utils.conversions import parallel_to_aec aec_env = parallel_to_aec(parallel_env) # Pad observations for different-sized agents env = wrappers.PadObservations(env) # Flatten dict observations env = wrappers.FlattenObservations(env)
pythonfrom pettingzoo.mpe import simple_spread_v3 env = simple_spread_v3.parallel_env( N=3, # Number of agents local_ratio=0.5, # How much agents see max_cycles=100, render_mode="human", ) observations, infos = env.reset(seed=42) for cycle in range(100): actions = {} for agent in env.agents: # observations[agent] is the local observation for that agent actions[agent] = env.action_space(agent).sample() observations, rewards, terminations, truncations, infos = env.step(actions) if all(terminations.values()) or all(truncations.values()): break env.close()
pythonfrom pettingzoo.mpe import simple_spread_v3 env = simple_spread_v3.env(N=3) # Per-agent spaces for agent in env.possible_agents: print(f"{agent} obs: {env.observation_space(agent)}") print(f"{agent} act: {env.action_space(agent)}") # Agent-specific policies policies = { "agent_0": policy_0, "agent_1": policy_1, "agent_2": policy_2, }
pythonfrom pettingzoo.atari import pong_v3 env = pong_v3.parallel_env(render_mode="human") observations, infos = env.reset() # Two agents: "first_0" and "second_0" # Each sees the game from their perspective for agent in env.agents: print(env.observation_space(agent)) # Box(210, 160, 3) print(env.action_space(agent)) # Discrete(6)
CleanRL has built-in support for multi-agent PettingZoo Atari:
python# See: cleanrl/ppo_pettingzoo_ma_atari.py from cleanrl.ppo_pettingzoo_ma_atari import make_env envs = make_env("pong_v3", seed=1)
pythonfrom pettingzoo import ParallelEnv import functools import gymnasium as gym from gymnasium import spaces import numpy as np class CustomMARLEnv(ParallelEnv): metadata = {"name": "custom_marl_v0"} def __init__(self, render_mode=None): super().__init__() self.possible_agents = ["agent_0", "agent_1"] self.observation_spaces = { a: spaces.Box(low=0, high=1, shape=(4,), dtype=np.float32) for a in self.possible_agents } self.action_spaces = { a: spaces.Discrete(3) for a in self.possible_agents } self.render_mode = render_mode def reset(self, seed=None, options=None): self.agents = self.possible_agents[:] self.state = np.zeros(4, dtype=np.float32) observations = {a: self.state.copy() for a in self.agents} infos = {a: {} for a in self.agents} return observations, infos def step(self, actions): # Apply actions, update state for agent, action in actions.items(): self.state[0] += (action - 1) * 0.1 self.state = np.clip(self.state, 0, 1) rewards = {a: float(self.state[0]) for a in self.agents} terminations = {a: False for a in self.agents} truncations = {a: False for a in self.agents} observations = {a: self.state.copy() for a in self.agents} infos = {a: {} for a in self.agents} # Remove dead agents if self.state[0] > 0.9: self.agents = [] return observations, rewards, terminations, truncations, infos def render(self): if self.render_mode == "human": print(f"State: {self.state}") def close(self): pass
bashpip install supersuit
pythonfrom pettingzoo.atari import space_invaders_v2 from supersuit import ( resize_v1, frame_skip_v0, frame_stack_v1, color_reduction_v0, dtype_v0, pettingzoo_env_to_vec_env_v1, ) env = space_invaders_v2.parallel_env() env = resize_v1(env, (84, 84)) env = frame_skip_v0(env, 4) env = frame_stack_v1(env, 4) # Convert to Gymnasium VecEnv for SB3/CleanRL compat env = pettingzoo_env_to_vec_env_v1(env)
env.agents — it changes as agents are added/removedenv.observation_space(agent) and env.action_space(agent) — they can differ per agentgymnasium.spaces| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 13,791 | 14,021 | +2% | 1 | 1 | 0% | 2,166 | 4,450 | +105% | 0 | 0 | — |
case-02 | pass→pass | 7,353 | 7,623 | +4% | 1 | 1 | 0% | 1,238 | 3,269 | +164% | 0 | 0 | — |
case-03 | pass→pass | 9,332 | 7,886 | -15% | 1 | 1 | 0% | 1,578 | 3,465 | +120% | 0 | 0 | — |
case-04 | pass→pass | 3,454 | 2,154 | -38% | 1 | 1 | 0% | 560 | 2,430 | +334% | 0 | 0 | — |
case-05 | pass→pass | 6,931 | 4,709 | -32% | 1 | 1 | 0% | 1,155 | 2,933 | +154% | 0 | 0 | — |
case-06 | pass→pass | 6,507 | 4,522 | -31% | 1 | 1 | 0% | 1,139 | 2,882 | +153% | 0 | 0 | — |
case-07 | pass→pass | 3,889 | 2,868 | -26% | 1 | 1 | 0% | 736 | 2,645 | +259% | 0 | 0 | — |
case-08 | pass→pass | 4,974 | 3,092 | -38% | 1 | 1 | 0% | 899 | 2,639 | +194% | 0 | 0 | — |
case-09 | fail→pass | 10,819 | 4,133 | -62% | 1 | 1 | 0% | 1,828 | 2,761 | +51% | 0 | 0 | — |
case-10 | pass→pass | 7,083 | 3,080 | -57% | 1 | 1 | 0% | 1,310 | 2,618 | +100% | 0 | 0 | — |
case-11 | pass→pass | 5,629 | 4,170 | -26% | 1 | 1 | 0% | 1,006 | 2,923 | +191% | 0 | 0 | — |
case-12 | pass→pass | 6,423 | 4,924 | -23% | 1 | 1 | 0% | 1,133 | 2,976 | +163% | 0 | 0 | — |
case-17 | pass→pass | 2,574 | 3,212 | +25% | 1 | 1 | 0% | 421 | 2,626 | +524% | 0 | 0 | — |
case-13 | pass→pass | 6,508 | 6,086 | -6% | 1 | 1 | 0% | 1,094 | 3,226 | +195% | 0 | 0 | — |
case-14 | fail→fail | 4,180 | 4,623 | +11% | 1 | 1 | 0% | 760 | 2,828 | +272% | 0 | 0 | — |
case-15 | pass→pass | 4,061 | 4,028 | -1% | 1 | 1 | 0% | 610 | 2,807 | +360% | 0 | 0 | — |
case-16 | pass→pass | 4,930 | 4,751 | -4% | 1 | 1 | 0% | 806 | 2,889 | +258% | 0 | 0 | — |
case-18 | pass→pass | 5,697 | 3,332 | -42% | 1 | 1 | 0% | 1,031 | 2,662 | +158% | 0 | 0 | — |
case-19 | pass→pass | 5,232 | 3,872 | -26% | 1 | 1 | 0% | 894 | 2,767 | +210% | 0 | 0 | — |
case-20 | pass→pass | 6,886 | 5,367 | -22% | 1 | 1 | 0% | 1,312 | 3,112 | +137% | 0 | 0 | — |
case-21 | pass→pass | 5,755 | 4,948 | -14% | 1 | 1 | 0% | 1,072 | 2,974 | +177% | 0 | 0 | — |
case-22 | pass→pass | 8,334 | 7,581 | -9% | 1 | 1 | 0% | 1,585 | 3,607 | +128% | 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. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
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.