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.
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.
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.
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.
- 174tasks Gamma verified as successful across the logged sessions.
- 177of 4,714 model calls included a screenshot, 3.8%.
- 21,778events in one runaway session, 86% of everything logged.
- 93of 182 stored skills were redundant copies, 51%.
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.
