Readable State Property
Both useAgent (React) and AgentClient (vanilla JS) now expose a state property that reflects the current agent state in real-time. Previously, accessing state required manual tracking through the onStateUpdate callback. The new state property is reactive—components re-render automatically when state changes from either server updates or client-side setState() calls.
For React components, use agent.state directly without separate useState setup:
const agent = useAgent<GameAgent, GameState>({ agent: "game-agent", name: "room-123" });
return <div>Score: {agent.state?.score}</div>;
agent.setState({ ...agent.state, score: (agent.state?.score ?? 0) + 10 });
For vanilla JavaScript, AgentClient state is populated on server connect (from initialState) or when setState() is called:
const client = new AgentClient<GameAgent>({ agent: "game-agent", name: "room-123", host: "your-worker.workers.dev" });
console.log(client.state); // { score: 100 }
The existing onStateUpdate callback remains unchanged and continues to work alongside the new property.
Idempotent Schedule Deduplication
schedule() now prevents duplicate rows from accumulating across Durable Object restarts with a new idempotent option. Cron schedules are idempotent by default—calling schedule("0 * * * *", "tick") multiple times with identical parameters returns the existing row instead of creating a new one.
Delayed and date-scheduled types support opt-in idempotency:
async onStart() {
// Safe across restarts—only one row is created
await this.schedule(60, "maintenance", undefined, { idempotent: true });
}
The SDK emits helpful warnings to catch common mistakes:
- A
console.warnappears whenschedule()is called insideonStart()without{ idempotent: true } - If 10+ stale one-shot rows accumulate for the same callback, the SDK warns via console and a
schedule:duplicate_warningdiagnostics event
Full TypeScript Inference for AgentClient
AgentClient now accepts an optional agent type parameter for complete type inference on RPC calls, matching the typed experience of useAgent:
const client = new AgentClient<MyAgent>({ agent: "my-agent", host: window.location.host });
// Method names autocomplete, args and return types inferred
const value = await client.call("getValue");
// New stub proxy for direct RPC-style calls
await client.stub.getValue();
await client.stub.add(1, 2);
// State is automatically typed in callbacks
const client = new AgentClient<MyAgent>({
agent: "my-agent",
host: window.location.host,
onStateUpdate: (state) => { /* state typed as MyAgent's state type */ }
});
The RPC type utilities (AgentMethods, AgentStub, RPCMethods) are now exported from agents/client for advanced typing scenarios.
Additional Improvements
Zod 4 Migration: The agents, @cloudflare/ai-chat, and @cloudflare/codemode packages now require zod ^4.0.0; Zod v3 is no longer supported.
@cloudflare/ai-chat Fixes:
- Turn serialization now queues work to prevent concurrent streaming of user requests, tool continuations, and
saveMessages() - Clicking stop during an active stream no longer splits assistant messages into multiple entries
- Orphaned client IDs no longer leak into persistent storage
keepAlive() Stabilization: keepAlive() and keepAliveWhile() are no longer experimental. The functions now use lightweight in-memory ref counting instead of schedule rows, allowing multiple concurrent callers to share a single alarm cycle.
TanStack AI Integration: A new entry point @cloudflare/codemode/tanstack-ai adds support for TanStack AI's chat() as an alternative to the Vercel AI SDK's streamText().