INTEGRITY Dokumentace

Ukládání a synchronizace stavu

Agents poskytují vestavěnou správu stavu s automatickou perzistencí a synchronizací v reálném čase napříč všemi připojenými klienty.

Přehled

Stav v rámci Agenta je:

Stav agenta se ukládá v databázi SQL vestavěné v každé jednotlivé instanci Agent. Se stavem můžete pracovat pomocí vysokoúrovňového this.setState API (doporučeno), které umožňuje synchronizovat stav a spouštět události při jeho změnách, nebo přímým dotazováním databáze pomocí this.sql.

import { Agent } from "agents";

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

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

	addPlayer(name) {
		this.setState({
			...this.state,
			players: [...this.state.players, name],
		});
	}
}
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],
		});
	}
}

Definování počátečního stavu

Použijte initialState vlastnost pro definování výchozích hodnot nových instancí agenta:

export class ChatAgent extends Agent {
	initialState = {
		messages: [],
		settings: { theme: "dark", notifications: true },
		lastActive: 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,
	};
}

Typová bezpečnost

Druhý generický parametr Agent definuje váš typ stavu:

// State is fully typed
export class MyAgent extends Agent {
	initialState = { count: 0 };

	increment() {
		// TypeScript knows this.state is MyState
		this.setState({ count: this.state.count + 1 });
	}
}
// 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 });
	}
}

Když platí počáteční stav

Počáteční stav se aplikuje líně při prvním přístupu, ne při každém probuzení:

  1. Nový agent - initialState se použije a uloží
  2. Stávající agent - Perzistentní stav se načítá z SQLite
  3. Ne initialState definováno - this.state je undefined
class MyAgent extends Agent {
	initialState = { count: 0 };
	async onStart() {
		// Safe to access - returns initialState if new, or persisted state
		console.log("Current count:", this.state.count);
	}
}
class MyAgent extends Agent<Env, { count: number }> {
	initialState = { count: 0 };
	async onStart() {
		// Safe to access - returns initialState if new, or persisted state
		console.log("Current count:", this.state.count);
	}
}

Čtení stavu

Přístup k aktuálnímu stavu přes this.state getter:

class MyAgent extends Agent {
	async onRequest(request) {
		// Read current state
		const { players, status } = this.state;

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

		return Response.json(this.state);
	}
}
class MyAgent extends Agent<
	Env,
	{ players: string[]; status: "waiting" | "playing" | "finished" }
> {
	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 Response.json(this.state);
	}
}

Nedefinovaný stav

Pokud nedefinujete initialState, this.state vrací undefined:

export class MinimalAgent extends Agent {
	// No initialState defined

	async onConnect(connection) {
		if (!this.state) {
			// First time - initialize state
			this.setState({ initialized: true });
		}
	}
}
export class MinimalAgent extends Agent {
	// No initialState defined

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

Aktualizace stavu

Použijte setState() k aktualizaci stavu. Tato akce:

  1. Ukládá do SQLite (trvalé)
  2. Vysílá všem připojeným klientům (kromě připojení, kde shouldSendProtocolMessages vrácen false)
  3. Spouští onStateChanged() (po broadcastu, s nejlepší možnou snahou)
// 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,
});
// 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,
});

Stav musí být serializovatelný

Stav se ukládá jako JSON, proto musí být serializovatelný:

// Good - plain objects, arrays, primitives
this.setState({
	items: ["a", "b", "c"],
	count: 42,
	active: true,
	metadata: { key: "value" },
});

// Bad - functions, classes, circular references
// Functions do not serialize
// Dates become strings, lose methods
// Circular references fail

// For dates, use ISO strings
this.setState({
	createdAt: new Date().toISOString(),
});
// Good - plain objects, arrays, primitives
this.setState({
	items: ["a", "b", "c"],
	count: 42,
	active: true,
	metadata: { key: "value" },
});

// Bad - functions, classes, circular references
// Functions do not serialize
// Dates become strings, lose methods
// Circular references fail

// For dates, use ISO strings
this.setState({
	createdAt: new Date().toISOString(),
});

Reakce na změny stavu

Přepsat onStateChanged() k reakci na změny stavu (oznámení nebo vedlejší efekty):

