Skip to content

Store and sync state

Last updated View as MarkdownAgent setup

The Agent class installs the State capability and wires it to its WebSocket connections. State is one value per Agent: it is stored in SQLite, survives restarts and hibernation, and syncs to every connected client. This page covers the Agent API. For how state is stored and synced, refer to the State capability.

Initial state

import { Agent } from "agents";

export class GameAgent extends Agent {
	initialState = { players: [], status: "waiting" };

	// Runs before a change is stored. Throw to reject it.
	validateStateChange(next, source) {
		if (source !== "server" && next.status !== this.state.status) {
			throw new Error("Only the server can change the game status");
		}
	}

	// Runs after a change is stored and broadcast.
	onStateChanged(state, source) {
		if (source !== "server" && state.players.length >= 2) {
			this.setState({ ...state, status: "playing" });
		}
	}

	addPlayer(name) {
		this.setState({ ...this.state, players: [...this.state.players, name] });
	}
}
import { Agent, type Connection } from "agents";

type GameState = {
	players: string[];
	status: "waiting" | "playing";
};

export class GameAgent extends Agent<Env, GameState> {
	initialState: GameState = { players: [], status: "waiting" };

	// Runs before a change is stored. Throw to reject it.
	validateStateChange(next: GameState, source: Connection | "server") {
		if (source !== "server" && next.status !== this.state.status) {
			throw new Error("Only the server can change the game status");
		}
	}

	// Runs after a change is stored and broadcast.
	onStateChanged(state: GameState, source: Connection | "server") {
		if (source !== "server" && state.players.length >= 2) {
			this.setState({ ...state, status: "playing" });
		}
	}

	addPlayer(name: string) {
		this.setState({ ...this.state, players: [...this.state.players, name] });
	}
}

initialState applies the first time an Agent reads its state. After that, this.state returns the stored value. State must be JSON-serializable.

Sync with clients

useAgent receives every change and can push its own with setState(). A client change goes through validateStateChange() before it is stored:

import { useAgent } from "agents/react";

export function Lobby() {
	const agent = useAgent<{ players: string[]; status: string }>({
		agent: "GameAgent",
		name: "game-1",
	});
	return <p>{agent.state?.players.length ?? 0} players</p>;
}

API

Member Description
initialState Default value for a new Agent
this.state The current value
this.setState(state) Stores the value and broadcasts it to clients
validateStateChange(next, source) Synchronous check before a change is stored. Throw to reject it
onStateChanged(state, source) Runs after a change. source is "server" or the client's Connection

In onStateChanged(), ignore changes whose source is "server" when you call setState() from inside it, or you will loop.

For data that does not need to reach clients, use SQLite with this.sql. For values a client should only read, make its connection readonly.

Update state from a Workflow

A Workflow step can change its Agent's state. These calls are durable, so they are not repeated when the Workflow retries:

Method Description
step.updateAgentState(state) Replaces the state
step.mergeAgentState(partial) Merges fields into the state
step.resetAgentState() Resets the state to initialState

Next steps

State capability

How state is validated, stored, and synced on any Durable Object.

WebSockets

Handle connections and messages on an Agent.

Was this helpful?