Okay. Let me tell you about a crane.
This crane lifts pallets onto delivery trucks. It switches its work lights on when it gets dark. It refuses to swing a load over anyone's head, and it stops when the wind picks up. Pretty standard crane behaviour.
Here's the weird part: the crane has no idea the pallets exist. Or the trucks. Or the workers, the floodlights, the generators or the weather station. Each of those was built separately, shipped separately, and could have come from a completely different company. Nobody ever introduced them. They still work together like a crew that's shared a site for years.
That's what this post is about. And it isn't a video. It's running right here, in your browser:
equipmentmaterialspowerlightingworkforcesafetyweatherlogisticsThe thirty-second version, for the CEO in the room#
If you run a product, here's why this should make you a little excited:
- Partners can extend your product without you shipping anything. New equipment, a new overlay, a new integration: each one is a plugin the host loads at runtime. Your app doesn't get rebuilt, and nobody sends a pull request into your codebase.
- Plugins from different providers cooperate without knowing each other. They agree on shared words (this can be lifted, this provides power), not on each other's code. Swap one provider for another, and everything that relied on what it does keeps working.
- Switching something off doesn't take anything else down. Turn a plugin off and its objects become placeholders, its data stays put, and everyone who relied on it adapts. We ran all 256 on/off combinations of the eight plugins. Every one of them runs clean.
- Bad bundles never get to run. A plugin built for the wrong version, one missing a dependency, or one whose files don't match what was approved is stopped at the door before a single line of it executes.
Engineers: the rest of this post is for you. CEOs: you're very welcome to stay. There are cranes.
A quick heads-up before the code
The @z0devs/* libraries behind this demo are in private beta. The demo above needs nothing at all.
To build your own, request beta access.
Go on, try to break it#
Cut the power after dark
Press Day to make it night, then switch the power plugin off. Every floodlight goes dark and the crane's work lights go out. The lighting plugin didn't crash. It simply can't find anything that provides power any more. Switch power back on and the site lights up again.
Take away the cargo
Switch materials off. The pallets and pipes turn into wireframe placeholders, and their data is safe. The crane goes idle, and the delivery truck parks at the gate and tells you exactly why: "nothing on site provides liftable cargo". Honestly, that's better communication than I get from most delivery services. Switch materials back on and the runs resume.
See who depends on whom
Turn on Graph. Arcs appear between everything that depends on something else right now: power draw, wind readings, loads in the air, workers walking around obstacles. Switch a plugin off and the arcs that ran through it turn into dashed red broken links. The panel underneath shows who provides each capability and who uses it.
Meet the plugins that didn't make the cut
Three entries in the plugin list fail on purpose. Hover them to see why: one was built for an API version this host doesn't offer, one needs a plugin nobody installed, and one isn't the bundle that was approved. None of them ran a single line of code. They didn't even get through the gate.
Eight builds, eight providers#
In the real world, the crane comes from an equipment company, the pallets from a materials supplier, and the safety overlay from whoever gets the phone call when something goes wrong. They ship on their own schedules from their own repositories. None of them knows which of the others a given site has installed. And you can't import code from a company you've never heard of.
So each plugin in the demo is its own Rspack build, with its own remoteEntry.js, served from its own
URL. The only thing they share is a small SDK, @z0devs/three-sdk: the contract and a handful of
shared words. The host finds out about plugins from a manifest with one entry each:
{
"name": "materials",
"service": { "id": "materials", "remoteEntry": "/demo/three-materials/remoteEntry.js" },
"version": "1.0.3",
"shellApiVersion": "^1.0.0",
"vendor": "z0devs",
"permissions": ["registry:objectTypes"],
"provides": ["liftable", "geometry"],
"uses": {}
}Full disclosure: all eight list the same vendor, because we wrote them all. Nothing in the host cares.
Swapping materials for another provider's plugin is a one-entry change. If the newcomer provides
liftable, the crane lifts its loads and the truck orders them, and neither of them is any the wiser.
To make that work, we set one rule and refused to break it: no plugin may name another plugin. It can't import another plugin, call it, or look it up by name. Everything else in this post is what it took to keep that promise.
The host owns the world#
Every plugin system eventually gets the request: "just give us the scene, for flexibility". With great
power comes... you know how that one ends. Hand eight plugins the raw Three.js scene and any one of
them can scene.clear() the other seven's work.
So the host is the site manager. It owns the renderer, the scene, the camera, the lights and the lifetime of every object. Plugins never see any of it. They register object types, and the host builds the objects:
type ObjectType<P> = {
id: string; // "tower-crane", namespaced to "equipment:tower-crane" by the host
label: string;
defaults: P;
size: [number, number, number]; // footprint, also what a placeholder draws
traits?(props: P): Record<string, unknown>;
create(props: P): {
object: Object3D;
tick?(dt: number, world: World, self: Snapshot): void;
status?(): Record<string, unknown>;
};
Inspector?: ComponentType<{ props: P; status: Snapshot["status"]; onChange(next: P): void }>;
};Owning the lifetime means the host cleans up too. When an object is removed or rebuilt, or its plugin switches off, the host frees its geometry, materials and textures. In one run, switching the equipment plugin off took the GPU geometry count from 371 down to 130. A plugin that forgets to clean up can't leak memory, because cleaning up was never its job. (I'd love this arrangement for my kitchen.)
One copy of three#
Three.js is the engine that draws all of this. Load two copies of it on one page and they quietly stop recognising each other's objects, which is exactly as fun to debug as it sounds. So the host shares one copy, and plugins borrow it instead of bringing their own:
// in every plugin's federation config
three: { singleton: true, eager: false, import: false, requiredVersion: "^0.186.0" }import: false means a plugin carries no copy of its own. Load it into a host that doesn't provide
three and it fails at load, which is correct: it has nothing to draw with. Three ships breaking
changes in 0.x minor releases, so the range is pinned to one minor (^0.186.0 means
>=0.186.0 <0.187.0). The payoff: each plugin is about 120 kB, and the shell, which carries the one
copy of three and React, is about 1.2 MB.
Switching a plugin off never deletes anything#
The scene is a plain JSON document: id, type, position, size, props. When the plugin that
draws a type switches off, its entries stay in the document and show up as labelled wireframe
placeholders. They're chalk outlines, just less ominous. Switch the plugin back on and everything
returns exactly as it was.
How strangers cooperate#
If plugins can't talk to each other, how does a crane load a truck? Through the host. Every interaction in the demo goes through one of five channels the host controls:
| Channel | What it is | Example |
|---|---|---|
| Traits | Public, static facts about an object | liftable, power-source: { range } |
| Status | Public, live data, published every frame | a generator's loadKw, a crane's state |
| Attach | A write the target has to allow | only objects that declared liftable can be lifted |
| Spawn / remove | Writes the host checks first | spawning is refused if nothing provides the type; only the carrier may remove |
| Links | "I depend on that, right now", self-reported | what graph mode draws; never read by behaviour |
Here's how a delivery happens. A truck parks and posts a note in its status: { wants: "materials:pallet", bed: [x, y, z] }. The crane, looking for work with world.query({ trait: "liftable" }), spots the
request, fetches a matching pallet, hangs it off its own hook with world.attach, and lowers it onto
the truck's bed. The host moves the load and records where it went. Then the truck drives off with it.
The crane has never heard of the logistics plugin, and the truck has never heard of the equipment
plugin. It's like two coworkers coordinating entirely through sticky notes on the fridge, which is also
how most offices work.
Power works the same way. Each floodlight posts which generator it's drawing from and how many kW it draws. The generator adds up everyone who named it, and trips its breaker when the total goes over capacity. The crane is a power consumer too, so it lights its work area at night, and the generator counts its draw like anyone else's.
Depend on what things can do, not who made them#
The truck never orders cargo by plugin name. (A hard-coded "materials:pallet" is just a plugin's name
wearing a fake moustache.) It asks the world what's available right now:
const suppliers = world.catalogue({ trait: "liftable" }); // data only: { id, label, traits }
if (!suppliers.length) {
note = "standing by — nothing on site provides liftable cargo";
return;
}
const order = suppliers.find((s) => s.id.endsWith(":pallet")) ?? suppliers[0];Any provider's plugin that declares liftable works, unchanged. A missing capability changes the
truck's behaviour instead of breaking it. And the catalogue only ever returns data, never another
plugin's code.
Shared words beat shared code#
Some rules need two plugins to agree. The crane won't lower a load while someone is under it. Very responsible. Workers walk around anything with a footprint on the ground. Also very responsible. But a load in the air has no footprint, so how does a worker know to keep clear without the workforce plugin knowing anything about cranes?
With a shared word. keep-out lives in the SDK both plugins already use. While the crane has a load
in the air, it publishes status.keepOut: { x, z, radius }. Workers look for { trait: "keep-out" }
and step out of any area they find, whoever published it. Nobody ends up in a polite standoff under a
swinging pallet, and the two plugins coordinate through vocabulary alone. Next time someone wants a no-go zone (a welder, a blast area, a very angry foreman), the word is
already there.
Trust, but verify#
Once everything depends on capabilities, you want a map: who provides what, who needs what, and what breaks if you switch something off. For a business, that's the difference between asking "what happens if we drop this provider?" before you do it and finding out after.
There are two obvious ways to get that map, and each one fails on its own. You can watch what plugins do at runtime, but:
- It costs every frame. Recording every read means wrapping everything every plugin touches.
- It only sees what has already happened. The crane's high-wind stop has no edge until it's windy.
- It knows nothing about plugins that aren't loaded. "Can I switch materials off?" has to be answered before you do it.
Or you can make plugins declare their dependencies, but declarations can drift. It's easy to forget that workers depend on materials just because they walk around every pallet. So we use all three of these:
| Layer | Where | Cost |
|---|---|---|
provides / uses in the manifest drive the map, the impact warnings and human review | Production, before any plugin runs | zero |
Each plugin's view of the world is scoped by its declarations: an undeclared trait query, or a query with no trait filter without declaring geometry, is flagged | Production | one map lookup and one set lookup per query |
| A test runs every plugin combination and fails on undeclared uses, and on declarations nothing ever used | CI | zero in production |
Enforcement stays cheap because traits are the only way plugins discover each other, so the host
only has to watch query and catalogue, not every property read. Reading footprints is a declaration
of its own, geometry ("reads everyone's footprints"), so the map shows materials → geometry →
workforce, and any plugin that reads footprints without declaring it gets flagged. Hover any plugin in the demo and it tells you what switching it off would affect.
The host doesn't name plugins either#
The rule applies to the host's own UI too. See the safety badge floating over the site? The host has no
idea a safety plugin exists. An overlay can return badge(world) → { tone, label }, and the host draws
an icon from the tone alone (a green shield, or a red triangle) and shows the overlay's own panel as its
tooltip. The menu says "Safety" because the overlay's label says so. A second overlay from another
provider would get its own pane, tab and badge with zero changes to the host.
Don't take my word for it#
"Isolated" is an easy word to put on a slide. So the demo's simulation also runs without a screen, and a test holds us to it:
- All 256 combinations of the eight plugins each run for 30 simulated seconds, switching to a different combination halfway through, with night falling partway. Any error from plugin code fails the run. The whole suite takes about ten seconds.
- Behaviour tests:
- the truck delivers and the crane stores what it delivered;
- with no
liftablesupplier, the truck stands by; - floodlights go dark without a power source;
- the truck steers around a generator dropped on its road, and yields to a worker standing in it;
- the crane never holds a load over someone for long.
- We broke things on purpose to make sure the tests notice. A crane that assumes a weather station
exists fails every combination without one. Workers that ignore footprints walk into a floodlight, and
workers that ignore
keep-outend up stuck under a load.
What gets me excited#
Today it's a construction site. But underneath, the contract knows nothing about cranes. It's object types, traits, status and a few shared words. Any world where things from different makers have to share space fits the same shape: a factory floor, a warehouse, a port, a hospital wing.
Picture your partners shipping 3D equipment, sensors and overlays straight into your product. Each one gets checked at the door, works with whatever else is installed, and can be switched off without leaving a mess. On the host's side, adding one isn't an integration project. It's a manifest entry.
And here's the idea I can't stop thinking about. Every plugin here already sees the world as plain data and acts only through requests the host checks. That's the same loop an AI agent runs: observe, decide, act. Give an agent the truck's seat and a delivery schedule, and see what happens. I have a feeling it'll be more fun than it should be.
Want one of these?#
The libraries are in private beta with design partners. If you'd like your product to be the next construction site (cranes optional), this is the way in.