Durable agents
To the right, two Claude agents are playing chess — each one a durable loop running on Postgres. Between every move the worker that runs them terminates. The game doesn’t notice.
They’re the same loop, forked twice: same model, same tools — one prompt says attack, the other says hold the position. That’s the whole difference between one agent and another.
Live demo
The same durable loop, forked twice — same model, different prompt.
Simple to build
When you reshape the loop, there are really only four things you touch. Those four points are where one agent becomes different from another — and the two players above show it exactly: only the first one differs.
Same model, same tools, same finish line. One prompt attacks, the other defends. Everything underneath — checkpointing, retries, resume — is the substrate, and each move is itself a durable execution: a call that fails gets retried, a call that waits can wait for days.
One primitive
Both agents run the same loop, and every move it makes — asking Claude for a move, writing the new position, waiting for the other side — is wrapped in one primitive: ctx.run. The model call is a durable step like any other. If the API flakes, the step retries. If Claude returns an illegal move, the loop corrects itself before writing anything down.
Below is the execution trace of the running game, live — what resonate tree would show you. Each move you watch land carries its real payload: the move, what it captured, the agent’s reasoning. And between moves you can watch ctx.sleep count down while the worker is terminated — the server holds the continuation and re-invokes it when the timer fires.
The whole loop
The entire game running on the board is this. One agentPlayer call for both players, and three Resonate primitives: ctx.run, ctx.sleep, and ctx.detached.
import type { Context } from "@resonatehq/sdk";
import { Chess } from "chess.js";
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const MOVE_DELAY_MS = 4500;
// The only thing that differs between the two agents: the system prompt.
const PROMPTS = {
w: "You are White. Play aggressively — seize the initiative.",
b: "You are Black. Play for position — trade into a stable structure.",
};
// One agent. Given the side, the position, and the legal moves, it picks one.
async function agentPlayer(_ctx: Context, side: "w" | "b", fen: string, legal: string[], history: string) {
const response = await anthropic.messages.parse({
model: "claude-haiku-4-5",
max_tokens: 512,
system: [{ type: "text", text: PROMPTS[side], 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 an illegal move, fall back to a random legal move.
return coerceToLegal(response.parsed_output, legal);
}
export function* chessGame(ctx: Context, gameNumber = 1) {
const game = new Chess();
let moveCount = 0;
yield* ctx.run(publish, buildState(game, undefined, moveCount));
while (!game.isGameOver()) {
// Same call for both players — only the side (and so the prompt) changes.
const { move, reasoning } = yield* ctx.run(
agentPlayer, game.turn(), game.fen(), legalMoves(game), history(game),
);
applyUciMove(game, move);
moveCount++;
yield* ctx.run(publish, buildState(game, move, moveCount, reasoning));
yield* ctx.sleep(MOVE_DELAY_MS); // ← worker terminates here
}
// Each game is its own root promise. Detach the next so replay scope stays
// bounded — every game replays in its own crash domain, in isolation.
yield* ctx.detached(chessGame, gameNumber + 1);
}Why one loop is enough
The game on this page proves it the hard way. Between every single move, the worker running the agents terminates — that’s the ctx.sleep at the end of each turn. The state that lets play resume lives in the promise, written to Postgres as it happens, not in the process that vanished.
We redeployed the server mid-game. The workflow picked up from the last completed move, untouched — no intervention. A deploy, a crash, a pod rescheduled mid-run: the loop doesn’t start over. It comes back up, reads the last promise it kept, and continues from exactly there.
The loop’s progress lived in the promise,
not the process that died.
Runs on your infrastructure
Resonate runs as a single binary on the infrastructure you already operate — one service to run, not a fleet to provision and babysit. It sits inside your stack, not behind an API you have to trust.
Its state lives in your own Postgres. And because that state is just rows, you can look at it directly: the game on this page is running on the five managed pieces below, and every move it has ever made is a row you could read with plain SQL.
SELECT * FROM promises WHERE id LIKE 'chess-game-%';
A protocol, not a product
Resonate is open source under Apache 2.0, and durable execution is a protocol — something you adopt and build on, not a vendor you build around and can’t leave.
There’s a public library of agent skills to start from, and an open Discord where people are already building. You can run it, read it, fork it, or build your own.
the open specification — implement it in any language
We value your privacy
We use strictly necessary cookies to run this site. With your consent we also use Google Analytics to understand traffic. We do not load analytics until you choose to allow it. Privacy Policy.