Personalized World Models with Reactor
Use an Onairos persona to seed real-time, walkable worlds with Reactor's world model.
Onairos has partnered with Reactor to make generated worlds personal by default. Together we turn personalized world models into something simple and magical to build.
How the pipeline works
Three pieces make the interface work:
- Onairos brings the user-owned context (the persona).
- An image model (e.g. Gemini) renders that context into a seed frame.
- Reactor turns the frame into a live, walkable world.
Only one step is yours to design: how you translate a persona into a world. What that world is — a travel scene, a game level, a storefront, a gallery, a calm space to think — is entirely up to your product. Everything else (reading the profile, seeding the frame, and spawning the world) stays the same.
Conventions for these examples
Some names below are real APIs you import and call. The rest are helpers you write yourself — placeholders standing in for your product's own logic.
OnairosButton,initializeApiKey— from theonairosSDK.LingbotProvider,useLingbot(),useLingbotImageAccepted()— from Reactor's@reactor-models/lingbotSDK.useLingbot()returnsuploadFile,setImage,setPrompt,setSeed,start,setMovement,setLookHorizontal, andsetLookVertical.
The Seed Forge route shown in Step 3 is an internal reference, not a public endpoint — you go through your own forgeSeed helper, which calls it server-side.
extractProfile,translateToScene,forgeSeed,captureWorldFrame,yourMatcher— your product's own logic; name and implement them however you like.ReactorWorldProvider— a thin wrapper you write around Reactor'sLingbotProvider.waitImageAccepted— a one-line promise wrapper around theuseLingbotImageAccepted()hook, so you canawaitthe event inline.
1. Read the persona
A few lines drop the Onairos consent flow into your app. When the user connects, onComplete hands you their permissioned profile.
import { OnairosButton, initializeApiKey } from 'onairos';
await initializeApiKey({ apiKey: process.env.NEXT_PUBLIC_ONAIROS_API_KEY });
<OnairosButton
webpageName="YourApp"
requestData={['basic', 'personality']}
trainingScreenMode="fast"
onComplete={(result) => buildWorld(extractProfile(result))}
/>
A user's Onairos profile is a plain object you can shape a scene from:
// extractProfile(result) →
{
archetype: "The Quiet Cartographer", // one-line identity
summary: "Drawn to depth over breadth, …", // a paragraph of nuance
topTraits: [{ name: "Curious Exploration", score: 92 }, …], // scored positives
lowTraits: [{ name: "Thrill-Seeking", score: 18 }, …], // what to avoid
mbti: "INFP", // light energy / social signal
platforms: ["spotify", "youtube", "reddit"] // where the read came from
}
2. Translate the profile into a scene
This is the part you design. Turn the profile into a scene prompt — a description of the world to generate. How you map a persona to a world is your product's own logic, so the same pipeline can power very different apps.
// your logic: map an Onairos persona → a scene for the world
const scene = await translateToScene(profile);
// → { prompt, ...anything else your product needs }
// e.g. attach a search query, a level id, a product category…
Not every field matters equally. Here is what each one drives:
| Field | What it shapes |
|---|---|
topTraits | The base reality: the kind of world, its mood and pace. Highest scores dominate. |
lowTraits | The base reality by exclusion: what to keep out, like thrill, crowds, or danger. |
archetype | The anchor: sets the genre of the world before any detail. |
summary | Augmentation: the specific objects and textures once the world's kind is set. |
mbti | A light nudge on energy and social density. |
platforms | Which data sources the persona was read from. |
Traits set the base reality — the kind of world and what to keep out. The summary fills in the details once that world exists. The archetype is the anchor that ties it together.
3. Plant the seed frame
Reactor's world model needs a seed frame, so the scene is handed to an image model and rendered into a single still — the frame the world grows from.
// scene prompt → one still image (a Blob), the frame the world grows from
const seed = await forgeSeed(scene.prompt);
Under the hood, forgeSeed posts the scene to a server-side "Seed Forge" route and names the image model to use. The provider call stays server-side, so no image-model key ever touches the client. The shape below is for reference only — it shows what your forgeSeed helper sends; it isn't a public endpoint you call directly.
# reference only — reached through your forgeSeed helper, not called directly
POST /reactor/seed-forge
{
"prompt": scene.prompt,
"imageProvider": "gemini",
"imageModel": "gemini-3.1-flash-image",
"aspectRatio": "16:9",
"imageSize": "2K"
}
// → streams back the rendered frame as an image Blob
Swap imageProvider or imageModel to render the seed with a different image model — the rest of the pipeline doesn't change.
4. Hand it to Reactor
The Reactor provider mints a short-lived JWT and opens the WebRTC connection for you, so you never manage sockets. From there it's a short handshake: upload the seed, wait for the model to lock onto the frame, set the prompt, then start.
// inside <ReactorWorldProvider> — your wrapper around LingbotProvider (auto-connects + handles auth)
const { uploadFile, setImage, setPrompt, setSeed, start } = useLingbot();
const ref = await uploadFile(seed); // upload the seed frame → a handle
const ready = waitImageAccepted(); // wraps useLingbotImageAccepted() → a promise; arm before setImage
await setImage({ image: ref });
await ready; // model has locked onto the frame
await setPrompt({ prompt: scene.prompt }); // how the world unfolds as you move
await setSeed({ seed: 42 }); // pin the RNG → reproducible world
await start(); // live: WASD to move, look to explore
- Order matters. Subscribe with
useLingbotImageAccepted()(here wrapped aswaitImageAccepted()) beforesetImage, or you can miss the event. Prompt and seed are set after the frame is accepted, thenstart(). - The seed is a frame, not text.
setImageis the heavy lift — it's what the world grows from.setPromptsteers how it unfolds as you move. - Output is a live video track rendered by the SDK's view component. Movement and look are ongoing commands (
setMovement,setLookHorizontal,setLookVertical) and the world reacts in real time.
5. (Optional) Connect the world to your product
The live world is a real-time video the user moves through. At any moment you can capture the current frame and combine it with the persona to drive whatever comes next in your app. This step is entirely yours — Reactor just gives you the live view to build on.
const frame = captureWorldFrame(); // the current live view
// feed the frame + persona into your own product logic
const results = await yourMatcher({ profile, image: frame });
// travel → destinations · commerce → products · media → titles · games → quests
Because it is your logic, you decide what "next" means — surfacing matches, saving state, or triggering an action.
Reactor builds the world, Onairos makes it personal, and together they turn personalized world models into something genuinely simple to build. This is the first step toward world models that are personal by default.
Building something dope with this? Ship it and tag @reactorworld and @onairosapp on X — we'll help get eyes on it. Now go build something worth showing off. Good luck. 🚀