How the homepage demo works
The same game you see on the homepage is a single Resonate workflow running on Cloud Run and Cloud Functions. White is a deterministic chess engine; black is Claude Haiku 4.5 picking moves one at a time. Each move is a durable step. The worker scales to zero between moves. The workflow survives it.
One playGame workflow is running right now on Cloud Run. On white’s turn it calls computeMoveEngine. On black’s turn it calls agentMove, which asks Claude for a move, validates it against the legal list, and writes both the move and Claude’s reasoning to Firestore.
The board below is a Firestore subscription — no SSE, no gateway. The workflow runs forever; one game finishes, the next starts.
Live demo
Claude Haiku 4.5 plays black, one durable step per move.
Each move is three durable steps: compute (engine or Claude), write, sleep. The pulsing node is what the workflow is doing right now. When it says ctx.sleep the worker is terminated — the server is holding the continuation in Postgres and will re-invoke the function when the timer fires.
Five pieces, all managed GCP services. The worker scales to zero. The server is one Cloud Run container. State lives in Postgres on the workflow side and in a single Firestore doc on the presentation side — browsers subscribe to the doc and never talk to the server.
The whole thing. One generator. The only Resonate primitives are ctx.run and ctx.sleep.
import type { Context } from "@resonatehq/sdk";
import { Chess } from "chess.js";
import { aiMove } from "js-chess-engine";
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const MOVE_DELAY_MS = 4500;
// White = deterministic chess engine.
async function computeMoveEngine(_ctx: Context, fen: string, level: number) {
const move = aiMove(fen, level) as Record<string, string>;
const [from, to] = Object.entries(move)[0];
return `${from.toLowerCase()}${to.toLowerCase()}`;
}
// Black = Claude, given the position and the legal moves.
async function agentMove(_ctx: Context, fen: string, legal: string[], history: string) {
const response = await anthropic.messages.parse({
model: "claude-haiku-4-5",
max_tokens: 512,
system: [{ type: "text", text: SYSTEM_PROMPT, cache_control: { type: "ephemeral" } }],
messages: [{ role: "user", content: `FEN: ${fen}\nLegal: ${legal.join(",")}\nHistory: ${history}` }],
output_config: { format: { type: "json_schema", schema: MOVE_SCHEMA } },
});
// Validate, retry once on illegal move, fall back to random legal move.
return coerceToLegal(response.parsed_output, legal);
}
export function* playGame(ctx: Context) {
while (true) {
const game = new Chess();
let moveCount = 0;
yield* ctx.run(writeState, buildState(game, undefined, moveCount));
while (!game.isGameOver()) {
const uci = game.turn() === "w"
? yield* ctx.run(computeMoveEngine, game.fen(), 3)
: (yield* ctx.run(agentMove, game.fen(), legalMoves(game), history(game))).move;
const move = applyUciMove(game, uci);
moveCount++;
yield* ctx.run(writeState, buildState(game, move, moveCount, reasoning));
yield* ctx.sleep(MOVE_DELAY_MS); // ← worker terminates here
}
}
}An LLM call is just a durable step
Claude’s move is wrapped in ctx.run — the same primitive as the engine’s move. If the API flakes, the step retries. If the response is an illegal move, the workflow corrects itself before writing state.
Serverless workers actually work
Each move is a cold-friendly function invocation that ends with ctx.sleep. The container goes away between moves. The workflow keeps going.
The workflow outlives the server
We redeployed the Cloud Run service mid-game. The workflow picked up from the last completed move, untouched. No intervention.
No streaming gateway
Browsers read Firestore directly. There is no long-lived service between the workflow and the UI — the worker writes one doc and the clients see it.
Heads up: the server in this deployment is pre-release and unauthenticated — URL-as-secret, not for public traffic. Don’t copy this shape for anything that isn’t a demo.
If you have a long-running, step-by-step process that needs to survive crashes, sleep between steps, and stay alive across redeploys — this is the shape.