How we build a living grid presence in Luau

The working notes behind inworld.help — bots, a viewer, a hub, and one scripting language everywhere.

2026-08-12

This is a high-level sketch of the system we operate and how we develop it. It is written to be read quickly by an unfamiliar AI or engineer — enough to know what exists, why it's shaped that way, and how work moves through it — without drowning in the API surface.

The thing, in one breath

We run a presence on the grid — a set of bots and a viewer — all scripted in Luau (Roblox's flavor of Lua, chosen because it is fast, sandboxed, and typeable). Every script talks to the grid through a single global table, client. Every bot and the viewer can be driven over one central hub, which routes control requests and exposes a Model Context Protocol (MCP) API so that AI tooling — and we ourselves — can drive any of them.

The key point: none of this needs a remote server. A bot runs just as well on a local machine — a laptop, a home box — with or without a hub in the middle. The remote host is optional; it exists only for the bonuses it adds (a stable public presence, a persistent relay, MCP exposed over the network, many hosts reachable from one door). If you only need a local bot that reads a parcel and sends you an IM, you can run it entirely on one machine and never touch a server.

Bots — headless grid clients running on a server, driven by Luau services.

Viewer — a full grid client with an embedded VM; it implements the same client surface, so the same script runs on either host.

Hub — a WebSocket relay + MCP server. Registers every bot and viewer, routes control, persists state.

Luau — the one language for scripts, services, and our own tooling. No Python in the workflow.

One contract, many hosts

The core design bet is a single scripting contract. A bot embeds a Luau VM behind a C-ABI shim; the viewer embeds the same VM over its C++ managers. Both expose the same client table — chat, movement, inventory, objects, marketplace, web, RLV lockdown, timers, and a raw packet escape hatch. Because the surface is identical, a script written against the reference runs verbatim on either host; only the binding code beneath differs.

This matters for how we work: we can develop and verify behavior on a cheap, disposable bot, and know the same script behaves on the interactive viewer. The full binding reference lives at dev.inworld.help/ukulele.

The three places we run code

  1. Server — a VPS hosting the hub and the bot fleet. Services start at login, run on timers and event handlers, and are controllable live over MCP. This is the optional, "for the bonuses" host — convenient when you want a permanent presence and a reachable door, but not required.
  2. Viewer (laptop) — the interactive client the operator sits in. It hosts Luau services too, loadable at login or hot-swapped live over the hub without a restart.
  3. Laptop tooling — a standalone Luau runtime for scripts, one-off automation, and the local half of our MCP bridge. Everything we build by hand is Luau.

A local-only setup simply runs a bot and its services on one machine, talking straight to the grid — no hub, no server. The hub becomes relevant when you want many hosts coordinated, state persisted centrally, or the whole fleet reachable through one MCP endpoint.

How control flows

Locally, a bot runs its own services against the grid directly. Add a hub and it becomes the nerve center: a bot registers under its grid username, the viewer registers too, and the hub relays requests to any of them — so many bots and viewers are drivable at once, over MCP tooling (reachable over HTTP) or directly. Anything without a typed binding is reachable through the raw-packet surface: subscribe to or send any classic-UDP packet by name.

The workflow

We develop in a tight loop:

  1. Write Luau — a script, service, or tool, targeting the client contract.
  2. Compile-check — a fast static pass catches type and syntax errors.
  3. Run against a live host — eval a snippet on a bot, or hot-reload a viewer service in place.
  4. Verify, don't trust — a clean build is not a working feature; we confirm the behavior actually shows up over the hub.

Pure-Luau changes hot-reload with no restart. Only changes to the bindings surface itself need a rebuild.

Reusing code, the Luau way

We don't drop to Python for libraries. Lute ships a package manager (Loom) that pulls third-party Luau/Lua code straight from GitHub pinned by commit SHA — our native stand-in for pip install. Most pure-Lua 5.1 libraries drop in with a thin shim. The standing rule: if it can be done in Luau, it is done in Luau.

The repositories we lean on

Most of what we build is a thin layer over a handful of open-source projects. Knowing which is which helps place everything else.

The pattern to notice: small, focused open-source cores (a language, a runtime, a grid protocol library, a viewer, a package manager, an AI-tool protocol), and our work is the thin, opinionated layer that wires them together with one scripting contract and one workflow.

The short version

Bots and a viewer, all scripted in Luau against one client contract. A local bot runs fine on its own — the hub and remote host are an optional layer that adds coordination, a persistent public presence, and an MCP door. We develop in Luau end to end, verify live, and reuse third-party code without leaving the language. That's the whole shape of it.

Want the API details? The Luau/LibreMetaverse client reference and the MCP server usage guide. The grid protocol itself is LibreMetaverse, and the language is Luau.