> ## 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.

# State Management

> Built-in state management with automatic persistence and real-time synchronization across all connected clients

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

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
// 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:

<Steps>
  <Step title="New agent">
    `initialState` is used and persisted
  </Step>

  <Step title="Existing agent">
    Persisted state is loaded from SQLite
  </Step>

  <Step title="No initialState defined">
    `this.state` is `undefined`
  </Step>
</Steps>

```typescript theme={null}
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:

```typescript theme={null}
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`:

```typescript theme={null}
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:

<Steps>
  <Step title="Saves to SQLite">
    State is persisted to storage
  </Step>

  <Step title="Broadcasts to all clients">
    All connected WebSocket clients receive the update
  </Step>

  <Step title="Triggers onStateChanged()">
    After broadcast; best-effort notification
  </Step>
</Steps>

```typescript theme={null}
// 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:

<CodeGroup>
  ```typescript Good - Serializable theme={null}
  // Plain objects, arrays, primitives
  this.setState({
    items: ["a", "b", "c"],
    count: 42,
    active: true,
    metadata: { key: "value" }
  });
  ```

  ```typescript Bad - Non-Serializable theme={null}
  // Functions, classes, circular references
  this.setState({
    callback: () => {}, // Functions don't serialize
    date: new Date(), // Becomes string, loses methods
    self: this // Circular reference
  });
  ```

  ```typescript Dates - Use ISO Strings theme={null}
  // For dates, use ISO strings
  this.setState({
    createdAt: new Date().toISOString()
  });
  ```
</CodeGroup>

## Responding to State Changes

Override `onStateChanged()` to react when state changes (notifications/side-effects):

```typescript theme={null}
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**

```typescript theme={null}
validateStateChange(nextState: GameState, source: Connection | "server") {
  // Example: reject negative scores
  if (nextState.score < 0) {
    throw new Error("score cannot be negative");
  }
}
```

<Note>
  `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.
</Note>

### The source Parameter

The `source` tells you who triggered the update:

| Value        | Meaning                             |
| ------------ | ----------------------------------- |
| `"server"`   | Agent called `setState()`           |
| `Connection` | A 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

```typescript theme={null}
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

```typescript theme={null}
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)

```typescript theme={null}
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)

```typescript theme={null}
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:

```typescript theme={null}
// 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:

<CodeGroup>
  ```typescript Bad - Large State theme={null}
  // Storing large arrays in state
  initialState = {
    allMessages: []  // Could grow to thousands of items
  };
  ```

  ```typescript Good - Lightweight State theme={null}
  // Store in SQL, keep state light
  initialState = {
    messageCount: 0,
    lastMessageId: null
  };

  // Query SQL for full data
  async getMessages(limit = 50) {
    return this.sql`SELECT * FROM messages ORDER BY created_at DESC LIMIT ${limit}`;
  }
  ```
</CodeGroup>

### Optimistic Updates

For responsive UIs, update client state immediately:

```typescript theme={null}
// 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 For                      | Use SQL For       |
| ---------------------------------- | ----------------- |
| UI state (loading, selected items) | Historical data   |
| Real-time counters                 | Large collections |
| Active session data                | Relationships     |
| Configuration                      | Queryable data    |

### Avoid Infinite Loops

Be careful not to trigger state updates in response to your own updates:

<CodeGroup>
  ```typescript Bad - Infinite Loop theme={null}
  onStateChanged(state: State) {
    this.setState({ ...state, lastUpdated: Date.now() });
  }
  ```

  ```typescript Good - Check Source theme={null}
  onStateChanged(state: State, source: Connection | "server") {
    if (source === "server") return;  // Don't react to own updates
    this.setState({ ...state, lastUpdated: Date.now() });
  }
  ```
</CodeGroup>

## API Reference

### Properties

<ParamField path="state" type="State">
  Current state (getter)
</ParamField>

<ParamField path="initialState" type="State">
  Default state for new agents
</ParamField>

### Methods

<ParamField path="setState" type="(state: State) => void">
  Update state, persist, and broadcast
</ParamField>

<ParamField path="onStateChanged" type="(state: State, source: Connection | 'server') => void">
  Called after state is persisted and broadcast
</ParamField>

### Workflow Step Methods

<ParamField path="step.updateAgentState" type="(state) => Promise<void>">
  Replace agent state from workflow
</ParamField>

<ParamField path="step.mergeAgentState" type="(partial) => Promise<void>">
  Merge partial state from workflow
</ParamField>

<ParamField path="step.resetAgentState" type="() => Promise<void>">
  Reset to `initialState` from workflow
</ParamField>
