Orchestration for a fleet of Minecraft agents that build a large schematic together.
An operator uploads a schematic, picks the agents to work it, and Osmium splits it into segments and hands each agent its own slice. The dashboard shows what the fleet is doing over the last six hours: blocks left burning down, throughput, the traffic every host moves, what needs attention, and what happened.
Status: early. Authentication, accounts, hosts, agents, the audit log, chat, activity, telemetry, live updates and the host transport are built and tested, and so is the build pipeline end to end: uploading a schematic, reading it, dividing it between agents, recording the result as a job — what is being built, where, by whom, and how far along — choosing the order each piece is placed in — three nested sweeps, shown as a filling cube rather than described — handing each segment to the host that holds the agent, serving its blocks as block states, and taking progress back as they are placed. The host in
host/signs agents in, puts them on a server and reports what they see; building is the part of it still to be written. A mock host in this repository speaks the same protocol, and the feeds stay empty until something connects and starts reporting. Remote configuration is wired end to end — what an operator sets is stored, sent to the host, and replayed on every reconnect — so nothing in the interface runs on mock data any more. An agent's world can also be watched live: the host streams the blocks and entities around it, and the browser renders them. Alongside that, agents chart the ground they walk over into a shared top-down map, report what they are carrying — which an operator can rearrange, drop or put in hand — and report everyone in view with their health — and where each of them was, kept past the moment they went, so the map can say who was standing there an hour ago. An agent's connection can be routed through one of its host's proxies, chosen by name, with the credential for it never leaving that machine, and pinned to IPv4 or IPv6 where one of the two does not work. An agent can be sent somewhere — clicked on the map or in the 3D view, typed as a coordinate, or asked for in game — and it walks there, or flies where the server allows it: high over whatever is in the way, climbing and gliding down along the way. The route is drawn in both views as it goes, and what came of it is announced in the corner, linked back to the agent. An administrator can see what all of that costs on disk, and free it. An agent builds what it is given: it fetches its piece of the schematic, walks the order the operator chose, and works one course at a time, placing everything it can reach as it travels rather than stopping over every block — stairs the right way round, slabs on the right half of the square, doors hinged the side they were drawn on, repeaters clicked to the delay the design asked for. It keeps out of the square it is about to fill, goes back for what it could not place while that course is still open and while it is passing anyway, climbs before it crosses to the next layer, and — where the order asks for it — snakes those layers so each one starts where the last ended. What it could not reproduce it reports rather than quietly accepting, and a piece that ends short says which blocks and why. A square with nothing standing around it is filled by clicking the square itself, so a floating column goes in rather than failing the piece. A plan says where it is for and which way it faces: a server and a world, and a job goes there and nowhere else — the crew is checked against the plan rather than the plan being guessed from the crew — plus a quarter turn that turns every block state with it. Not every job is a building. A box of world can be planned and dug out, and a footprint can be planned and flown over to be charted: both are saved, named and come back to like any plan, divided between agents by the same arithmetic, and drawn in their own colour wherever a job appears. Nothing is dispatched for those two yet — a host knows how to place a box of blocks and nothing else — so their pieces are cut, crewed and assigned, and the interface says exactly that. Where each piece of work stands is drawn on the map and in the 3D view — a plan dashed, a job solid in its kind's colour with the pieces it was cut into inside it.
| Module | What it is | State |
|---|---|---|
backend/ |
Spring Boot 4.1 / Kotlin. Auth, accounts, hosts, agents, schematics, build plans and jobs, and the WebSocket hosts dial into. | Built, 695 tests |
frontend/ |
Vue 3 / Vite SPA. Operator dashboard, the build pipeline, the live world viewer, the charted map and the storage breakdown. | Built, 665 tests |
host/ |
Runs on a machine you control, holds the Minecraft credentials and the proxies, drives the agents. TypeScript, on mineflayer. | Connects, plays, walks or flies where it is sent, reports its world, inventory and neighbours, streams what it sees, and builds the pieces it is given, 702 tests |
testserver/ |
A Paper server and the rig that builds on it: a schematic made of nothing but the block states that are hard to place, built for real and read back. | Two scripts; see its README |
host/ → osmium-link |
The host's own command line: the accounts it can log in with, and the proxies it can route through. | Built — see below |
Osmium never holds Minecraft credentials, and never performs the login.
The backend sends a host a setup_agent command; that host logs the account in by whatever means it
prefers and reports back only the resulting username and UUID. A full database dump therefore
reveals which accounts you run — not the ability to run them.
That constraint shapes everything else: how agents are addressed, why hosts dial out instead of being connected to, and why a host being unreachable makes an agent's state unknown rather than offline.
FLEET_CONNECTIVITY.md is the design document — credential custody, the
wire protocol, liveness, chat, and the alternatives that were rejected and why.
Needs JDK 25, Node 24 and Docker.
# 1. Postgres
docker compose -f backend/docker-compose.yml up -d
# 2. Backend on :8080
cd backend && ./gradlew bootRun # gradlew.bat on Windows
# 3. Frontend on :5173, proxying /api to the backend
cd frontend && npm install && npm run dev
# 4. Optional: a mock host, once you have registered one and copied its token
cd backend && OSMIUM_HOST_TOKEN=osm_host_1_… ./gradlew mockHostThe mock host speaks the real protocol over the real socket — it connects, reports agents, telemetry and chat, fetches segments and places blocks — so the whole pipeline can be exercised without a Minecraft account. It is a development convenience, not a stand-in for the host program.
Sign in with admin / admin. Those are development defaults and are seeded only while the users
table is empty — override OSMIUM_BOOTSTRAP_USERNAME / OSMIUM_BOOTSTRAP_PASSWORD, and
OSMIUM_JWT_SECRET, before running this anywhere real.
The interface can do almost everything, and the two things it cannot are both about custody. A host holds its own Minecraft credentials and its own proxy passwords, and neither ever crosses to the backend — so both are managed on the machine, by the tool that ships beside the host.
cd host && npm run link # or osmium-link from the imageRun with no arguments it takes the screen: the terminal's alternate buffer, a menu moved through with the arrow keys, and a form for anything that needs more than one answer — adding a proxy is one screen of five fields rather than five questions in a row. It comes back to the menu when a task is done, shows a failure as a page rather than falling over, and leaves your scrollback exactly as it found it.
| Command | What it does |
|---|---|
microsoft |
the device-code sign-in, and keeps the account |
token |
adds a Minecraft session token — the one credential nothing here can obtain |
list / remove <id> |
what this host holds, and dropping one |
proxy add |
a proxy agents can be routed through, by name |
proxy list / proxy remove <name> |
what it can route through, and dropping one |
proxy check <name> |
dials through one and says whether it worked |
Every command also takes flags, so a script needs no terminal at all — with two exceptions that are
never arguments, because an argument is in the shell history and in ps for every other user on the
machine: a session token, and a proxy password. Details, the proxy file format and the Git Bash
caveat are in host/README.md.
Routes authorize against permission nodes, never against role names. Roles are named bundles of nodes, arranged as nested tiers:
| Role | Adds |
|---|---|
viewer |
watch the fleet and the schematics, and see your own account |
orchestrator |
viewer + acting on hosts and agents, and uploading schematics |
administrator |
orchestrator + user management, the audit trail, and everything irreversible |
So the split is "runs the agents" versus "runs the people". Details in
backend/README.md.
cd backend && ./gradlew test # 695 tests; needs Docker for Testcontainers
cd frontend && npm test # 665 tests
cd host && npm test # 702 testsPlacing blocks is the one thing none of those can settle, because what a placement turns into is
the server's arithmetic. So it has a rig of its own: testserver/ puts up a real Paper server
and cd host && npm run rig -- run builds 183 specimens on it — every family of block whose state
depends on where the agent stood, what it clicked and which way it looked — then reads the world
back and reports every square that came out as something else. See
testserver/README.md.
The backend covers every route — happy paths, 401s, per-role 403s, 409s, 429s, 503s — plus real clients over real host sockets, and unit tests on an injected clock for anything about the passage of time. The frontend covers the route guard, the auth store, the API client middleware, the fleet store's derived state, cursor paging, the geometry behind the charts and the box viewer, which stream updates earn a notice, and that the English and German copy stay in step. The host covers the protocol codec, the route search and the driver that walks it, the chat formats against lines captured from real servers, the chat command system — including an adversarial pass on the one input it takes from strangers — the world stream, where the columns a viewer actually puts on the wire are checked against the ones its own spiral asked for, the proxy file and what counts as a dialable entry in it, and the command line's own reading of a keystroke: the escape sequences a terminal sends, and what each one does to a form.
Six workflows, all path-filtered so a change to one module does not run the others' jobs.
| Workflow | Runs on | Does |
|---|---|---|
backend-tests.yml |
pull request, or called | ./gradlew test, annotates failures, uploads reports |
frontend-tests.yml |
pull request, or called | Vitest, ESLint and the vue-tsc build, all three under if: always() |
host-tests.yml |
pull request, or called | Vitest and the tsc build, both under if: always() |
backend-image.yml |
push to main |
runs the tests, then publishes ghcr.io/integr-dev/osmium/backend |
frontend-image.yml |
push to main |
runs the tests, then publishes ghcr.io/integr-dev/osmium/frontend |
host-image.yml |
push to main |
runs the tests, then publishes ghcr.io/integr-dev/osmium/host |
Nothing is published without a green suite. Each image workflow calls the matching test workflow
as a reusable workflow and gates its publishing job on it with needs. The test workflows therefore
have no push trigger of their own — on main the image workflow drives them, so a push runs the
suite once rather than twice, and the suite cannot drift between the pull-request run and the
publishing run.
Image tags come from the version in build.gradle.kts and each module's package.json, plus
sha-<short> and latest. Failing tests become inline annotations and a job summary table, built
from JUnit XML by .github/scripts/junit-summary.mjs.
See LICENSE.