WebSockets (2)·续

The default onError implementation logs the error and rethrows it. Override it to add custom error handling, reporting, or recovery logic.

Message types

Type Description
string Text message (typically JSON)
ArrayBuffer Binary data
ArrayBufferView Typed array view of binary data

Hibernation

Agents support hibernation — they can sleep when inactive and wake when needed. This saves resources while maintaining WebSocket connections.

Enabling hibernation

Hibernation is enabled by default. To disable:

export class AlwaysOnAgent extends Agent {
    static options = { hibernate: false };
}
export class AlwaysOnAgent extends Agent {
    static options = { hibernate: false };
}

How hibernation works

  1. Agent is active, handling connections
  2. After a period of inactivity with no messages, the agent hibernates (sleeps)
  3. WebSocket connections remain open (handled by Cloudflare)
  4. When a message arrives, the agent wakes up
  5. onMessage is called as normal

What persists across hibernation

Persists Does not persist
this.state (agent state) In-memory variables
connection.state Timers/intervals
SQLite data (this.sql) Promises in flight
Connection metadata Local caches

Store important data in this.state or SQLite, not in class properties:

export class MyAgent extends Agent {
    initialState = { counter: 0 };

// Do not do this - lost on hibernation
    localCounter = 0;

onMessage(connection, message) {
        // Persists across hibernation
        this.setState({ counter: this.state.counter + 1 });

// Lost after hibernation
        this.localCounter++;
    }
}
export class MyAgent extends Agent<Env, { counter: number }> {
    initialState = { counter: 0 };

// Do not do this - lost on hibernation
    private localCounter = 0;

onMessage(connection: Connection, message: WSMessage) {
        // Persists across hibernation
        this.setState({ counter: this.state.counter + 1 });

// Lost after hibernation
        this.localCounter++;
    }
}

Common patterns

Presence tracking

Track who is online using per-connection state. Connection state is automatically cleaned up when users disconnect:

export class PresenceAgent extends Agent {
    onConnect(connection, ctx) {
        const url = new URL(ctx.request.url);
        const name = url.searchParams.get("name") || "Anonymous";

connection.setState({
            name,
            joinedAt: Date.now(),
            lastSeen: Date.now(),
        });

// Send current presence to new user
        connection.send(
            JSON.stringify({
                type: "presence",
                users: this.getPresence(),
            }),
        );

// Notify others that someone joined
        this.broadcastPresence();
    }

onClose(connection) {
        // No manual cleanup needed - connection state is automatically gone
        this.broadcastPresence();
    }

onMessage(connection, message) {
        if (message === "ping") {
            connection.setState((prev) => ({
                ...prev,
                lastSeen: Date.now(),
            }));
            connection.send("pong");
        }
    }

getPresence() {
        const users = {};
        for (const conn of this.getConnections()) {
            if (conn.state) {
                users[conn.id] = {
                    name: conn.state.name,
                    lastSeen: conn.state.lastSeen,
                };
            }
        }
        return users;
    }

broadcastPresence() {
        this.broadcast(
            JSON.stringify({
                type: "presence",
                users: this.getPresence(),
            }),
        );
    }
}
type UserState = {
    name: string;
    joinedAt: number;
    lastSeen: number;
};

export class PresenceAgent extends Agent {
    onConnect(connection: Connection<UserState>, ctx: ConnectionContext) {
        const url = new URL(ctx.request.url);
        const name = url.searchParams.get("name") || "Anonymous";

connection.setState({
            name,
            joinedAt: Date.now(),
            lastSeen: Date.now(),
        });

// Send current presence to new user
        connection.send(
            JSON.stringify({
                type: "presence",
                users: this.getPresence(),
            }),
        );

// Notify others that someone joined
        this.broadcastPresence();
    }

onClose(connection: Connection) {
        // No manual cleanup needed - connection state is automatically gone
        this.broadcastPresence();
    }

onMessage(connection: Connection<UserState>, message: WSMessage) {
        if (message === "ping") {
            connection.setState((prev) => ({
                ...prev!,
                lastSeen: Date.now(),
            }));
            connection.send("pong");
        }
    }

private getPresence() {
        const users: Record<string, { name: string; lastSeen: number }> = {};
        for (const conn of this.getConnections<UserState>()) {
            if (conn.state) {
                users[conn.id] = {
                    name: conn.state.name,
                    lastSeen: conn.state.lastSeen,
                };
            }
        }
        return users;
    }

private broadcastPresence() {
        this.broadcast(
            JSON.stringify({
                type: "presence",
                users: this.getPresence(),
            }),
        );
    }
}

Chat room with broadcast

