A comprehensive Minecraft-based benchmark evaluation agent for evaluating AI agents' planning, reasoning, and multi-step execution capabilities. Green Agent evaluates Purple Agent in complex, open-world minecraft environments using the A2A protocol.
This Green Agent provides a rigorous evaluation for various tasks in minecraft environment that goes beyond simple task completion metrics. It evaluates:
- Spatial Reasoning: Navigation, building, and exploration in 3D space
- Tool Use: Strategic use of items and environmental interactions
- Resource Management: Efficient use of materials and time under constraints
- Adaptability: Handling diverse task types across various distinct categories
- Multi-step Planning: Complex tasks requiring sequential action planning (e.g., crafting requires gathering materials → using crafting table → combining items)
Diverse Tasks across 12 categories
Reward-based Evaluation with simulation tracking and video assessment
Reproducible & Consistent results with deterministic task initialization
A2A Protocol Compatible - works with any compliant agent
Automated Scoring using MCU benchmark reward system
Configurable - select categories and customize step limits
- Python 3.10+
- Docker (for containerized deployment)
- OpenAI API key (for video evaluation)
First, clone repository and install dependencies:
# Clone repository
git clone https://github.com/KWSMooBang/MCU-AgentBeats.git
cd MCU-AgentBeats
# Install dependencies with uv
uv syncThen, Create a .env file in the project root:
# Required for video evaluation
OPENAI_API_KEY=your-openai-api-key-hereuv python src/server.py --host 0.0.0.0 --port 9009
# With custom agent card URL
python src/server.py --host 0.0.0.0 --port 9009 --card-url "http://localhost:9009"docker build -t mcu-green-agent .
docker run \
-p 9009:9009 \
--env-file .env \
mcu-green-agent \
--host 0.0.0.0 \
--port 9009Server Health Check:
curl http://localhost:9009/.well-known/agent-card.jsonThe repository includes a sample Purple Agent using STEVE-1 model for testing:
# In a separate terminal, start the test Purple Agent
uv run python test_purple_agent.py --host 0.0.0.0 --port 9019
# When you run green agent server with Docker
uv run python test_purple_agent.py --host 0.0.0.0 --port 9019 --card-url "http://host.docker.internal:9019"Edit test_scenario.toml to configure your evaluation:
[green_agent]
endpoint = "http://127.0.0.1:9009"
[[participants]]
role = "agent"
endpoint = http://127.0.0.1:9019"
or
endpoint = "http://host.docker.internal:9019" (with Docker)
[config]
task_category = "crafting" # Change to desired category
max_steps = 900 # Optional: customize step limitAvailable categories: building, combat, crafting, decoration, ender_dragon, explore, find, mine_diamond_from_scratch, mining_and_collecting, motion, tool_use, trapping
# Run evaluation with scenario file
uv run python test_evaluation.py test_scenario.toml
# Save results to JSON file
uv run python test_evaluation.py test_scenario.toml output/results.jsonExpected Output:
[Status: submitted]
Starting MCU evaluation with 8 tasks from crafting
[Status: working]
Running task: craft_furnace
[Status: working]
Running task: craft_ladder
[Status: completed]
MCU Evaluation Result
Category: crafting
Number of Tasks: 8
Total Score: 65.4
Task Results:
Task 'craft_furnace': 8.7
Task 'craft_ladder': 7.9
Task 'craft_enchanting_table': 8.2
...
test_evaluation.py runs one agent against one task. A comparison arm is that plus the
isolation and the settings that make two runs differ in exactly one thing. Getting this
wrong is not loud — a shared credential, a shared session store, or an inherited config
file changes results without changing any log line you would think to read — so the setup
is scripted rather than described.
# One arm.
scripts/run-arm.sh --arm dm_wm --agent worldmodel \
--task mine_diamond_from_scratch \
--green-port 9053 --purple-port 9063
# Three arms at once. Different ports, different --arm names, nothing else shared.
scripts/run-arm.sh --arm dm_pl --agent prolong --green-port 9051 --purple-port 9061 &
scripts/run-arm.sh --arm dm_wm --agent worldmodel --green-port 9053 --purple-port 9063 &
scripts/run-arm.sh --arm dm_wmc --agent worldmodel --green-port 9055 --purple-port 9065 --session-turns 12 --compact &
wait--dry-run prints the plan and exits — worth doing once before a 12,000-step run.
| Option | Default | What it decides |
|---|---|---|
--arm NAME |
required | output/NAME, the log prefix, and the codex home |
--agent KIND |
required | worldmodel, prolong, or steve1 |
--task NAME |
mine_diamond_from_scratch |
task category |
--green-port / --purple-port |
required | A2A ports; must be free |
--steps N |
12000 |
max_steps |
--step-timeout SEC |
600 |
seconds per observation |
--session-turns N |
1 |
turns a codex session is resumed for before it is dropped; 0 is unbounded and does not work — see below |
--compact |
off | on a full conversation, summarise and carry on instead of cold-starting. Only does anything above one session turn |
Nothing in these scripts kills anything. Ports are checked as free and the run aborts if
one is taken. A pkill in a launcher has taken out four running arms on this project
before; adding one back would do it again.
source env.sh handles JDK8, Xvfb and MINESTUDIO_DIR, and run-arm.sh sources it for
you. Two of its constraints matter when you place anything else:
$HOMEis a network filesystem here. MineStudio's sqlite instance registry fails on it withcannot rollback - no transaction is active, which is whyMINESTUDIO_DIRpoints at local disk. Anything else holding sqlite needs the same treatment.- No GPU render path, so the engine runs through Xvfb and Mesa llvmpipe at roughly 22 FPS per instance. Three concurrent arms is comfortable; each Minecraft instance also wants a few GB of RAM for the JVM.
steve1 additionally needs a CUDA device — set CUDA_VISIBLE_DEVICES before launching it,
and give concurrent neural arms different devices.
scripts/codex-home.sh builds one CODEX_HOME per arm. Use it directly if you are running
something run-arm.sh does not cover:
eval "$(scripts/codex-home.sh my_arm)" # exports CODEX_RUNTIME_HOME and CODEX_SQLITE_DIREach of its four jobs comes from something that went wrong here:
A private session store. Six arms shared one
~/.codex/runtime-home/thread_history_1.sqlite until it failed PRAGMA integrity_check
with database disk image is malformed. That breaks codex exec resume for every process
on the host, which forces CODEX_SESSION_MAX_TURNS=1 — no conversation memory at all — and
it stayed broken for a full campaign because the codex wrapper auto-repairs four other
databases and not that one. If you ever see failed to access thread history, move the
file aside and codex rebuilds it.
A shared credential, never a copy. auth.json is symlinked, because each copy of an
OAuth credential carries the same single-use refresh token: the second arm to refresh gets
your refresh token was already used and every later turn 401s. Point MCU_CODEX_AUTH at
a different file to run a different account.
No personal config. The codex wrapper symlinks ~/.codex/{AGENTS.md,lessons.md,RTK.md}
into whatever home it is given, and codex prepends them to every turn — 24,756 bytes on
this account, ahead of the agent's own 19.6 KB system prompt. --ignore-user-config does
not cover them. Empty real files defeat the symlink step, which skips a target that
already exists.
Not under /tmp. Codex refuses to create its PATH-alias helper binaries there
(Refusing to create helper binaries under temporary dir). The default is
/var/tmp/mcu-codex-$USER: same local ext4 partition, not wiped at boot, no refusal.
Override with MCU_CODEX_ROOT, but keep it on local disk. Budget about 6 MB per arm plus
roughly 20 KB per model turn of session history — a 12,000-step arm lands near 25 MB.
Homes are chmod 700: the credential is a symlink to a 600 file and was never exposed, but
the session store holds the full conversation, and a shared host has other accounts on it.
Two arms are only comparable if everything below is either identical or the one thing under test.
-
--compactvs the default. Codex ships auto-compaction. When a resumed conversation fills up, either it compacts and carries on, or it overflows and cold-starts onto the filesystem memory.CODEX_AUTO_COMPACT_TOKEN_LIMITdecides which, andrun-arm.shalways sets it explicitly — leaving it unset hands the choice to whatever codex defaults to, which is neither measured nor guaranteed equal between two runs.worldmodel_report.jsonreports the limit in force alongsidecompactionsandoverflow_resets, so check there rather than inferring from logs. At one session turn neither can ever fire, which makes this flag inert under the default; it only becomes a real choice once--session-turnsis raised, and even then the byte wall above arrives first. Measured: a 258 K window, ~1.9 K tokens a turn, so the token budget is 27 % used at the point the payload has already stalled the arm. -
--step-timeout. The benchmark default of 60 s suits a reactive policy answering in milliseconds. A planning agent spends a whole model turn on the step where it re-plans, and at 60 s those steps silently become noops. 600 is the working value here. -
--session-turns, and why it defaults to 1. Every act turn attaches the current frame, because the observation here is pixels rather than text. On a resumed session the old frames stay in the thread and codex re-sends the whole conversation on every call, so the request grows about 0.4 MB a turn with nothing to bound it. Measured on three independent arms: below ~5 MB a turn costs42s + 11.2s per MB; somewhere between 5.15 and 6.15 MB it steps to a flat ~600 s and stays there however much further it grows. All three crossed at turn 11–18 and dropped to roughly one step per ten minutes — no errors, no log line, just 10× slower. A 12,000-step episode needs 1,500–2,000 turns, so anything that grows per turn is dead long before the end; at one turn per session the payload is a constant 0.48 MB and the wall is unreachable. Session accumulation itself is harmless: 1,878 consecutive single-turn sessions showed no latency drift (53–71 s median throughout) and cost about 513 KB each on disk.Both agents are built to survive this.
_discard_sessionalready drops a thread on any unclean ending, precisely becauselogs.txt, the world model documents and the hypothesis graph live on the filesystem. Raising--session-turnsis how you measure what the conversation adds on top of that — it is roughly free up to ~5 turns, costs about+3.4 h per MBof session payload over a full episode, and hits the wall above ~12.Note the counters are token-based and this failure is byte-based: at the point the arms stalled, the token budget was 27 % used while the wire payload was already past the wall.
compactionsandoverflow_resetswill read zero through the whole thing. -
The action space is a property of the host, not the arm.
src/agent.pybuildsMinecraftSimwithaction_type="env", where a camera value reaches the game as written. Under MinecraftSim'sagentdefault it round-trips through VPT's mu-law binning and only 11 values per axis survive — measured on the live simulator, a request of 4.32° (one 18 px inventory slot) is delivered as 4.3200 inenvand 3.2154 inagent. That is lossless for a neural policy whose head emits those bins and lossy for anything writing degrees. If you change it,MCU_CAMERA_QUANTISED=1puts the world model's compensation and the camera correction incamera_contract.pyback.
logs/<arm>_green.log environment side, including the final score block
logs/<arm>_purple.log agent side: turns, milestones, session and quota errors
logs/<arm>_client.log the evaluation client
logs/<arm>_result.json the score
output/<arm>/episode_001/
logs.txt every action and the state that followed
frames/ the first-person view per step
memory/ world-model arms only: hypotheses, world_model/, procedures/
worldmodel_report.json milestones with the step each landed, turn counts, codex audit
Two things worth checking before trusting a number:
# Milestones with the step each landed on -- the comparison that matters, since the score
# counts milestones reached and is blind to how many steps each took.
python3 -c "import json;print(json.load(open('output/dm_wm/episode_001/worldmodel_report.json'))['milestones'])"
# Failed turns are usually infrastructure, not the agent. Check before concluding anything.
grep -oE "wrote no actions.json \(rc=[0-9]+\); .{0,60}" logs/dm_wm_purple.log | sort | uniq -cVideo scoring needs OPENAI_API_KEY. Without it that component fails cleanly and
Total Score falls back to the milestone-derived sim_score, which is the intended mode
for these comparisons.
- participants.agent (str): URL of the Purple Agent to evaluate
- Example:
"http://green-agent:9009"or"http://purple-agent:9019" - Must be A2A protocol compatible
- Example:
-
task_category (str): Task category to evaluate
- Examples:
"combat","crafting","building" - Available categories:
building,combat,crafting,decoration,ender_dragon,explore,find,mine_diamond_from_scratch,mining_and_collecting,motion,tool_use,trapping
- Examples:
-
max_steps (int): Maximum steps per task
- Default for standard tasks: 1200 steps
- Default for long-term tasks: 12000 steps
- Long-term tasks categories:
kill_ender_dragon,mine_diamond_from_scratch - These tasks require extensive resource gathering, crafting chains, and exploration
- Long-term tasks categories:
- Custom values: Can be set in config, but will be capped at 1200 for non-long-term tasks
- Recommended ranges:
- Quick tasks (motion, explore): 500-900 steps
- Standard tasks (crafting, combat): 900-1500 steps
- Long-term tasks: 10000-12000 steps
MCU-AgentBeats/
├── scripts/
│ ├── run-arm.sh # One comparison arm end to end
│ └── codex-home.sh # Isolated CODEX_HOME for one arm
├── src/
│ ├── server.py # A2A server entry point
│ ├── agent.py # Main evaluation logic
│ ├── messenger.py # Purple agent communication
│ ├── executor.py # Task execution management
│ ├── model.py # Pydantic data models
│ └── util.py # Helper functions (task loading, video processing)
│
├── MCU_benchmark/
│ ├── task_configs/
│ │ └── tasks/ # Task definitions by category
│ │ ├── building/
│ │ ├── combat/
│ │ ├── crafting/
│ │ ├── decoration/
│ │ ├── ender_dragon/
│ │ ├── explore/
│ │ ├── find/
│ │ ├── mine_diamond_from_scratch/
│ │ ├── mining_and_collecting/
│ │ ├── motion/
│ │ ├── tool_use/
│ │ └── trapping/
│ │
│ └── auto_eval/
│ ├── prompt/ # Video evaluation prompts
│ └── criteria_files/ # Task-specific evaluation criteria
│
├── output/ # Evaluation results and recordings
├── logs/ # Server logs
└── pyproject.toml # Project dependencies
Minecraft provides a rich, open-world environment that closely mirrors real-world agent challenges:
- Complex State Space: Thousands of block types, items, and environmental states
- Emergent Complexity: Simple actions combine into complex behaviors
- Grounded Multimodal Input: Visual observation (RGB images) with spatial reasoning
- Diverse Skill Requirements: Navigation, crafting, combat, exploration, construction
- Long-horizon Tasks: Require over 12000 steps with intermediate goals
Our benchmark includes 12 distinct categories, each testing different capabilities:
| Category | Skills Tested | Example Tasks |
|---|---|---|
| building | Spatial reasoning, multi-step execution | Build house, tower, maze |
| combat | Real-time decision making, positioning | Combat zombies, skeletons, enderman |
| crafting | Sequential planning, recipe knowledge | Craft enchanting table, furnace, ladder |
| decoration | Creative placement, aesthetic judgment | Decorate wall/ground, lay carpet |
| explore | Pathfinding, environment interaction | Explore chest, boat, climb |
| find | Visual search, navigation | Find diamond, village, bedrock |
| mining_and_collecting | Efficient resource gathering | Collect wood/wool/dirt, mine ores |
| motion | Basic movement and interaction | Drop item, look at sky, stacking |
| tool_use | Tool understanding, context-aware actions | Use bow, shield, brew potion |
| trapping | Strategic planning, entity manipulation | Trap mobs |
| Category | Skills Tested | Example Tasks |
|---|---|---|
| ender_dragon | Advanced combat, long-term planning, resource pipeline | Kill ender dragon |
| mine_diamond_from_scratch | Complete resource gathering → crafting → mining pipeline | Mine diamond from scratch |
These tasks require extensive multi-stage planning: gathering initial resources → crafting tools → exploring → achieving final goal.
The evaluation system uses different scoring approaches based on task configuration:
For tasks with reward configurations (most standard tasks):
-
Primary Score: Simulation reward tracking during execution
- MineStudio simulator monitors task events (e.g., craft_item, mine_block)
- Rewards are accumulated based on achieved milestones
sim_score = total_rewards_achieved
-
Video Enhancement:
- Video evaluation is performed only when
sim_score < max_score - If sim_score already equals max_score (perfect completion), video evaluation is skipped
- GPT-4 Vision analyzes the gameplay video and scores "Task Progress" (0-10)
final_score = (sim_score + task_progress_score) / 2- This helps capture partial progress not reflected in discrete reward events
- Video evaluation is performed only when
Example:
# craft_furnace.yaml
reward_cfg:
- event: craft_item
objects:
- furnace
reward: 10.0
max_reward_times: 1For complex, multi-stage, long horizon tasks (kill_ender_dragon, mine_diamond_from_scratch):
- Milestone Tracking: Task broken into sequential milestones
- Continuous Scoring:
continuous_score = completed_milestones + current_progresscompleted_milestones: Number of fully achieved milestonescurrent_progress: Progress on current incomplete milestone (0-1), evaluated by GPT-4 Vision
- Final Score:
(continuous_score / total_milestones) * 100
This provides fine-grained progress measurement for tasks requiring 12,000+ steps.
For tasks without reward_cfg or milestone_reward_cfg:
- Video-Only Evaluation: GPT-4 Vision analyzes recorded gameplay
- Score: Based solely on "Task Progress" dimension (0-10)
- Use case: Tasks where success is subjective or difficult to define programmatically
When video evaluation is used, GPT-4 Vision assesses gameplay across 6 dimensions:
| Criterion | Description | Score Range |
|---|---|---|
| Task Progress | Goal achievement level | 0-10 |
| Action Control | Precision and appropriateness of actions | 0-10 |
| Error Recognition | Detection and correction of mistakes | 0-10 |
| Creative Attempts | Novel problem-solving approaches | 0-10 |
| Task Efficiency | Speed and resource optimization | 0-10 |
| Material Selection | Correctness of tool/item usage | 0-10 |
Note: Video evaluation requires OpenAI API key and is used strategically:
- For long-term tasks: evaluating progress on incomplete milestones
- For standard tasks: enhancing simulation scores when partial completion is detected
- For non-reward tasks: primary scoring method
| Task Type | Primary Metric | Video Evaluation Role | Max Score |
|---|---|---|---|
Standard with reward_cfg |
Simulation rewards | Enhancement when score < max | 10 |
Long-term with milestone_reward_cfg |
Milestone completion + progress | Current milestone progress | 100 |
| No reward config | Video "Task Progress" | Primary scoring method | 10 |
Results are saved in output/{timestamp}/result.json:
{
"participants": {
"agent": "019bc2e2-b44b-71f3-9fa5-e89901920e31"
},
"results": [
{
"task_category": "crafting",
"num_tasks": 10,
"total_max_score": 100.0,
"total_score": 21.5,
"avg_action_control": 3.4,
"avg_error_recognition_and_correction": 0.9,
"avg_creative_attempts": 0.2,
"avg_task_completion_efficiency": 1.9,
"avg_material_selection_and_usage": 4.3,
"task_metrics": {
"craft_oak_planks": {
"max_score": 10.0,
"sim_score": 0.0,
"score": 4.0,
"action_control": 9.0,
"error_recognition_and_correction": 8.0,
"creative_attempts": 2.0,
"task_completion_efficiency": 8.0,
"material_selection_and_usage": 9.0
},
"craft_the_crafting_table": {
"max_score": 10.0,
"sim_score": 0.0,
"score": 1.5,
"action_control": 8.0,
"error_recognition_and_correction": 0.0,
"creative_attempts": 0.0,
"task_completion_efficiency": 0.0,
"material_selection_and_usage": 4.0
},
"craft_diorite": {
"max_score": 10.0,
"sim_score": 0.0,
"score": 2.0,
"action_control": 2.0,
"error_recognition_and_correction": 0.0,
"creative_attempts": 0.0,
"task_completion_efficiency": 1.0,
"material_selection_and_usage": 3.0
},
...
}
}
]
}Your Purple Agent must implement the following A2A message protocol. For detailed action space documentation, refer to MineStudio Action Space.
Request (InitPayload):
{
"type": "init",
"prompt": "You are an AI agent that can play Minecraft...",
"text": "craft furnace from cobblestone"
}- prompt: Basic instruction or context for the agent's role and behavior. Example: "You are an AI agent that can play Minecraft...". This defines the agent's overall style, goals, and constraints, action space, and serves as a decision-making guideline during action inference.
- text: The specific task or command to be performed. Example: "craft furnace from cobblestone". This clearly specifies the goal to be achieved and is a key input for determining the agent's purpose during action inference.
Response (AckPayload):
{
"type": "ack",
"success": true,
"message": "Agent initialized and ready"
}Request (ObservationPayload):
{
"type": "obs",
"step": 42,
"obs": "<base64_encoded_128x128_RGB_image>"
}- obs: The current observation of the environment, typically a base64-encoded RGB image of Minecraft game screen. This allows the agent to perceive the environment and infer the most appropriate action for the current situation.
Response (ActionPayload):
The agent supports three action formats:
{
"type": "action",
"action_type": "agent",
"buttons": [123],
"camera": [60]
}buttons: Single integer (0-8191) encoding all button states as a bitmaskcamera: Single integer (0-120) for discretized camera movement
{
"type": "action",
"action_type": "agent",
"buttons": [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"camera": [0.0, 90.0]
}buttons: 20-element binary array for individual button statescamera: [yaw, pitch] in degrees
{
"type": "action",
"action_type": "env",
"action": {
"forward": 1,
"back": 0,
"left": 0,
"right": 0,
"jump": 0,
"sneak": 0,
"sprint": 0,
"attack": 0,
"use": 0,
"drop": 0,
"inventory": 0,
"hotbar.1": 0,
"hotbar.2": 0,
"hotbar.3": 0,
"hotbar.4": 0,
"hotbar.5": 0,
"hotbar.6": 0,
"hotbar.7": 0,
"hotbar.8": 0,
"hotbar.9": 0,
"camera": [0.0, 0.0]
}
}For complete action space specification, see the MineStudio documentation.
Button Actions:
- Movement:
forward,back,left,right,jump,sneak,sprint - Interaction:
attack,use,drop,inventory - Hotbar:
hotbar.1throughhotbar.9
Camera Control:
- Format:
[yaw, pitch]or single discretized value - Yaw range: -180° to 180° (horizontal rotation)
- Pitch range: -90° to 90° (vertical rotation)
Note: All three formats are automatically parsed and converted by the Green Agent. Choose the format that best suits your agent's architecture.
- Create YAML file in appropriate category folder
- Define init commands, task text, and reward config
- Test with
run_task.py
Main files to modify:
src/agent.py: Core evaluation loopsrc/util.py: Task loading and video processingMCU_benchmark/auto_eval/: Video evaluation prompts
MIT License
For questions or issues, please create an issue on GitHub or contact the maintainers.