Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt

Use this file to discover all available pages before exploring further.

Agents provide built-in state management with automatic persistence and real-time synchronization across all connected clients.

Overview

Agent state is:
  • Persistent - Automatically saved to SQLite, survives restarts and hibernation
  • Synchronized - Changes broadcast to all connected WebSocket clients instantly
  • Bidirectional - Both server and clients can update state
  • Type-safe - Full TypeScript support with generics
import { Agent } from "agents";

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

export class GameAgent extends Agent<Env, GameState> {
  // Default state for new agents
  initialState: GameState = {
    players: [],
    score: 0,
    status: "waiting"
  };

  // React to state changes
  onStateChanged(state: GameState, source: Connection | "server") {
    if (source !== "server" && state.players.length >= 2) {
      // Client added a player, start the game
      this.setState({ ...state, status: "playing" });
    }
  }

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

Defining Initial State

Use the initialState property to define default values for new agent instances:
type State = {
  messages: Message[];
  settings: UserSettings;
  lastActive: string | null;
};

export class ChatAgent extends Agent<Env, State> {
  initialState: State = {
    messages: [],
    settings: { theme: "dark", notifications: true },
    lastActive: null
  };
}

Type Safety

The second generic parameter to Agent<Env, State> defines your state type:
// State is fully typed
export class MyAgent extends Agent<Env, MyState> {
  initialState: MyState = { count: 0 };

  increment() {
    // TypeScript knows this.state is MyState
    this.setState({ count: this.state.count + 1 });
  }
}

When Initial State Applies

Initial state is applied lazily on first access, not on every wake:
1

New agent

initialState is used and persisted
2

Existing agent

Persisted state is loaded from SQLite
3

No initialState defined

this.state is undefined
async onStart() {
  // Safe to access - returns initialState if new, or persisted state
  console.log("Current count:", this.state.count);
}

Reading State

Access the current state via the this.state getter:
async onRequest(request: Request) {
  // Read current state
  const { players, status } = this.state;

  if (status === "waiting" && players.length < 2) {
    return new Response("Waiting for players...");
  }

  return new Response(JSON.stringify(this.state));
}

Undefined State

If you don’t define initialState, this.state returns undefined:
export class MinimalAgent extends Agent<Env> {
  // No initialState defined