class MyAgent extends Agent {
	onStateChanged(state, source) {
		console.log("State updated:", state);
		console.log("Updated by:", source === "server" ? "server" : source.id);
	}
}
class MyAgent extends Agent<Env, GameState> {
	onStateChanged(state: GameState, source: Connection | "server") {
		console.log("State updated:", state);
		console.log("Updated by:", source === "server" ? "server" : source.id);
	}
}

Parametr source

source zobrazuje, kdo aktualizaci vyvolal:

Hodnota Význam
"server" Agent s názvem setState()
Connection Klient odeslal stav přes WebSocket

Toto se hodí pro:

class MyAgent extends Agent {
	onStateChanged(state, source) {
		// 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);
		}
	}
}
class MyAgent extends Agent<
	Env,
	{ status: "waiting" | "playing" | "finished" }
> {
	onStateChanged(state: GameState, 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);
		}
	}
}

Běžný vzor: akce řízené klientem

class MyAgent extends Agent {
	onStateChanged(state, source) {
		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,
				),
			});
		}
	}
}
class MyAgent extends Agent<Env, { messages: Message[] }> {
	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,
				),
			});
		}
	}
}

Ověřování aktualizací stavu

Pokud chcete aktualizace stavu validovat nebo odmítat, přepište validateStateChange():

class MyAgent extends Agent {
	validateStateChange(nextState, source) {
		// Example: reject negative scores
		if (nextState.score < 0) {
			throw new Error("score cannot be negative");
		}

		// Example: only allow certain status transitions
		if (this.state.status === "finished" && nextState.status !== "finished") {
			throw new Error("Cannot restart a finished game");
		}
	}
}
class MyAgent extends Agent<Env, GameState> {
	validateStateChange(nextState: GameState, source: Connection | "server") {
		// Example: reject negative scores
		if (nextState.score < 0) {
			throw new Error("score cannot be negative");
		}

		// Example: only allow certain status transitions
		if (this.state.status === "finished" && nextState.status !== "finished") {
			throw new Error("Cannot restart a finished game");
		}
	}
}

Synchronizace stavu na straně klienta

Stav se automaticky synchronizuje s připojenými klienty.

React (useAgent)

import { useAgent } from "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) => {
		agent.setState({
			...agent.state,
			players: [...agent.state.players, name],
		});
	};

	return <div>Players: {agent.state?.players.join(", ")}</div>;
}
import { useAgent } from "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 "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 });
import { AgentClient } from "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 });

Stavový tok

flowchart TD
    subgraph Agent
        S["this.state<br/>(persisted in SQLite)"]
    end
    subgraph Clients
        C1["Client 1"]
        C2["Client 2"]
        C3["Client 3"]
    end
    C1 & C2 & C3 -->|setState| S
    S -->|broadcast via WebSocket| C1 & C2 & C3

Stav z Workflows

Při použití Workflows, můžete aktualizovat stav agenta z kroků workflow:

// In your workflow
class MyWorkflow extends Workflow {
	async run(event, step) {
		// 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;
	}
}
// In your workflow
class MyWorkflow extends Workflow<Env> {
	async run(event: AgentWorkflowEvent, 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;
	}
}

Jde o trvalé operace, které přetrvávají, i když workflow provede nový pokus.

SQL API

Každá jednotlivá instance Agenta má vlastní databázi SQL (SQLite), která běží ve stejném kontextu jako samotný Agent. Díky tomu je vkládání i dotazování dat ve vašem Agentovi prakticky bez latence: Agent nemusí cestovat přes celý kontinent nebo svět, aby přistoupil ke svým vlastním datům.

K SQL API máte přístup v rámci kterékoli metody Agenta přes this.sql. SQL API přijímá template literály:

export class MyAgent extends Agent {
	async onRequest(request) {
		let userId = new URL(request.url).searchParams.get("userId");

		// 'users' is just an example here: you can create arbitrary tables and define your own schemas
		// within each Agent's database using SQL (SQLite syntax).
		let [user] = this.sql`SELECT * FROM users WHERE id = ${userId}`;
		return Response.json(user);
	}
}
export class MyAgent extends Agent {
	async onRequest(request: Request) {
		let userId = new URL(request.url).searchParams.get("userId");

		// 'users' is just an example here: you can create arbitrary tables and define your own schemas
		// within each Agent's database using SQL (SQLite syntax).
		let [user] = this.sql`SELECT * FROM users WHERE id = ${userId}`;
		return Response.json(user);
	}
}

