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.
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.
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>;
}| 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.
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 |