export class ChatRoom extends Agent {
    onConnect(connection, ctx) {
        const url = new URL(ctx.request.url);
        const username = url.searchParams.get("username") || "Anonymous";

connection.setState({ username });

// Notify others
        this.broadcast(
            JSON.stringify({
                type: "join",
                user: username,
                timestamp: Date.now(),
            }),
            [connection.id], // Do not send to the joining user
        );
    }

onMessage(connection, message) {
        if (typeof message !== "string") return;

const { username } = connection.state;

this.broadcast(
            JSON.stringify({
                type: "message",
                user: username,
                text: message,
                timestamp: Date.now(),
            }),
        );
    }

onClose(connection) {
        const { username } = connection.state || {};
        if (username) {
            this.broadcast(
                JSON.stringify({
                    type: "leave",
                    user: username,
                    timestamp: Date.now(),
                }),
            );
        }
    }
}
type Message = {
    type: "message" | "join" | "leave";
    user: string;
    text?: string;
    timestamp: number;
};

export class ChatRoom extends Agent {
    onConnect(connection: Connection, ctx: ConnectionContext) {
        const url = new URL(ctx.request.url);
        const username = url.searchParams.get("username") || "Anonymous";

connection.setState({ username });

// Notify others
        this.broadcast(
            JSON.stringify({
                type: "join",
                user: username,
                timestamp: Date.now(),
            } satisfies Message),
            [connection.id], // Do not send to the joining user
        );
    }

onMessage(connection: Connection, message: WSMessage) {
        if (typeof message !== "string") return;

const { username } = connection.state as { username: string };

this.broadcast(
            JSON.stringify({
                type: "message",
                user: username,
                text: message,
                timestamp: Date.now(),
            } satisfies Message),
        );
    }

onClose(connection: Connection) {
        const { username } = (connection.state as { username: string }) || {};
        if (username) {
            this.broadcast(
                JSON.stringify({
                    type: "leave",
                    user: username,
                    timestamp: Date.now(),
                } satisfies Message),
            );
        }
    }
}

Suppressing protocol messages

By default, agents send JSON text frames (identity, state sync, MCP server lists) to every connection. Override shouldSendProtocolMessages to suppress them for specific connections — for example, binary-only clients that cannot handle JSON text frames:

export class IoTAgent extends Agent {
    shouldSendProtocolMessages(connection, ctx) {
        const url = new URL(ctx.request.url);
        return url.searchParams.get("protocol") !== "binary";
    }
}
export class IoTAgent extends Agent {
    shouldSendProtocolMessages(
        connection: Connection,
        ctx: ConnectionContext,
    ): boolean {
        const url = new URL(ctx.request.url);
        return url.searchParams.get("protocol") !== "binary";
    }
}

When this returns false, the connection does not receive identity, state, or MCP server list frames — neither on connect nor via broadcasts. The connection can still send and receive regular messages, use RPC, and participate in all non-protocol communication.

Use isConnectionProtocolEnabled(connection) to check the status of any connection at runtime.

Agent properties

These properties are available on this inside any Agent method:

Property Type Description
this.name string The instance name of this agent
this.state State The current agent state (lazy-loaded from SQLite)
this.env Env Worker environment bindings
this.ctx DurableObjectState Durable Object context (storage, alarms, etc.)
this.sql template tag SQL template tag for executing queries against the agent's SQLite storage
this.mcp MCPClientManager MCP client manager for connecting to external MCP servers

Connecting from clients

For browser connections, use the Agents client SDK:

  • Vanilla JS: AgentClient from agents/client
  • React: useAgent hook from agents/react

Refer to Client SDK for full documentation.

Next steps

State synchronization

Sync state between agents and clients.

Callable methods

RPC over WebSockets for method calls.

Cross-domain authentication

Secure WebSocket connections across domains.

Was this helpful?

YesNo

On this page

WebSockets (2)·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/agents/runtime/communication/websockets/#page","headline":"WebSockets","description":"Handle real-time WebSocket connections, messages, broadcasts, and lifecycle hooks in the Agents SDK.","url":"https://developers.cloudflare.com/agents/runtime/communication/websockets/","inLanguage":"en","image":"https://developers.cloudflare.com/agents/runtime/communication/websockets/og.png?v=b59d4260b0dd4fe9","dateModified":"2026-06-03","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Give an agent a catalog of on-demand instructions, resources, and scripts with agents/skills, activated by the model only when a task matches. title: Agent Skills image: https://developers.cloudflare.com/agents/runtime/execution/agent-skills/og.png?v=737c4b720e609086


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/agents/llms.txt
Use this file to discover all available pages before exploring further.