Minecraft Survival Gym
A Gymnasium environment for training reinforcement learning agents to survive in Minecraft Java, with visual observations, full player controls, and keyboard/mouse demonstration recording.
Minecraft Survival Gym connects Python policies to Minecraft Java 1.21 through a Fabric client mod and exposes the game as minecraft_gym/MinecraftSurvival-v0. It is designed as an experimental foundation for reinforcement learning, imitation learning, DAgger, and future VLM/LLM-driven objectives and rewards.
Features
- Gymnasium-compatible
reset(),step(),render(), andclose()API; - RGB observations combined with structured player state;
- movement, camera, combat, item use, hotbar, and GUI actions;
- controlled simulation time with an exact number of ticks per action;
- agent, human, and DAgger control modes;
- atomic trajectory recording for behavior cloning and imitation learning;
- deterministic mock backend for development without Minecraft;
- pluggable reward functions decoupled from the game loop.
Architecture
RL policy / Human operator
│
▼
MinecraftSurvivalEnv
(Gymnasium)
│ Local TCP / JSON
▼
Fabric bridge mod
│
▼
Minecraft Java — Survival
│
└── RGB + vitals + pose + inventory + events
The bridge listens only on 127.0.0.1:25570. Requests are processed sequentially to preserve the relationship between each action, the executed game ticks, and the returned observation.
Project status
The environment has been validated end to end against a real Minecraft client:
- Python-to-Fabric handshake;
128 × 128 × 3RGB capture;- exact four-tick advancement per action;
- player movement and camera rotation;
- DAgger execution;
- human episode recording;
- Gymnasium environment checker compliance;
- automated Python test suite.
Requirements
- Python 3.10 or newer;
- Minecraft Java Edition 1.21;
- Fabric Loader;
- Fabric API compatible with Minecraft 1.21.
uv is required only for development from a
source checkout. It is not required when installing the published Python
package with pip. The published mod runs with the Java 21 runtime selected by
the Minecraft Launcher; a full JDK 21 is required only to build or run the
Fabric development client from source.
Installation
Choose one route. The published package opens your regular Minecraft
Launcher; a source checkout runs a separate Fabric development client. Do not
run minecraft-gym start and ./gradlew runClient as consecutive setup steps.
| Route | Use it when | Minecraft command | Where worlds are saved |
|---|---|---|---|
| Published release | You want to use the installed Python package | minecraft-gym setup, then minecraft-gym start (version 0.3.1+) |
Your Launcher installation's game directory |
| Source checkout | You are developing this repository | cd fabric && ./gradlew runClient |
fabric/run/saves/ |
Published release (regular Launcher)
One-time setup command (version 0.3.1 and later)
The normal Launcher flow is:
python -m pip install --upgrade minecraft-gym==0.3.1
minecraft-gym setup
minecraft-gym start
minecraft-gym setup installs Fabric Loader for Minecraft 1.21, the matching
Fabric API, and the bridge mod into the official Minecraft Launcher's game
directory. It verifies the downloaded files, leaves existing worlds untouched,
and refuses conflicting mod versions rather than deleting them. Install and
open the official Launcher once before running it. Java 21 must be available
for the one-time Fabric Loader installation. You can preview the target with
minecraft-gym setup --dry-run or choose a nondefault Launcher directory with
minecraft-gym setup --game-dir /path/to/minecraft.
Running setup again upgrades the project's own bridge mod when a supported
older release is present. The previous .jar is preserved outside mods in
the .minecraft-gym-backup directory.
The command cannot create a Minecraft account or click through the Launcher:
after minecraft-gym start, select the Minecraft 1.21 Fabric profile,
click Play, create or enter a single-player Survival world, wait for the
terrain and HUD, then press Enter in the terminal and choose recording or an
agent. No repository checkout or Gradle command is needed.
Earlier PyPI release (version 0.2.0)
-
Install Minecraft Java Edition 1.21 and Fabric Loader for 1.21. The installer must create a Fabric profile in the Minecraft Launcher.
-
Download Fabric API for Minecraft 1.21 and the
minecraft-gym-bridge-0.1.0.jarrelease asset. Put both.jarfiles in that installation'smodsdirectory. The default macOS location is~/Library/Application Support/minecraft/mods/; a custom Launcher game directory has its ownmodsfolder. -
Install the Python package in the environment where you will run your agent:
pip install minecraft-gym==0.2.0
-
Start the interactive helper in a terminal:
minecraft-gym startOn macOS it opens the Minecraft Launcher. Select the 1.21 Fabric profile, click Play, create or open a single-player Survival world, and wait until terrain and the player HUD are visible. The title screen is not enough. Return to the terminal and press Enter when prompted. Choose 1 to record keyboard/mouse actions or 2 to run an agent script. Keep Minecraft focused while recording.
To select a mode without the final menu, use:
minecraft-gym start record --output datasets/demonstrations --steps 9000
minecraft-gym start agent path/to/agent.py
Run one of these commands per session, not both. The agent script must
create and close its own Gymnasium environment; start agent does not supply
a built-in policy. On Linux, start uses minecraft-launcher if it is on
PATH; otherwise pass --launcher. If Minecraft is already open, add
--no-launch after start. The Python package does not install Minecraft,
Fabric Loader, Fabric API, or the bridge mod.
Recording uses reset() before its first step. This soft reset clears the
player inventory, restores vitals, time, and weather, and moves the player to
the session anchor. It does not rebuild modified blocks.
To check the connection without creating a file:
python -c 'import gymnasium as gym, minecraft_gym; env = gym.make("minecraft_gym/MinecraftSurvival-v0"); observation, info = env.reset(seed=42); print(observation["rgb"].shape, info["tick"]); env.close()'
This command only checks a running world with the bridge mod loaded; it does
not launch Minecraft.
To stop a start session, press Ctrl+C and close Minecraft normally.
The repository's stop_dev.sh command manages only processes started by the
source-development launcher.
Development installation from source
1. Clone the repository and install Python dependencies
git clone https://github.com/rlsgarcia-code/Minecraft-Survival-Gym.git
cd Minecraft-Survival-Gym
uv sync --extra dev
Verify the Gymnasium API without launching Minecraft:
uv run python scripts/smoke_env.py --backend mock --steps 20
2. Select JDK 21
Minecraft 1.21 development requires JDK 21. On macOS, fabric/gradlew now selects an installed JDK 21 for that command even if the shell's JAVA_HOME points to an older JDK. It checks the macOS Java registry and Homebrew's openjdk@21. Verify the JVM used by Gradle with cd fabric && ./gradlew --version; the JVM line should report 21.
If JDK 21 is not installed, install it with Homebrew:
brew install openjdk@21
export JAVA_HOME="$(brew --prefix openjdk@21)/libexec/openjdk.jdk/Contents/Home"
export PATH="$JAVA_HOME/bin:$PATH"
java -version
The export lines are optional for direct Gradle commands on macOS, but are useful when other Java commands also need JDK 21. The wrapper does not modify ~/.zshrc.
3. Run the development client
From the repository root, start the development client in Terminal 1:
cd fabric
./gradlew runClient
runClient builds and loads the bridge mod automatically. You do not need
to install the published bridge .jar into this client's mods folder, and
you do not need to run minecraft-gym start for this route. When Minecraft
opens, create or select a single-player Survival world and wait until the
terrain and player HUD are visible. Keep this terminal and Minecraft open.
In Terminal 2, from the repository root, verify the real connection:
uv run python scripts/smoke_env.py --steps 20
The development world's save directory is fabric/run/saves/. If you also
want a standalone mod .jar, build it separately:
cd fabric
./gradlew build
The mod artifact is written to:
fabric/build/libs/minecraft-gym-bridge-0.1.0.jar
Running the real environment
The real backend always requires a running Minecraft window with a loaded single-player world. The number of terminals depends on the installation:
| Installation | Start Minecraft | Start the Python environment |
|---|---|---|
Published release (pip) |
minecraft-gym start opens the launcher; select Fabric and click Play |
start record captures input; start agent path/to/agent.py runs your script |
| Source checkout | Terminal 1: cd fabric && ./gradlew runClient |
Terminal 2: agent, smoke test, or notebook |
Starting Minecraft is not enough: you must enter a single-player world and wait until the player HUD and terrain are visible before calling env.reset().
One-command development launcher
The helper script starts the Minecraft Fabric development client and JupyterLab
with the test notebook in the background.
It is available only in a cloned source checkout and is not installed by
pip.
./scripts/run_dev.sh
The script detects a Homebrew JDK 21 installation automatically, stores PID
files under .minecraft-gym/, opens JupyterLab, and writes service logs to:
.minecraft-gym/logs/minecraft.log
.minecraft-gym/logs/jupyter.log
After it starts, enter a single-player Survival world in the Minecraft window. The script cannot select a world on your behalf unless a development save name is explicitly provided:
MINECRAFT_GYM_QUICK_PLAY_WORLD=MyWorld ./scripts/run_dev.sh
MyWorld must already exist under fabric/run/saves/. To inspect what would
be launched without starting processes:
./scripts/run_dev.sh --dry-run
Stop both Minecraft and JupyterLab with:
./scripts/stop_dev.sh
The stop script terminates only the process trees recorded by the launcher and preserves the logs for debugging.
Option A — development client
This is the quickest way to run the project during development.
Terminal 1: start Minecraft
From the repository root (after installing JDK 21 as described above):
cd fabric
./gradlew runClient
Leave Terminal 1 running. A separate Minecraft window will open.
Minecraft window: open a world
- Click Singleplayer.
- Create or open a world.
- Make sure the player is in Survival mode.
- Wait until the terrain and player HUD are fully visible.
- Leave Minecraft open inside the world.
The title screen does not satisfy this requirement. If Python connects while Minecraft is still at the title screen, the bridge returns:
Open a single-player world before using the bridge
Terminal 2: run the Gym environment
Open a new terminal and return to the repository root:
cd /path/to/Minecraft-Survival-Gym
uv run python scripts/smoke_env.py --steps 20
Do not close Terminal 1 or the Minecraft window while this command is running.
A successful run starts with output similar to:
reset tick=... seed=... rgb=(128, 128, 3)
step=1 tick=... reward=... terminated=False truncated=False
Option B — regular Minecraft installation
- Install Fabric Loader for Minecraft 1.21.
- Place Fabric API in that installation's
modsdirectory. - Copy
fabric/build/libs/minecraft-gym-bridge-0.1.0.jarinto the samemodsdirectory. - Launch Minecraft with the Fabric profile.
- Enter a single-player Survival world and wait for it to finish loading.
- Run the Python command from Terminal 2 as shown above.
Test notebook
The repository includes a guided notebook that validates observation shapes, renders an RGB frame, executes a controlled action, checks tick advancement, and optionally connects to the real Fabric bridge:
output/jupyter-notebook/minecraft-survival-gym-quickstart.ipynb
Launch it from the repository root:
uv run --extra notebook jupyter lab \
output/jupyter-notebook/minecraft-survival-gym-quickstart.ipynb
The notebook uses the mock backend by default, so Run All is safe without
Minecraft. For the real test, keep Terminal 1 running ./gradlew runClient,
enter a loaded single-player world, and then set REAL_BACKEND = True in the
notebook.
Basic usage
import gymnasium as gym
import minecraft_gym # registers the environment
env = gym.make(
"minecraft_gym/MinecraftSurvival-v0",
backend="socket",
width=128,
height=128,
frame_skip=4,
max_episode_steps=9_000,
control_mode="agent",
render_mode="rgb_array",
)
observation, info = env.reset(seed=42)
terminated = False
truncated = False
while not (terminated or truncated):
action = env.action_space.sample()
observation, reward, terminated, truncated, info = env.step(action)
env.close()
Use backend="mock" for fast and deterministic tests that do not require Minecraft.
MinecraftSurvival-v0 remains available as a compatibility alias. New code
should use the namespaced ID to avoid collisions with other third-party
environment packages.
Action space
The action space is a 15-component gymnasium.spaces.MultiDiscrete:
MultiDiscrete([3, 3, 2, 2, 2, 2, 2, 2, 2, 5, 5, 10, 5, 5, 3])
The value 0 is NOOP for every component.
| Index | Control | Values |
|---|---|---|
| 0 | Strafe | idle, left, right |
| 1 | Movement | idle, forward, backward |
| 2 | Jump | no, yes |
| 3 | Sprint | no, yes |
| 4 | Sneak | no, yes |
| 5 | Attack | no, yes |
| 6 | Use | no, yes |
| 7 | Inventory | keep, toggle |
| 8 | Drop item | no, yes |
| 9 | Horizontal camera | neutral, ±6°, ±18° |
| 10 | Vertical camera | neutral, ±6°, ±18° |
| 11 | Hotbar | keep or select slots 1–9 |
| 12 | Horizontal GUI cursor | neutral, slow, or fast in either direction |
| 13 | Vertical GUI cursor | neutral, slow, or fast in either direction |
| 14 | GUI click | none, primary, secondary |
The canonical ControlState retains continuous mouse deltas. Human demonstrations therefore preserve more information than the discrete projection used by the initial policy space.
Observation space
The environment returns a gymnasium.spaces.Dict:
| Field | Type and shape | Contents |
|---|---|---|
rgb |
uint8[H, W, 3] |
rendered RGB frame |
vitals |
float32[8] |
health, hunger, armor, air, XP, and body flags |
pose |
float32[10] |
position, velocity, camera, ground contact, and fall state |
inventory_ids |
int32[36] |
item registry identifiers |
inventory_counts |
int32[36] |
item count for each slot |
equipped_slot |
Discrete(9) |
selected hotbar slot |
ui_mode |
Discrete(8) |
gameplay, inventory, container, chat, death, or other screen |
The info dictionary includes:
- current tick and actual world seed;
- events such as damage, death, and inventory increases;
- individual reward terms;
policy_action,human_action, andexecuted_action;control_source, indicating whether the agent or a human controlled the step.
Control modes
| Mode | Behavior |
|---|---|
agent |
executes only the action supplied by the policy |
human |
ignores the policy action and records keyboard/mouse input |
dagger |
uses the policy by default and accepts human intervention |
Recording demonstrations
Keep Minecraft focused and inside a loaded world. In Terminal 2, run:
uv run python scripts/record_human.py \
--mode human \
--steps 9000 \
--output datasets/demonstrations
To collect human corrections during policy execution:
uv run python scripts/record_human.py \
--mode dagger \
--steps 9000 \
--output datasets/dagger
Each episode produces a compressed .npz archive and a .json metadata file. Episodes are committed atomically to reduce the chance of partially written datasets.
Main arrays:
| Field | Purpose |
|---|---|
actions |
action originally submitted to the environment |
policy_actions |
policy action projected onto the discrete action space |
human_actions |
human action projected onto the discrete action space |
executed_actions |
action actually executed by the game |
policy_controls |
continuous canonical policy controls |
human_controls |
keyboard state and continuous mouse deltas |
executed_controls |
canonical controls actually applied |
human_action_present |
mask indicating human action availability/intervention |
Rewards
The default reward is deliberately conservative:
- a small reward for remaining alive;
- a penalty proportional to health lost;
- a larger penalty for death.
A different strategy can be injected without modifying the environment:
from minecraft_gym import MinecraftSurvivalEnv
from minecraft_gym.rewards import RewardResult
class MyReward:
def __call__(self, previous, transition):
collected = sum(
event.get("count", 0)
for event in transition.events
if event.get("type") == "inventory_increased"
)
return RewardResult(
total=float(collected),
terms={"collected_items": float(collected)},
)
env = MinecraftSurvivalEnv(reward_function=MyReward())
This separation makes it possible to experiment with VLM/LLM-generated semantic rewards, intermediate objectives, and knowledge retrieved from survival guides without coupling those experiments to the Minecraft bridge.
Time and episode semantics
During a Gym session, the bridge freezes the integrated server and advances exactly frame_skip ticks for each step() call.
terminated=True: the player died;truncated=True: an operational limit such asmax_episode_stepswas reached.
Reset behavior
The current version implements a soft reset. It:
- forces Survival mode;
- returns the player to the session's initial position;
- clears inventory and status effects;
- restores health, hunger, and saturation;
- restores time and weather.
The reset does not yet rebuild modified blocks or change the seed of the currently loaded world. requested_seed_matches_world reports whether the requested seed matches the actual world seed. For experiments requiring identical terrain, start each batch from a clean copy of the save.
Environment configuration
| Parameter | Default | Description |
|---|---|---|
backend |
"socket" |
real backend or "mock" |
host |
127.0.0.1 |
bridge address |
port |
25570 |
local TCP port |
bridge_timeout |
30.0 |
connection timeout in seconds |
width |
128 |
RGB observation width |
height |
128 |
RGB observation height |
frame_skip |
4 |
ticks executed per action |
max_episode_steps |
9000 |
internal episode limit |
control_mode |
"agent" |
agent, human, or dagger |
render_mode |
None |
use "rgb_array" for render() |
reward_function |
sparse survival | pluggable reward strategy |
Troubleshooting
Gradle requires JVM 17 or later / configured to use JVM 8
Gradle found an older Java installation. Select JDK 21 before running the wrapper:
export JAVA_HOME="$(brew --prefix openjdk@21)/libexec/openjdk.jdk/Contents/Home"
export PATH="$JAVA_HOME/bin:$PATH"
java -version
cd fabric
./gradlew build
ConnectionRefusedError: [Errno 61] Connection refused
The Python package could not find the bridge at 127.0.0.1:25570. Confirm that:
- Terminal 1 is still running
./gradlew runClient, or Minecraft was launched from a Fabric installation containing the bridge mod; - the Minecraft process has finished starting;
- no other process is using port
25570.
Open a single-player world before using the bridge
Python successfully connected to the mod, but Minecraft is still at the title screen or not fully inside a world. In the Minecraft window, click Singleplayer, load or create a world, wait for the player HUD and terrain to appear, and then rerun the Python command from Terminal 2.
The --backend mock smoke test neither launches Minecraft nor tests the Fabric connection.
Development
Run the Python test suite:
uv run pytest
Build and validate the Fabric mod:
cd fabric
./gradlew build
Repository structure:
src/minecraft_gym/ environment, actions, rewards, recorder, and transport
fabric/ Fabric mod and local bridge
scripts/smoke_env.py real or simulated smoke test
scripts/record_human.py human demonstration collection
scripts/run_dev.sh start Minecraft and JupyterLab
scripts/stop_dev.sh stop the development session
tests/ Gym contract and protocol tests
docs/protocol.md TCP protocol specification
Roadmap
- hard resets and automatic world restoration;
- vectorized environments and multiple Minecraft instances;
- hierarchical and temporal action wrappers;
- behavior cloning and reinforcement learning baselines;
- VLM-based reward evaluation and generation;
- language-model subgoal planning with survival-guide retrieval;
- demonstration inspection, replay, and curation tools.
Protocol
The bridge specification is available in docs/protocol.md.
Contributing
Issues and pull requests are welcome. See CONTRIBUTING.md
for the development workflow. When reporting a problem, include the Minecraft,
Fabric Loader, Fabric API, Java, and operating system versions, along with
minimal reproduction steps.
Metadata
Release files for minecraft-gym 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| minecraft_gym-0.3.1.tar.gz | 24.4 kB | Without provenance | Details for minecraft_gym-0.3.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| minecraft_gym-0.3.1-py3-none-any.whl | Python 3 | none | any | Details for minecraft_gym-0.3.1-py3-none-any.whl |
Total release size: 53.4 kB
Release files / minecraft_gym-0.3.1.tar.gz
| Download URL | minecraft_gym-0.3.1.tar.gz |
|---|---|
| Size | 24.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d48897e811b8dc4158a68e9a0171d35fd0430e2ec869083aa4ca5b4ce8ad27c4
|
|
BLAKE2b-256 checksum How to use checksums |
9beffcd5c64b13dff5dcbb75ad6a9121de4c57420caa3a2677a6c7d18ba59322
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / minecraft_gym-0.3.1-py3-none-any.whl
| Download URL | minecraft_gym-0.3.1-py3-none-any.whl |
|---|---|
| Size | 29.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2ceee3f12e95d729962197cbfa0e6e5ef4df9a6a0a33f82897934dd47b545dee
|
|
BLAKE2b-256 checksum How to use checksums |
ba50cc0de736f51f5cfad261d6f8b2da8795d33bafda28ea0869aca1e034d286
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Security for minecraft-gym 0.3.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.