  async onConnect(connection: Connection) {
    if (!this.state) {
      // First time - initialize state
      this.setState({ initialized: true });
    }
  }
}

Updating State

Use setState() to update state. This:
1

Saves to SQLite

State is persisted to storage
2

Broadcasts to all clients

All connected WebSocket clients receive the update
3

Triggers onStateChanged()

After broadcast; best-effort notification
// Replace entire state
this.setState({
  players: ["Alice", "Bob"],
  score: 0,
  status: "playing"
});

// Update specific fields (spread existing state)
this.setState({
  ...this.state,
  score: this.state.score + 10
});

State Must Be Serializable

State is stored as JSON, so it must be serializable:
// Plain objects, arrays, primitives
this.setState({
  items: ["a", "b", "c"],
  count: 42,
  active: true,
  metadata: { key: "value" }
});

Responding to State Changes

Override onStateChanged() to react when state changes (notifications/side-effects):
onStateChanged(state: GameState, source: Connection | "server") {
  console.log("State updated:", state);
  console.log("Updated by:", source === "server" ? "server" : source.id);
}

Validating State Updates

If you want to validate or reject state updates, override validateStateChange():
  • Runs before persistence and broadcast
  • Must be synchronous
  • Throwing aborts the update
validateStateChange(nextState: GameState, source: Connection | "server") {
  // Example: reject negative scores
  if (nextState.score < 0) {
    throw new Error("score cannot be negative");
  }
}
onStateChanged() is not intended for validation; it is a notification hook and should not block broadcasts.Migration note: onStateChanged replaces the deprecated onStateUpdate (server-side hook). If you’re using onStateUpdate on your agent class, rename it to onStateChanged — the signature and behavior are identical. A console warning will fire once per class until you rename it.

The source Parameter

The source tells you who triggered the update:
ValueMeaning
"server"Agent called setState()
ConnectionA client pushed state via WebSocket
This is useful for:
  • Avoiding infinite loops (don’t react to your own updates)
  • Validating client input
  • Triggering side effects only on client actions
onStateChanged(state: State, source: Connection | "server") {
  // Ignore server-initiated updates
  if (source === "server") return;

  // A client updated state - validate and process
  const connection = source;
  console.log(`Client ${connection.id} updated state`);

  // Maybe trigger something based on the change
  if (state.status === "submitted") {
    this.processSubmission(state);
  }
}

Common Pattern: Client-Driven Actions

onStateChanged(state: State, source: Connection | "server") {
  if (source === "server") return;

  // Client added a message
  const lastMessage = state.messages[state.messages.length - 1];
  if (lastMessage && !lastMessage.processed) {
    // Process and update
    this.setState({
      ...state,
      messages: state.messages.map(m =>
        m.id === lastMessage.id ? { ...m, processed: true } : m
      )
    });
  }
}

Client-Side State Sync

State synchronizes automatically with connected clients.

React (useAgent)

import { useAgent } from "@cloudflare/agents/react";

function GameUI() {
  const agent = useAgent({
    agent: "game-agent",
    name: "room-123",
    onStateUpdate: (state, source) => {
      console.log("State updated:", state);
    }
  });

  // Push state to agent
  const addPlayer = (name: string) => {
    agent.setState({
      ...agent.state,
      players: [...agent.state.players, name]
    });
  };

  return <div>Players: {agent.state?.players.join(", ")}</div>;
}

Vanilla JS (AgentClient)

import { AgentClient } from "@cloudflare/agents/client";

const client = new AgentClient({
  agent: "game-agent",
  name: "room-123",
  onStateUpdate: (state) => {
    document.getElementById("score").textContent = state.score;
  }
});

// Push state update
client.setState({ ...client.state, score: 100 });

State from Workflows

When using Workflows, you can update agent state from workflow steps:
// In your workflow
async run(event: AgentWorkflowEvent<Params>, step: AgentWorkflowStep) {
  // Replace entire state
  await step.updateAgentState({ status: "processing", progress: 0 });

  // Merge partial updates (preserves other fields)
  await step.mergeAgentState({ progress: 50 });

  // Reset to initialState
  await step.resetAgentState();

  return result;
}
These are durable operations - they persist even if the workflow retries.

Patterns and Best Practices

Keep State Small

State is broadcast to all clients on every change. For large data:
// Storing large arrays in state
initialState = {
  allMessages: []  // Could grow to thousands of items
};

Optimistic Updates

For responsive UIs, update client state immediately:
// Client-side
function sendMessage(text: string) {
  const optimisticMessage = {
    id: crypto.randomUUID(),
    text,
    pending: true
  };

  // Update immediately
  agent.setState({
    ...agent.state,
    messages: [...agent.state.messages, optimisticMessage]
  });

  // Server will confirm/update
}

// Server-side
onStateChanged(state: State, source: Connection | "server") {
  if (source === "server") return;

  const pendingMessages = state.messages.filter(m => m.pending);
  for (const msg of pendingMessages) {
    // Validate and confirm
    this.setState({
      ...state,
      messages: state.messages.map(m =>
        m.id === msg.id ? { ...m, pending: false, timestamp: Date.now() } : m
      )
    });
  }
}

State vs SQL

Use State ForUse SQL For
UI state (loading, selected items)Historical data
Real-time countersLarge collections
Active session dataRelationships
ConfigurationQueryable data

Avoid Infinite Loops

Be careful not to trigger state updates in response to your own updates:
onStateChanged(state: State) {
  this.setState({ ...state, lastUpdated: Date.now() });
}

API Reference

Properties

state
State
Current state (getter)
initialState
State
Default state for new agents

Methods

setState
(state: State) => void
Update state, persist, and broadcast
onStateChanged
(state: State, source: Connection | 'server') => void
Called after state is persisted and broadcast

Workflow Step Methods

step.updateAgentState
(state) => Promise<void>
Replace agent state from workflow
step.mergeAgentState
(partial) => Promise<void>
Merge partial state from workflow
step.resetAgentState
() => Promise<void>
Reset to initialState from workflow

Build docs developers (and LLMs) love