All projects

M.A.C.E.

A Minecraft agent that plans, writes code and watches for danger at the same time. I rebuilt NVIDIA's Voyager as three agents on one event bus, using Gemma 4 on Cerebras.

Context
Research project on concurrent supervision of an LLM agent, extending NVIDIA's Voyager
Role
Design and implementation
Stack
TypeScript, Bun, Mineflayer, libSQL, Gemma 4 31B on Cerebras
Year
2026
Outcome
174 verified tasks in the logs; median code generation 0.52 s
Diagram of M.A.C.E.: Alpha, Beta and Gamma run in one Bun process and exchange numbered events on a typed in-process event bus. All three call the Gemma 4 model on Cerebras. Beta reads the top three skills from a local skill library and runs generated code in Minecraft through Mineflayer. Gamma checks danger every three seconds and can send an emergency interrupt that aborts Beta's running skill.

A supervisor that runs beside the worker.

Voyager, NVIDIA's Minecraft agent, plans a task, writes code for it, runs it and checks the result, one step at a time. If the bot is attacked while the model is thinking, nothing happens until the step ends.

My research question was what changes when a supervisor runs beside the worker instead of after it, and what breaks first. Gemma 4 31B on Cerebras answers in about half a second, which makes a supervisor that thinks every few seconds practical, so I rebuilt the loop around it.

The project in 54 seconds. The first five shots are generated illustrations of the idea, not footage of the agent. The events, timestamps, tasks and numbers after them are copied from the logs.

Three agents on one event bus.

I split Voyager's loop into three long-lived agents that talk only through a typed event bus.

  • Alpha, the commander, proposes the next task from the bot's inventory and recent history.
  • Beta, the operator, retrieves similar past skills, asks the model for JavaScript and runs it.
  • Gamma, the overwatch, checks each result against its task and watches for danger.

They share one Bun process. They are not threads: Gamma's timer fires while Beta waits on the model or on a skill, so supervision overlaps work without locks. The cost is that a blocked event loop would stall all three.

M.A.C.E.: three agents, one event bus Alpha, Beta and Gamma run in one Bun process and talk only through a typed in-process event bus. Alpha sends TASK_NEW to Beta, Beta sends TASK_AWAITING_VERIFICATION to Gamma, Gamma answers with TASK_RETRY or TASK_SUCCESS, and every 3 seconds Gamma runs a local danger check and can send EMERGENCY_INTERRUPT to Beta. All three agents call the Gemma 4 model on Cerebras. Beta reads the top three skills from a local skill library, runs its generated code in Minecraft through Mineflayer, and Gamma reads the game state and screenshots back. Three agents, one event loop M.A.C.E. splits Voyager's loop into Alpha, Beta and Gamma, which talk only through typed events. Gemma 4 31B on Cerebras OpenAI-compatible API, capped at 95 requests a minute plan code verify, danger check One Bun process, one event loop Alpha, commander plans the next task LLM curriculum, rule-based fallback Beta, operator writes and runs code 120 s timeout, cooperative abort Gamma, overwatch checks results, watches danger every 3 s: local danger check Event bus (typed, in-process) TASK_NEW TASK_AWAITING_VERIFICATION TASK_RETRY (critique, max 3) TASK_SUCCESS, MACRO_GOAL_MET EMERGENCY_INTERRUPT abort the skill, run the survival routine 1 2 3 4 5 saved on TASK_SUCCESS top 3 as examples runs the generated code state and screenshot Skill library libSQL file, BGE-small embeddings Minecraft, through Mineflayer the bot, the world and the pathfinder
Solid gold lines are events on the bus, numbered in the order a task uses them. Dashed grey lines are calls outside the bus. Coral is the emergency interrupt. The bus is a typed wrapper over Node's EventEmitter, and the library is a local libSQL file.

Writing and running code.

Beta gives the model a short list of Mineflayer helpers: explore, mine, craft, place, smelt, fight and use chests. The model returns a few lines of JavaScript and Beta runs them with a 120 second timeout.

The typical reply is about 1,300 characters, which is why a call is quick. Gamma then reads the result and the bot's state. It can ask for a retry with a critique, up to three times, and only a verified success is saved as a skill.

Gamma's critic prompt treats a screenshot as supporting evidence only, and only 177 of the 4,714 model calls I logged included one. The vision path exists, but most of this run was text.