Dotazu můžete také předat argument typu TypeScript, který se použije k odvození typu výsledku:

export class MyAgent extends Agent {
	async onRequest(request) {
		let userId = new URL(request.url).searchParams.get("userId");
		// Supply the type parameter to the query when calling this.sql
		// This assumes the results returns one or more User rows with "id", "name", and "email" columns
		const [user] = this.sql`SELECT * FROM users WHERE id = ${userId}`;
		return Response.json(user);
	}
}
type User = {
	id: string;
	name: string;
	email: string;
};

export class MyAgent extends Agent {
	async onRequest(request: Request) {
		let userId = new URL(request.url).searchParams.get("userId");
		// Supply the type parameter to the query when calling this.sql
		// This assumes the results returns one or more User rows with "id", "name", and "email" columns
		const [user] = this.sql<User>`SELECT * FROM users WHERE id = ${userId}`;
		return Response.json(user);
	}
}

Není nutné určovat typ pole (User[] nebo Array<User>), jako this.sql vždy vrátí pole zadaného typu.

SQL API zpřístupněné agentovi je podobné tomu, v rámci Durable Objects. Stejné dotazy SQL můžete použít i pro databázi Agenta. Vytvářejte tabulky a dotazujte se na data stejně jako u Durable Objects nebo D1.

Osvědčené postupy

Udržujte stav malý

Stav se vysílá všem klientům při každé změně. Pro velká data:

// Bad - storing large arrays in state
initialState = {
  allMessages: [] // Could grow to thousands of items
};

// Good - 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}`;
}

Optimistické aktualizace

Pro citlivě reagující uživatelská rozhraní aktualizujte stav klienta okamžitě:

// Client-side
function sendMessage(text) {
	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
class MyAgent extends Agent {
	onStateChanged(state, source) {
		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,
				),
			});
		}
	}
}
// 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
class MyAgent extends Agent<Env, { messages: Message[] }> {
	onStateChanged(state: GameState, 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,
				),
			});
		}
	}
}

Stav vs SQL

Použití stavu pro Použití SQL pro
Stav UI (načítání, vybrané položky) Historická data
Počítadla v reálném čase Velké kolekce
Data aktivní relace Vztahy
Konfigurace Dotazovatelná data
export class ChatAgent extends Agent {
	// State: current UI state
	initialState = {
		typing: [],
		unreadCount: 0,
		activeUsers: [],
	};

	// SQL: message history
	async getMessages(limit = 100) {
		return this.sql`
      SELECT * FROM messages
      ORDER BY created_at DESC
      LIMIT ${limit}
    `;
	}

	async saveMessage(message) {
		this.sql`
      INSERT INTO messages (id, text, user_id, created_at)
      VALUES (${message.id}, ${message.text}, ${message.userId}, ${Date.now()})
    `;
		// Update state for real-time UI
		this.setState({
			...this.state,
			unreadCount: this.state.unreadCount + 1,
		});
	}
}
export class ChatAgent extends Agent {
	// State: current UI state
	initialState = {
		typing: [],
		unreadCount: 0,
		activeUsers: [],
	};

	// SQL: message history
	async getMessages(limit = 100) {
		return this.sql`
      SELECT * FROM messages
      ORDER BY created_at DESC
      LIMIT ${limit}
    `;
	}

	async saveMessage(message: Message) {
		this.sql`
      INSERT INTO messages (id, text, user_id, created_at)
      VALUES (${message.id}, ${message.text}, ${message.userId}, ${Date.now()})
    `;
		// Update state for real-time UI
		this.setState({
			...this.state,
			unreadCount: this.state.unreadCount + 1,
		});
	}
}

Vyhněte se nekonečným smyčkám

Dávejte pozor, abyste jako reakci na vlastní aktualizace nevyvolali další aktualizace stavu:

// Bad - infinite loop
onStateChanged(state: State) {
  this.setState({ ...state, lastUpdated: Date.now() });
}

