Recipes
Patterns for building specific kinds of game mechanics on top of the SDK.
- Multi-character scenesPattern for several AI characters in a shared scene, each tracking their own perspective. One agent per character, broadcast each event via addMessage, take turns via message-less send. Covers chat rooms, juries, panels, councils, debates, ensemble casts.
- Structured output (generative scenarios)Use agent.send with responseFormat and a Zod schema to get typed, parsed JSON back instead of free text. The schema's .describe() calls are the spec the LLM follows. Ideal for generating a whole scenario, cast, or config once at game start, then driving the rest of the game from it.
- Custom (non-Chat) frontendsBuild a bespoke UI instead of the built-in Chat. defineFrontend gives you state, emit, and gameHistory — render whatever you want from them. Derive views (e.g. per-character threads) from gameHistory.recentBlocks rather than duplicating transcripts in state, type sub-components from the SDK's exported building blocks, and theme with the host site's CSS variables.
- Generating imagesGenerate images during gameplay with io.activities.generateImage(callId, params), which returns { mediaId } at dispatch while the image renders in the background. Render a mediaId with the built-in RenderMedia component (or useMedia for custom UI), store it in state or emit it as an event, and drive prompts from an illustrator agent. Covers the model prompting guide, idempotency, and pre-authored image assets.
- Reference images & multi-character shotsUse generateImage's referenceMediaIds to keep a character visually consistent across images and to compose several characters into one image (a two-shot of both speakers, a group scene). Generate one canonical portrait per character, keep its mediaId in state, then pass it as a reference for every later shot.
- Time, ticks & background playHow to build time-driven games — countdown timers, pets, farms, day/night, idle simulations. Pair onTick with tickInterval to run code on a fixed cadence; read the two frozen clocks state.gameTime (game-active ms, the one for timers) and state.now (a wall-clock timestamp by default, or a derived false clock in game mode); choose a clock mode ('wall' vs { start } false time) and let players fast-forward game-mode sessions; emit input events from a tick to bridge into onEvent; and opt into backgroundExecution to keep ticking up to 48h after the player leaves.
- NotificationsReach the player when something happens in your game — "your farm needs water", "the negotiation took a turn". Declare requestNotifications true in defineBackend, then call ctx.io.notify({ title, body, image?, tag? }) from onEvent (or a tool's execute) to push a notification into the player's cross-game inbox (the nav bell) and, when they're on the site, a toast. Detect a condition in onTick and bridge to onEvent with emitInputEvent. Notifications are replay-safe, free (only the content you generate spends), and coalesce by tag.
- Rolling your own composerThe built-in Chat composer (textarea + Send) is deliberately minimal and cannot be styled — no class hooks, inline styles, and none are coming. For any non-trivial input (custom width or alignment, your own styling, a single-line field, extra controls beside it) pass showInput={false} to Chat and render your own form that calls emit(...). Lock it on gameHistory.streaming || submitting || suspended; for a multiline variant, submit on Enter, newline on Shift+Enter, and guard e.nativeEvent.isComposing so an IME candidate confirmation doesn't submit mid-word.