Interrupting a running skill.

Every 3 seconds Gamma checks health, lava and fire at the bot's feet and eyes, and hostile mobs within five blocks. This check is local and needs no model call. If health is below 12 or food is below 10, Gamma also asks the model, with a screenshot when vision is on. In my logs none of those 486 model checks ended in a confirmed danger.

When Gamma finds danger it emits an emergency interrupt, with a 10 second cooldown between interrupts. Beta aborts the running skill and runs a fixed survival routine: equip a sword, eat, run from the nearest hostile mob. Gamma skips its checks while it is verifying a result.

Voyager's serial loop against M.A.C.E.'s supervised loop Top lane: Voyager plans, writes code, runs it and verifies it, one block after another, with nothing watching while a block runs. Bottom lane: M.A.C.E. runs the same four blocks, and Gamma checks for danger every 3 seconds while the first three run, but not while it verifies. A danger found during the Run block aborts the skill part-way through. One loop, or a worker with a supervisor Schematic. The block widths are not measured times. Voyager one step at a time Plancurriculum Write codemodel call Runin the game Verifycritic Nothing watches while a block runs. A threat that appears during any of them waits for the step to end. M.A.C.E. worker plus supervisor PlanAlpha Write codeBeta RunBeta Verifyskipped Gamma overwatch 3 s apart paused while Gamma verifies Danger at the next check: abort the skill, run the survival routine. Ticks are Gamma's local checks: health, lava and fire, hostile mobs within five blocks.
Voyager's loop has no observer while a step runs. In M.A.C.E. Gamma's checks fire during planning, code generation and running, and an interrupt can end a skill part-way through.

Once Gamma has detected a threat, the abort reached the running skill within 3 to 8 milliseconds in nine clean cases in my logs. That is a small sample and it measures only the hand-off after detection. The real reaction time is the wait for the next 3 second check.

Cancellation is cooperative. JavaScript cannot kill a running promise, so each helper checks an abort flag and the wrapper clears the pathfinder goal and the controls. Generated code that never calls a helper would not stop.

A small skill memory.

Verified programs go into a local libSQL file with 384-dimension BGE-small embeddings, run on the CPU through ONNX. A new task is embedded, compared with every stored skill by cosine similarity, and the top three programs go into Beta's prompt as examples. With 182 rows a plain scan is enough.

Retrieval does not replace generation: Beta asks the model every time. Voyager indexes skills by a written description. I embedded Gamma's reasoning text instead, which turned out to be noisy.

What the logs showed.

I ran the agent for 34 sessions, four of them with me answering the prompts by hand. The other 30 called the model. These are the numbers I recounted from the logs and the skill database.

Median time per stage, measured from the console timestamps in my own run logs. Each figure is the whole round trip, including the network and my 95 requests a minute limiter. It is not Cerebras' own timing, which I did not record.

Alpha proposes a task
0.49 s, over 623 calls
Beta generates code
0.52 s, over 1,766 calls
Gamma critiques a result
0.44 s, over 1,477 calls

Three failures were more useful than the speed.

An event storm

One session produced 21,778 events in 51 minutes. Alpha kept re-proposing “mine 32 dirt” (10,552 times) while the bot sat near zero health, and each emergency resolution triggered the next task. The current code has a 10 second cooldown on interrupts and a 10 second pause in Alpha after three emergencies within 15 seconds. I cannot tie later runs to an exact code version, but they show no repeat of the storm.

Success that meant nothing

The critic prompt asked whether the item was in the inventory. Told to “mine 32 stone” while already holding 32 cobblestone, the generated code returned at once and the critic passed it. That task was stored 44 times.

A memory full of copies

The skill table had 182 rows but only 89 distinct programs. One program appeared 42 times, so the top three results could be three copies of the same one.

What I would change.

  • Add backpressure to the bus, so one bad state cannot produce 10,000 tasks.
  • Score a task by what changed in the inventory, not by what is present.
  • Deduplicate skills on insert.
  • Record Cerebras' own timing fields, so the latency is not mixed with my network and rate limiter.

None of these is built yet.

The scope of these results.

  • There is no benchmark and no success rate against Voyager, so the numbers show how the system behaves, not that it beats Voyager.
  • There are no automated tests.
  • “Concurrent” means cooperative on one event loop, not parallel threads.