// Good - check source
onStateChanged(state: State, source: Connection | "server") {
  if (source === "server") return; // Do not react to own updates
  this.setState({ ...state, lastUpdated: Date.now() });
}

Použití stavu Agenta jako kontextu modelu

V Agentovi můžete kombinovat state a SQL API s jeho schopností volat modely AI k zahrnutí historického kontextu do promptů pro model. Moderní velké jazykové modely (LLM) mají často velmi rozsáhlá kontextová okna (až miliony tokenů), díky čemuž můžete relevantní kontext vložit přímo do promptu.

Můžete například použít vestavěnou databázi SQL Agenta k načtení historie, dotázat se pomocí ní modelu a před dalším voláním modelu do historie přidat výsledek:

export class ReasoningAgent extends Agent {
	async callReasoningModel(prompt) {
		let result = this
			.sql`SELECT * FROM history WHERE user = ${prompt.userId} ORDER BY timestamp DESC LIMIT 1000`;
		let context = [];
		for (const row of result) {
			context.push(row.entry);
		}

		const systemPrompt = prompt.system || "You are a helpful assistant.";
		const userPrompt = `${prompt.user}\n\nUser history:\n${context.join("\n")}`;

		try {
			const response = await this.env.AI.run("@cf/zai-org/glm-4.7-flash", {
				messages: [
					{ role: "system", content: systemPrompt },
					{ role: "user", content: userPrompt },
				],
			});

			// Store the response in history
			this
				.sql`INSERT INTO history (timestamp, user, entry) VALUES (${new Date()}, ${prompt.userId}, ${response.response})`;

			return response.response;
		} catch (error) {
			console.error("Error calling reasoning model:", error);
			throw error;
		}
	}
}
interface Env {
	AI: Ai;
}

export class ReasoningAgent extends Agent<Env> {
	async callReasoningModel(prompt: Prompt) {
		let result = this
			.sql<History>`SELECT * FROM history WHERE user = ${prompt.userId} ORDER BY timestamp DESC LIMIT 1000`;
		let context = [];
		for (const row of result) {
			context.push(row.entry);
		}

		const systemPrompt = prompt.system || "You are a helpful assistant.";
		const userPrompt = `${prompt.user}\n\nUser history:\n${context.join("\n")}`;

		try {
			const response = await this.env.AI.run("@cf/zai-org/glm-4.7-flash", {
				messages: [
					{ role: "system", content: systemPrompt },
					{ role: "user", content: userPrompt },
				],
			});

			// Store the response in history
			this
				.sql`INSERT INTO history (timestamp, user, entry) VALUES (${new Date()}, ${prompt.userId}, ${response.response})`;

			return response.response;
		} catch (error) {
			console.error("Error calling reasoning model:", error);
			throw error;
		}
	}
}

Funguje to proto, že každá instance Agenta má vlastní databázi a stav uložený v této databázi patří výhradně danému Agentovi, ať už jedná jménem jednotlivého uživatele, místnosti nebo kanálu, nebo nástroje pro hloubkový výzkum. Ve výchozím stavu tak nemusíte řešit souběh přístupů ani se kvůli načtení a uložení stavu obracet přes síť na centralizovanou databázi.

Referenční dokumentace API

Vlastnosti

Vlastnost Typ Popis
state State Aktuální stav (getter)
initialState State Výchozí stav pro nové agenty

Metody

Metoda Podpis Popis
setState (state: State) => void Aktualizace stavu, perzistence a vysílání
onStateChanged (state: State, source: Connection | "server") => void Volá se při změně stavu
validateStateChange (nextState: State, source: Connection | "server") => void Ověřte před uložením (v případě zamítnutí vyvolejte výjimku)

Metody kroků workflow

Metoda Popis
step.updateAgentState(state) Nahrazení stavu agenta z workflow
step.mergeAgentState(partial) Sloučit částečný stav z workflow
step.resetAgentState() Resetovat na initialState z workflow

Další kroky

Agents API

Kompletní referenční dokumentace API pro Agents SDK.

Sestavte chatovacího agenta

Sestavte AI chatovacího agenta a nasaďte ho.

WebSockets

Vytvářejte interaktivní agenty se streamováním dat v reálném čase.

Spouštění Workflows

Orchestrujte asynchronní workflows ze svého agenta.