Workers Issues provides built-in error monitoring for Cloudflare Workers. It detects production failures and groups related failures into issues without an SDK or application wrapper.
The Cloudflare API MCP server now sends these issues as MCP Events ↗︎, the first event it exposes. MCP clients discover cloudflare.alert.workers_observability_real_time_issue with events/list and subscribe with events/subscribe, scoped to an account or a single Worker. When Issues detects a new or recurring issue, the server posts a signed event to the client's webhook, so an agent can start investigating without polling.
To use it, enable Issues on your Worker and connect https://mcp.cloudflare.com/mcp with the Notifications Write and Workers Observability Write permissions to a client that supports MCP Events webhooks.
The Cloudflare Agents SDK now provides a browserTool that gives your agent a Browser Run browser it can return to on later turns. Your agent can open a website, inspect a page, and come back to it with its tabs, cookies, and login session intact while the browser session remains alive.
When you give browserTool to your agent, the model writes code that drives the browser through the Chrome DevTools Protocol (CDP). Screenshots return as images that an image-capable model can inspect and your UI can display.
Each Pi conversation gets its own active tab by default. Cookies and logins remain shared within the browser.
The browser stays alive between turns, including when the Durable Object hibernates. Its default idle timeout is 10 minutes. If the session expires or crashes, the tool creates a fresh browser and reports restarted: true. The old tabs and login state are gone.
You can also use the persistent browser with the AI SDK and TanStack AI adapters, or connect another harness through the public Browser and CDP APIs.
Refer to persistent browser tools for the complete Pi Durable example, required bindings, and behavior when a tool call is interrupted.
The Cloudflare Agents SDK now provides a web_fetch tool that reads webpages, PDFs, and supported Office documents, using Workers AI Markdown Conversion to turn them into text the model can read. The tool requests Markdown directly from sites that support Markdown for Agents and formats JSON for the model.
When you give the web_fetch tool to your agent, the model provides a URL and receives back the page content, along with the final URL, HTTP status, and content type. Long pages come back in smaller sections, with an offset the model can use to continue reading. You control which hosts the agent can fetch, response size, timeouts, and redirects.
When you also provide the web_search tool to your agent, the model is told to use web_fetch to read the pages that web_search returns.
The web_fetch tool works with any harness. The SDK includes AI SDK and TanStack AI adapters. If you use a different harness, or need to fetch a page without calling a model, use fetchWeb(), windowWebFetchPage(), and renderWebFetchPage() from agents/webfetch. Direct fetching does not require a Durable Object or browser.
For setup, examples, and request controls, refer to Web fetch.
The Agents SDK now supports OpenCode 2.0 ↗︎ through the experimental OpenCodeHarness. Run OpenCode inside a plain Durable Object, with sessions stored in SQLite and Lifecycle jobs that wake interrupted work after eviction.
The agents/models/opencode provider connects OpenCode to Workers AI through the AI binding. OpenCode plugins extend the harness with custom tools, remote MCP servers, and skills. Refer to OpenCode's Cloudflare SDK documentation ↗︎ for the embedded host API.
Install Agents SDK 0.28.0 or later with the OpenCode packages:
npm i agents@latest @opencode/sdk@2 @opencode/plugin@2
The OpenCode harness guide includes the workerTools plugin, Wrangler configuration, and session APIs. The working example ↗︎ adds a browser UI, WebSocket streaming, and Workspace tools from @cloudflare/computer for files and code execution.
When you give the web_search tool to your agent, the model provides a query and max results, and receives back a short numbered list of titles, URLs, and trimmed descriptions. When you also provide the web_fetch tool to your agent, the model is told to use it to read the pages that the web_search tool returns.
The SDK also includes AI SDK and TanStack AI adapters. To search without a model, such as from a scheduled job, use createAIWebSearch() or createHTTPWebSearch() from agents/websearch.
For setup, options, and error handling, refer to Web search.
The Agents SDK now provides first-class support for building agents using the Pi harness. You can build long-running agents using the combination of Pi 1.0 ↗︎, Pi Durable ↗︎, and the new PiHarness class that the Cloudflare Agents SDK provides, ensuring your agent's work is durably persisted, even if interrupted mid-turn.
Built with Earendil ↗︎, this integration is our first step toward first-class support for third-party agent harnesses on Cloudflare.
PiHarness is a new "Lifecycle capability" provided by the Cloudflare Agents SDK. Pi Durable provides the agent harness and the Lifecycle is responsible for keeping the agent running in the Durable Object. The Lifecycle is a core concept in the Agents SDK ensuring that long-running work can run in a Durable Object, surviving restarts, crashes, and network issues. We will share more on Lifecycle capabilities in the near future.
Install
npm i agents@latest @earendil-works/pi-durable @earendil-works/pi-ai
The agents/models/pi-ai entry point supports AI Gateway and Workers AI models, so you can get started with Cloudflare models right away or use your existing Pi AI provider.
Add tools with extensions
Both tools and system prompt sections are provided to the Pi Harness via extensions.
import { Type } from "@earendil-works/pi-ai";import { skills } from "agents/harness/pi";const WordCount = Type.Object({ text: Type.String() });const wordCount = { name: "word_count", description: "Count the words in a text.", parameters: WordCount, replay: "safe", async execute({ text }) { const words = text.split(/\s+/).filter(Boolean).length; return { content: [{ type: "text", text: String(words) }] }; },};// In the harness factory, before Harness.open():registry.install({ name: "editor", sections: [ { key: "preamble", render: () => "You are an editor.", tag: false }, ], tools: [wordCount],});registry.install(await skills(sources));
import { Type } from "@earendil-works/pi-ai";import type { ToolRegistration } from "@earendil-works/pi-durable";import { skills } from "agents/harness/pi";const WordCount = Type.Object({ text: Type.String() });const wordCount: ToolRegistration<typeof WordCount> = { name: "word_count", description: "Count the words in a text.", parameters: WordCount, replay: "safe", async execute({ text }) { const words = text.split(/\s+/).filter(Boolean).length; return { content: [{ type: "text", text: String(words) }] }; },};// In the harness factory, before Harness.open():registry.install({ name: "editor", sections: [ { key: "preamble", render: () => "You are an editor.", tag: false }, ], tools: [wordCount],});registry.install(await skills(sources));
For more information on creating and configuring extensions, refer to Extensions.
@cloudflare/workers-oauth-provider ↗︎ is now v1, with a new split API. One Worker acts as the authorization server: it signs users in and issues tokens. Your MCP server acts as the resource server, and can run in another Worker. It validates each token with the authorization server over a Service Binding, without crossing the public Internet.
A migration skill ↗︎ ships in the npm package so a coding agent can perform the upgrade.
The split API
import { OAuthAuthorizationServer, OAuthResourceServer, insufficientScope,} from "@cloudflare/workers-oauth-provider";import { WorkerEntrypoint } from "cloudflare:workers";// auth-server Worker: signs users in and issues tokens for both MCP servers.const authorizationServer = new OAuthAuthorizationServer({ issuer: "https://auth.example.com", resources: [ "https://calendar.example.com/mcp", "https://drive.example.com/mcp", ], scopesSupported: ["calendar:read", "calendar:write", "offline_access"], clientIdMetadataDocumentEnabled: true,});export class AuthServer extends WorkerEntrypoint { fetch(request) { if (new URL(request.url).pathname === "/authorize") { return showConsent(request, this.env); } return authorizationServer.fetch(request, this.env, this.ctx); } validateToken(resource, token) { return authorizationServer.validateToken(resource, token, this.env); }}// calendar MCP Worker: checks tokens with AuthServer over a Service Binding.export const calendar = new OAuthResourceServer({ resourceMetadata: { resource: "https://calendar.example.com/mcp", authorization_servers: ["https://auth.example.com"], }, requiredScopes: ["calendar:read"], validateToken: (env) => env.AUTH_SERVER.validateToken, handler: { fetch(request, env, ctx) { if ( request.method === "POST" && !ctx.auth.scope.includes("calendar:write") ) { return insufficientScope(ctx.auth, ["calendar:read", "calendar:write"]); } return handleMcp(request, ctx.props); }, },});
import { OAuthAuthorizationServer, OAuthResourceServer, insufficientScope,} from "@cloudflare/workers-oauth-provider";import { WorkerEntrypoint } from "cloudflare:workers";// auth-server Worker: signs users in and issues tokens for both MCP servers.const authorizationServer = new OAuthAuthorizationServer<Env>({ issuer: "https://auth.example.com", resources: [ "https://calendar.example.com/mcp", "https://drive.example.com/mcp", ], scopesSupported: ["calendar:read", "calendar:write", "offline_access"], clientIdMetadataDocumentEnabled: true,});export class AuthServer extends WorkerEntrypoint<Env> { fetch(request: Request) { if (new URL(request.url).pathname === "/authorize") { return showConsent(request, this.env); } return authorizationServer.fetch(request, this.env, this.ctx); } validateToken(resource: string, token: string) { return authorizationServer.validateToken(resource, token, this.env); }}// calendar MCP Worker: checks tokens with AuthServer over a Service Binding.export const calendar = new OAuthResourceServer<Env, AuthProps>({ resourceMetadata: { resource: "https://calendar.example.com/mcp", authorization_servers: ["https://auth.example.com"], }, requiredScopes: ["calendar:read"], validateToken: (env) => env.AUTH_SERVER.validateToken, handler: { fetch(request, env, ctx) { if ( request.method === "POST" && !ctx.auth.scope.includes("calendar:write") ) { return insufficientScope(ctx.auth, ["calendar:read", "calendar:write"]); } return handleMcp(request, ctx.props); }, },});
In the example, env.AUTH_SERVER.validateToken is that Service Binding call. The calendar Worker needs no KV namespace of its own.
{ "name": "calendar-mcp", "main": "src/index.ts", // Set this to today's date "compatibility_date": "2026-10-16", "services": [ { "binding": "AUTH_SERVER", "service": "auth-server", "entrypoint": "AuthServer", }, ],}
name = "calendar-mcp"main = "src/index.ts"# Set this to today's datecompatibility_date = "2026-10-16"[[services]]binding = "AUTH_SERVER"service = "auth-server"entrypoint = "AuthServer"
OAuthResourceServer publishes the RFC 9728 ↗︎ protected resource metadata that MCP clients use to find your authorization server. It answers requests without a token with a 401 challenge that points to that metadata. It also rejects tokens issued for any other resource.
You can still use OAuthProvider as both the authorization server and the MCP server. For most 0.x deployments, the only required change is to add resourceMetadata: { resource }.
The @cloudflare/voice package lets you build real-time voice agents with Cloudflare Agents. It streams microphone audio to an Agent over WebSocket, transcribes speech, runs your model through onTurn(), converts the response to speech, and streams audio back to the caller.
A turn moves through several stages:
User speaks -> speech-to-text -> model -> text-to-speech -> audio
Previously, the package's four aggregate metrics covered successful, non-empty speech turns. They did not show how failed, aborted, empty, or text turns ended.
Turn metrics
Each speech or text turn now produces a typed VoiceTurnMetrics summary with:
A turnId for correlating events from the same turn.
A terminal outcome such as completed, no_output, output_limit, content_filtered, model_error, tts_error, or aborted.
Timings for important stages, including speech-to-final-transcript, model-to-first-text, TTS-to-first-audio, and total turn duration.
These timings can overlap and are not additive. Timings for stages that a turn did not reach are omitted.
The latest summary is available through VoiceClient, useVoiceAgent(), and useVoiceInput(). Voice input includes only the speech and transcription timings it can measure.
If an agent produces no audio, you can now distinguish between the model returning no output, reaching an output limit, encountering content filtering, or failing.
Additional diagnostics
For local debugging, you can forward server lifecycle events to the browser console:
import { Agent } from "agents";import { withVoice } from "@cloudflare/voice";const VoiceAgent = withVoice(Agent, { diagnostics: { browserConsole: true, },});
import { Agent } from "agents";import { withVoice } from "@cloudflare/voice";const VoiceAgent = withVoice(Agent, { diagnostics: { browserConsole: true, },});
The browser console combines server lifecycle events with local microphone, connection, and playback events, including model start, first model text, first audio, and playback start. Diagnostics are off by default, and their event names and fields can change.
VoiceClient also exposes typed events for speech-to-text failures, connection errors, and model outcomes. The SDK removes known content fields and does not read arbitrary provider responses, but custom error messages must not contain sensitive data.
Install the release with a compatible Agents SDK version:
Wrangler and the Cloudflare API MCP server now use optional OAuth scopes. During authorization, you can choose which optional scopes to grant instead of approving every scope requested by each client.
The consent dialog now includes the option to edit the permissions you grant to Wrangler or the Cloudflare API MCP server:
You can then choose which specific permissions to grant:
Required scopes remain selected. Choosing fewer optional scopes limits each tool's access to the permissions needed for your workflow.
If a command or tool call needs a scope that you declined, reauthorize the client and grant that scope.
Cloudflare Agents ↗︎ now has a dedicated home in the Cloudflare dashboard that lists your traced agents and subagents.
Select an agent to inspect its traces in two ways:
Replay a session assembles the full conversation for a turn: system instructions, user messages, model reasoning, tool calls with arguments and results, and the final response.
View a trace shows the execution timeline, connecting agent operations to Workers infrastructure so you can see where time was spent.
We're releasing an early preview of @cloudflare/computer ↗︎, an open-source agent runtime that gives every agent its own computer. The runtime dynamically orchestrates between fast, efficient isolates and full Linux containers, so the agent always runs on the right compute primitive for the task at hand.
@cloudflare/computer provides a virtual filesystem backed by SQLite, which you can populate from cloud storage, source control, or any files you choose. Agents can read, write, and edit files, run shell commands, and interact with Git repositories. All operations are gated, audited, and observed.
Install the package via npm:
npm install @cloudflare/computer
Instantiate a Workspace inside any Durable Object to give your agent a filesystem and execution runtime:
import { Workspace } from "@cloudflare/computer";export class Agent { workspace = new Workspace({ storage: this.ctx.storage, });}
Several execution backends are included or you can write your own:
Isolate runtime — fast, horizontally scalable execution via just-bash and Dynamic Workers, ideal for file manipulation and data processing.
Container runtime — full Linux environment via Cloudflare Containers, mounted through FUSE, for tasks that need native binaries, package managers, or a complete userland.
The AI SDK-compatible toolkit provides common agent tools (read, write, edit, ls, exec) and guides the model to choose the appropriate backend for each task.
Cloudflare's product-specific MCP servers now support the new MCP 2026-07-28 Specification. Each request runs on a fresh stateless server without an MCP protocol session or protocol-specific Durable Object.
The /mcp endpoint also accepts stateless requests from 2025 Streamable HTTP clients. Most clients can reconnect without configuration changes.
Use /mcp for new connections. Historical /sse URLs continue to work as aliases for the same Streamable HTTP handler, but they no longer serve the deprecated HTTP+SSE transport. If a client forces SSE transport, change it to Streamable HTTP or automatic transport detection.
Agents SDK v0.20.0 adds client and server support for the MCP 2026-07-28 release candidate ↗︎. Workers can serve tools, prompts, resources, and elicitation without an MCP transport session or Durable Object. Agents can connect to both MCP 2026-07-28 servers and existing legacy servers.
Client support
The MCP client manager now uses @modelcontextprotocol/client. For each connection, it probes for MCP 2026-07-28 support with server/discover. If the server does not support the stateless protocol, the client continues with the legacy initialize handshake on the same connection. Existing addMcpServer calls do not need a protocol-version setting or separate clients for each protocol generation.
For stateless requests, elicitation uses input_required through multi-round-trip requests (MRTR). The legacy path uses the same form and URL handlers for pushed requests. The SDK collects input, retries the original operation, and resolves the original callTool, getPrompt, or readResource promise with its final result.
OAuth callbacks now validate issuer metadata through the v2 SDK. Discovery state and issuer-bound credentials persist across browser redirects and Durable Object hibernation.
Run stateless servers
createMcpHandler now accepts a factory that returns a server from @modelcontextprotocol/server. The factory creates an isolated server for each request.
The isolated agents/mcp/server entry keeps McpAgent, WorkerTransport, MCP client transports, and SDK v1 modules out of stateless server bundles.
The Workers wrapper validates present browser Origins, supports explicit delegation to trusted Origin middleware, and exposes request handling plus typed change notifications.
Backward compatibility
The same createMcpHandler(createServer)(request, env, ctx) route serves MCP 2026-07-28 clients and legacy clients that use stateless requests. You do not need separate routes or tool definitions for ordinary tools, prompts, and resources.
McpAgent is deprecated and feature-frozen. Migrate existing McpAgent servers to the stateless handler at your earliest convenience. If a server depends on protocol sessions, RPC, pushed server-to-client requests, standalone streams, or replay, use the migration guide to design stateless equivalents and run both routes while clients transition.
Migrate existing SDK v1 servers
Upgrade the Agents SDK:
npm i agents@latest
yarn add agents@latest
pnpm add agents@latest
bun add agents@latest
Move ordinary SDK v1 server definitions into an SDK v2 factory and serve them with createMcpHandler. The handler's default legacy compatibility means most stateless deployments need only one route.
If an existing McpAgent server still needs sessionful features, add the stateless path beside it. Use isLegacyRequest() to send only legacy traffic to the existing route:
Migrate the remaining sessionful features, allow existing sessions to drain, then remove the legacy route. Refer to Migrate to MCP SDK v2 for package changes, compatibility limits, and rollout steps.
Deprecations in v0.20.0
This release deprecates the following Agents SDK APIs:
Deprecated API
Replacement
Status
McpAgent
Use an SDK v2 factory with createMcpHandler for stateless servers. Use the migration guide to replace stateful features before removing a legacy route.
Feature-frozen. No removal version is announced.
createMcpHandler(v1Server, options)
Move the server to an SDK v2 factory and call createMcpHandler(factory, options). Use createLegacyMcpHandler only as a temporary bridge for sessionful features.
Scheduled for removal in the next major version.
MCPClientManager.callTool(params, resultSchema, options) and the equivalent withX402Client overload
Use callTool(params, options) or callTool(confirm, params, options).
Compatibility overload. No removal version is announced.
The MCP 2026-07-28 draft separately deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration.
The agents, @cloudflare/ai-chat, @cloudflare/codemode, and @cloudflare/think packages now support AI SDK v6 and v7. Existing applications can remain on v6 when updating these packages. Applications can also adopt v7 without changing the Cloudflare Agents APIs they use.
The supported peer ranges are ai@^6 || ^7 and @ai-sdk/react@^3 || ^4. Use matching major versions: pair AI SDK v6 with @ai-sdk/react v3, or pair AI SDK v7 with @ai-sdk/react v4.
To install the latest packages with AI SDK v7:
npm i agents@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/think@latest ai@^7 @ai-sdk/react@^4
bun add agents@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/think@latest ai@^7 @ai-sdk/react@^4
Think normalizes streaming, tool completion events, and telemetry across both AI SDK versions. Existing v6 applications do not need to migrate these integrations before updating Think.
This release reduces repeated MCP schema conversion and adds an opt-out for Think's automatic MCP tool exposure. It also lets non-AI-SDK hosts invoke the durable Code Mode runtime directly.
Control direct MCP tool exposure in Think
Agents SDK MCP clients now reuse converted input and output schemas while a live connection keeps the same tool catalog. This avoids converting every MCP JSON Schema to Zod again for each model turn.
@cloudflare/think also adds includeMcpTools. Set it to false when you expose MCP tools through Code Mode or another mechanism outside Think's automatic tool set:
import { Think } from "@cloudflare/think";export class MyAgent extends Think { includeMcpTools = false; waitForMcpConnections = true;}
import { Think } from "@cloudflare/think";export class MyAgent extends Think<Env> { includeMcpTools = false; waitForMcpConnections = true;}
This setting skips Think's automatic getAITools() call. MCP registration, restoration, discovery, raw catalog access, direct calls, and Code Mode connectors continue to work.
@cloudflare/codemode@latest adds execute(), search(), and describe() to the durable runtime handle. MCP servers and other hosts can now execute code and discover connector methods without adapting the runtime to an AI SDK tool.
Search and describe results include requiresApproval: true for protected connector methods. Resolve a paused execution with the existing approve() and reject() methods.
Agents connected to Model Context Protocol (MCP) servers with addMcpServer can now handle elicitation ↗︎ requests.
Elicitation lets an MCP server request user input while it handles a tool call. Form mode collects structured, non-sensitive data. URL mode asks for consent before opening an out-of-band flow, such as third-party authorization or payment.
sequenceDiagram
participant User
participant Agent as Agent (MCP client)
participant Server as MCP server
participant Browser
Server->>Agent: elicitation/create
Agent->>User: Show server, reason, and input or URL
User->>Agent: Submit, open, decline, or cancel
Agent->>Browser: Open URL after consent (URL mode)
Agent->>Server: accept, decline, or cancel
Server-->>Agent: Optional URL completion notification
Register a handler for each mode your Agent supports in onStart():
import { Agent } from "agents";export class MyAgent extends Agent { onStart() { this.mcp.configureElicitationHandlers({ form: (request, serverId) => this.forwardToUser(request, serverId), url: (request, serverId) => this.forwardToUser(request, serverId), }); } forwardToUser(request, serverId) { // Show the request in your UI and resolve after the user responds. throw new Error( `Implement elicitation for ${serverId}: ${request.params.message}`, ); }}
import { Agent } from "agents";import type { ElicitRequest, ElicitResult } from "agents/mcp";export class MyAgent extends Agent<Env> { onStart() { this.mcp.configureElicitationHandlers({ form: (request, serverId) => this.forwardToUser(request, serverId), url: (request, serverId) => this.forwardToUser(request, serverId), }); } private forwardToUser( request: ElicitRequest, serverId: string, ): Promise<ElicitResult> { // Show the request in your UI and resolve after the user responds. throw new Error( `Implement elicitation for ${serverId}: ${request.params.message}`, ); }}
Connections advertise only the modes with configured handlers. An Agent without handlers advertises no elicitation capability, which lets the server use its fallback. The SDK stores the advertised modes with each MCP server registration so they survive Durable Object hibernation. Callback functions remain in memory and reattach when onStart() runs.
The latest release of the Agents SDK ↗︎ makes it easier to run long work in the background, drive turns through one entry point, and keep chat agents working through deploys, evictions, and reconnects.
This release adds first-class detached (background) sub-agent runs with live progress and durable milestones, a single runTurn turn-admission entry point, and a large round of recovery and reliability fixes that continue converging @cloudflare/think and @cloudflare/ai-chat onto one model.
Background sub-agents with progress and milestones
runAgentTool can now dispatch a sub-agent without blocking the calling turn. A detached run returns a handle immediately and is owned by a durable, eviction-surviving backbone instead of being abandoned when the dispatching turn ends.
Durable, exactly-once-on-the-happy-path completion via a warm fast path plus a self-scheduling reconcile backbone that survives eviction and deploys.
Bounded. An absolute maxBudgetMs ceiling (default 24h) and cancelAgentTool(runId) keep abandoned runs from holding a concurrency slot forever.
detached: { notify: true } lets a finished background run inject a message back into the chat so the model reacts to the result — no hand-wired onFinish needed.
Sub-agents can also report mid-run progress that rides their own turn stream back to the parent's connected clients:
// Inside the child sub-agent:await this.reportProgress({ fraction: 0.6, phase: "deploying", message: "Generating menu page…",});
// Inside the child sub-agent:await this.reportProgress({ fraction: 0.6, phase: "deploying", message: "Generating menu page…",});
Progress surfaces on AgentToolRunState.progress via useAgentToolEvents, so a background-runs tray can render a live bar without drilling in, and the latest snapshot is persisted for inspection after eviction. Naming a milestone promotes a signal to a durable, replayable row, and detached: { onMilestones } can surface a milestone as a synthetic chat message ("narrate" for a cheap status line, or "react" to drive a model turn).
One entry point for turns: runTurn
@cloudflare/think adds a public runTurn(options) facade that unifies turn admission behind a single mode:
stream mode accepts array and function inputs to match wait mode, and all entry points now route through a shared internal admission path that throws a clear error on nested blocking admissions that previously could deadlock.
Recovery and reliability
A large part of this release continues hardening recovery and converging @cloudflare/think and @cloudflare/ai-chat onto one model:
Stream stall watchdog.AIChatAgent can detect and recover from a hung model/transport stream via the opt-in chatStreamStallTimeoutMs watchdog. With chatRecovery enabled the stall routes into the same bounded-recovery machinery a deploy or eviction uses; otherwise it surfaces as a terminal stream error so the spinner clears.
Interrupted tool-call repair.AIChatAgent now repairs a transcript with a dead server-tool call before re-entering inference (parity with @cloudflare/think), so a recovered turn no longer fails with AI_MissingToolResultsError. An overridable repairInterruptedToolPart(part) hook lets apps customize the repaired shape.
Stuck status after reconnect. Fixed AI SDK status getting stuck when a reconnect races a turn that has been accepted but has not started streaming yet, so the UI now renders the in-flight turn instead of settling on ready.
Live "recovering…" on connect.AIChatAgent now replays the recovering status to a client that connects mid-recovery, so useAgentChat's isRecovering reflects in-progress recovery immediately instead of appearing frozen.
Terminal connection failures. The client stops reconnecting on terminal WebSocket close events and exposes them via connectionError / onConnectionError on AgentClient, useAgent, and useAgentChat.
Agent-tool child recovery. A healthy long-running sub-agent run is no longer abandoned as interrupted after a deploy (both @cloudflare/think and AIChatAgent).
Workflows from sub-agent facets. Agent Workflows can now start from sub-agent facets, with callbacks and Workflow RPC routed back to the originating facet.
Plus forward-progress crediting convergence, broadcast-first give-up ordering, an event-driven auto-continuation barrier, and structured row-size compaction in AIChatAgent.
Other improvements
Shared chat React core. A new agents/chat/react entry exposes useAgentChat, transport helpers, and shared wire types, with syncMessagesToServer for server-authoritative transcript storage. @cloudflare/think/react and @cloudflare/ai-chat/react are now thin wrappers over it.
Optional ai peer. The root agents and @cloudflare/codemode runtimes no longer reference AI SDK types, so they bundle without ai / zod installed; AI-specific entry points still require the peer when imported. just-bash likewise moves to an optional peer used only by the skills bash runner.
Code Mode. The default DynamicWorkerExecutor timeout increases from 30s to 60s, executions now dispose the dynamically-loaded Worker and its RPC stub after each run (fixing a flaky isolate-shutdown assertion), connector imports are cleaned up, and the outer MCP tool-call context is passed to openApiMcpServer request callbacks.
Voice. Voice turns now support AI SDK fullStream responses (and warn when textStream is used).
MCP.McpAgent server-to-client requests can now be sent from callbacks that do not inherit the agent's async context, including callbacks reached through Worker Loader RPC.
Experimental: server actions and channels. This release lays groundwork for guarded server actions (action() / getActions() with a durable replay ledger and approvals) and a unified channels surface (configureChannels(), deliverNotice()). Both are experimental and their APIs may change, so we don't recommend depending on them yet.
Upgrade
To update to the latest version:
npm i agents@latest @cloudflare/think@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/voice@latest
The latest release of the Agents SDK ↗︎ makes it easier to build agents that can safely interact with real systems and keep working through interruptions.
Agents can now browse websites through Browser Run, write code against external tools through Code Mode, use client-provided tools when delegating to Think sub-agents, and recover more reliably from deploys, Durable Object evictions, and connection churn.
Safer browser automation
Agents can now use Browser Run through a single durable browser_execute tool. Instead of choosing from a fixed list of actions, the model writes code against the Chrome DevTools Protocol (CDP) and can inspect pages, capture screenshots, read rendered content, debug frontend behavior, and interact with live browser sessions.
Browser sessions can be one-time, reused, or promoted from one-time to persistent during a run. This is useful when an agent needs a human to log in, complete MFA, or approve a sensitive action. The run can pause, keep the same tabs and cookies, and resume after approval.
The browser tools also add Live View URLs, optional session recording, and quick actions such as browser_markdown, browser_extract, browser_links, and browser_scrape for one-shot browsing tasks.
Resumable code execution with approvals
Code Mode now uses createCodemodeRuntime, connectors, and a durable execution log. This lets you give a model one codemode tool instead of a large prompt full of tool definitions. The model can discover the capabilities it needs, write code against typed globals, and reuse saved snippets.
When the code reaches an approval-gated action, the runtime pauses execution and returns a pending approval. After approval, completed calls replay from the durable log, the approved action runs, and the same code continues. This makes it practical to build agents that create issues, update external systems, or perform other side effects without custom pause-and-resume logic for every tool.
Better Think delegation
Think sub-agents can now use client-defined tools over the RPC chat() path. A parent agent can pass tool schemas with clientTools and resolve tool calls through onClientToolCall. This lets delegated agents use caller-provided capabilities without requiring a browser WebSocket.
Think Workflows also improve step.prompt(). A prompt step now runs a full agentic turn before returning structured output, so the agent can call tools before producing the typed result. This makes Workflow steps more useful for durable triage, research, and approval flows.
The unified Think execute tool can also include cdp.* browser capabilities alongside state.* and tools.* when Browser Run is bound.
Voice output device selection
Voice clients can route assistant audio to a specific output device. Use outputDeviceId with useVoiceAgent, or call client.setOutputDevice() from the framework-agnostic client.
We are excited to announce GLM-5.2 on Workers AI, Z.ai's flagship agentic coding model.
@cf/zai-org/glm-5.2 is a text generation model built for agentic coding workflows. With function calling and reasoning support, it can handle long codebases, multi-step planning, and tool-augmented agents.
Key features and use cases:
Agentic coding: Designed for autonomous coding tasks, long-horizon planning, and complex software engineering workflows
Large context window: GLM-5.2 supports up to a 1,048,576 token context window. Workers AI is launching the model with a 262,144 token context window and plans to increase this in the future
Function calling: Build agents that invoke tools and APIs across multiple conversation turns
Reasoning: Tackles complex problem-solving and step-by-step reasoning tasks
Use GLM-5.2 through the Workers AI binding (env.AI.run()), the REST API at /run or /v1/chat/completions, or AI Gateway.
The latest release of the Agents SDK ↗︎ adds four new ways to build with @cloudflare/think: on-demand Agent Skills, chat messengers (starting with Telegram), declarative scheduled tasks, and durable reasoning steps inside Workflows. This release also significantly hardens durable chat recovery, so turns reliably ride through deploys, evictions, and stalled model streams in production.
Agent Skills (experimental)
Give an agent a catalog of on-demand instructions, resources, and scripts. A skill source adds a catalog to the system prompt, and the model activates a skill only when a task matches — so a large library of capabilities does not bloat every prompt.
import { Think, skills } from "@cloudflare/think";import bundledSkills from "agents:skills";export class SkillsAgent extends Think { getSkills() { return [ bundledSkills, skills.r2(this.env.SKILLS_BUCKET, { prefix: "skills/" }), ]; }}
import { Think, skills } from "@cloudflare/think";import bundledSkills from "agents:skills";export class SkillsAgent extends Think<Env> { getSkills() { return [ bundledSkills, skills.r2(this.env.SKILLS_BUCKET, { prefix: "skills/" }), ]; }}
The agents:skills import bundles a local ./skills directory through the Agents Vite plugin (one directory per skill, each with a SKILL.md). Skills can also load from R2 or a manifest. When skills are available, Think exposes activate_skill, read_skill_resource, and an optional run_skill_script tool. Skill loading is resilient: a duplicate or failing source is skipped with a warning instead of breaking the agent.
Agent Skills are experimental, and script execution in particular is early. The API may change in a future release. We would love your feedback — tell us what you are building and what is missing in the Agents repository ↗︎.
Messengers
Connect a Think agent directly to a chat platform. Think owns the webhook route, conversation routing, durable reply fiber, and streamed delivery back to the provider. Telegram ships as the first provider.
import { Think } from "@cloudflare/think";import { defineMessengers, ThinkMessengerStateAgent,} from "@cloudflare/think/messengers";import telegramMessenger from "@cloudflare/think/messengers/telegram";export { ThinkMessengerStateAgent };export class SupportAgent extends Think { getMessengers() { return defineMessengers({ telegram: telegramMessenger({ token: this.env.TELEGRAM_BOT_TOKEN, userName: "support_bot", secretToken: this.env.TELEGRAM_WEBHOOK_SECRET_TOKEN, }), }); }}
import { Think } from "@cloudflare/think";import { defineMessengers, ThinkMessengerStateAgent,} from "@cloudflare/think/messengers";import telegramMessenger from "@cloudflare/think/messengers/telegram";export { ThinkMessengerStateAgent };export class SupportAgent extends Think<Env> { getMessengers() { return defineMessengers({ telegram: telegramMessenger({ token: this.env.TELEGRAM_BOT_TOKEN, userName: "support_bot", secretToken: this.env.TELEGRAM_WEBHOOK_SECRET_TOKEN, }), }); }}
Each Chat SDK thread maps to its own Think sub-agent by default, so group chats and direct messages do not share memory. Multiple bots, custom conversation routing, and custom providers are all supported.
Scheduled tasks
Declare recurring, timezone-aware prompts and handlers with a typed domain-specific language (DSL). Think reconciles the declarations on startup and re-arms the next occurrence after each run, backed by durable idempotent submissions.
import { Think, defineScheduledTasks } from "@cloudflare/think";export class DigestAgent extends Think { getScheduledTasks() { return defineScheduledTasks({ weeklyCommitReport: { schedule: "every week on monday at 09:00", prompt: "Compile my GitHub commits for the last week and summarize them.", }, workout: { schedule: "every day at 08:00 in Europe/London", prompt: "Start my workout.", }, }); }}
import { Think, defineScheduledTasks } from "@cloudflare/think";export class DigestAgent extends Think<Env> { getScheduledTasks() { return defineScheduledTasks({ weeklyCommitReport: { schedule: "every week on monday at 09:00", prompt: "Compile my GitHub commits for the last week and summarize them.", }, workout: { schedule: "every day at 08:00 in Europe/London", prompt: "Start my workout.", }, }); }}
Think Workflows
Run a model-driven reasoning step inside a Cloudflare Workflow with ThinkWorkflow and step.prompt(), with durable typed structured output, long waits, and approval gates.
import { z } from "zod";import { ThinkWorkflow } from "@cloudflare/think/workflows";import type { ThinkWorkflowStep } from "@cloudflare/think/workflows";import type { AgentWorkflowEvent } from "agents/workflows";const draftSchema = z.object({ title: z.string(), summary: z.string(), labels: z.array(z.string()),});export class TriageWorkflow extends ThinkWorkflow<TriageAgent, Params> { async run(event: AgentWorkflowEvent<Params>, step: ThinkWorkflowStep) { const draft = await step.prompt("triage-issue", { prompt: `Triage issue #${event.payload.issueNumber}`, output: draftSchema, timeout: "3 days", }); await step.do("apply-labels", async () => { await this.agent.applyLabels(draft.labels); }); }}
Production hardening for durable chat recovery
Durable chat turns have always been designed to survive a mid-turn deploy or Durable Object eviction. This release is a major hardening pass on that machinery for production.
Better recovery during deploys. Turns now ride through continuous deploys and evictions without losing completed work or re-running tools that already ran.
A live "recovering…" signal.useAgentChat exposes a new isRecovering flag, so a recovering turn shows progress instead of looking frozen. Most UIs render isStreaming || isRecovering as "busy".
Stalled streams recover. Set chatStreamStallTimeoutMs to route a hung provider stream into the same recovery path instead of leaving an infinite spinner.
Sub-agents re-attach. On parent recovery, an in-flight agentTool() child is re-attached to its result rather than abandoned and re-run, so long-running children no longer lose work under deploys.
MCP transport improvements
Resumable streams — In-flight tool calls over Server-Sent Events (SSE) survive a dropped connection. Clients reconnect with Last-Event-ID and replay anything they missed.
Readable server IDs — addMcpServer accepts an optional id, so tools surface as readable keys (for example tool_github_create_pull_request) instead of opaque connection IDs.
Better handling of concurrent requests — Overlapping JSON-RPC requests are now correctly correlated to their responses across the HTTP and RPC transports.
Other improvements
Compaction — A Session's tokenCounter now also drives the compaction boundary decision ("what to compress"), not just the fire/no-fire trigger.
@cloudflare/worker-bundler — Adds a virtualModules option to createWorker to provide in-memory module source during bundling.
Client-tool continuations — Parallel tool results now coalesce into a single continuation, immediate resume requests attach to the pending continuation, and server-side needsApproval continuations resume reliably after approval.
Upgrade
To update to the latest version:
npm i agents@latest @cloudflare/think@latest @cloudflare/ai-chat@latest
Sandboxes can expose a service running inside the container on a public preview URL through the sandbox.tunnels namespace. The SDK uses cloudflared inside the sandbox so you can share a running service without configuring exposePort() or a custom domain.
By default, sandbox.tunnels.get(port) creates a quick tunnel ↗︎ on a zero-config *.trycloudflare.com URL — no Cloudflare account, DNS record, or custom domain required. This is perfect for quick development and for .workers.dev deployments.
For more control you can create a named tunnel through sandbox.tunnels.get(port, { name }). A named tunnel binds a hostname (<name>.<your-zone>) backed by a Cloudflare Tunnel and a CNAME record on your zone resulting in something like https://my-app-preview.example.com ↗︎.
Unlike quick tunnels, which generate a new random URL each time, a named tunnel produces a persistent URL that survives container restarts. This makes named tunnels suitable for production use cases where you want control over the tunnel and it's origin.
Calling sandbox.destroy() tears down the Cloudflare Tunnel and the associated DNS record alongside the container, so you do not leave dangling tunnels or records behind.
The latest release of the Agents SDK ↗︎ brings more reliable chat recovery, fixes Agent state synchronization during reconnects, adds durable submissions for Think, exposes routing retry configuration, and adds connection control for Voice agents.
Chat recovery improvements
@cloudflare/ai-chat now keeps server turns running when a browser or client stream is interrupted. This is useful for long-running AI responses where users refresh the page, close a tab, or temporarily lose connection. Calling stop() still cancels the server turn.
Set cancelOnClientAbort: true if browser or client aborts should also cancel the server turn:
Chat stream resume negotiation no longer throws when replay races with a closed WebSocket connection.
Recovered chat continuations no longer leave useAgentChat stuck in a streaming state when the original socket disconnects before a terminal response.
Approval auto-continuation preserves reasoning parts and persists continuation reasoning in the final message.
isServerStreaming now resets correctly when a resumed stream moves from the fallback observer path to a transport-owned stream.
Agent state and routing fixes
agents@0.12.4 prevents duplicate initial state frames during WebSocket connection setup. This avoids stale initial state messages overwriting state updates already sent by the client.
Agent recovery is also more reliable when tool calls span a Durable Object restart. Recovery now defers user finish hooks until after agent startup and isolates hook failures, so one failed hook does not block other recovered runs from finalizing.
getAgentByName() now supports routingRetry for transient Durable Object routing failures:
@cloudflare/think now supports durable programmatic submissions. submitMessages() provides durable acceptance, idempotent retries, status inspection, cancellation, and cleanup for server-driven turns that should continue after the caller returns.
Think.chat() RPC turns now run inside chat recovery fibers and persist their stream chunks. Interrupted sub-agent turns can recover partial output instead of starting over.
ChatOptions.tools has been removed from the TypeScript API. Define durable tools on the child agent or use agent tools for orchestration. Runtime options.tools values passed by legacy callers are ignored with a warning.
Think message pruning behavior change
@cloudflare/think no longer applies pruneMessages({ toolCalls: "before-last-2-messages" }) to model context by default. The previous default could strip client-side tool results from longer multi-turn flows.
truncateOlderMessages still runs as before, so context cost remains bounded. Subclasses that relied on the old aggressive pruning can opt back in from beforeTurn:
import { Think } from "@cloudflare/think";import { pruneMessages } from "ai";export class MyAgent extends Think { beforeTurn(ctx) { return { messages: pruneMessages({ messages: ctx.messages, toolCalls: "before-last-2-messages", }), }; }}
import { Think } from "@cloudflare/think";import { pruneMessages } from "ai";export class MyAgent extends Think<Env> { beforeTurn(ctx) { return { messages: pruneMessages({ messages: ctx.messages, toolCalls: "before-last-2-messages", }), }; }}
Voice agent connection control
@cloudflare/voice adds an enabled option to useVoiceAgent. React apps can now delay creating and connecting a VoiceClient until prerequisites such as capability tokens are ready.
This release also fixes Workers AI speech-to-text session edge cases and withVoice text streaming from AI SDK textStream responses.
Other improvements
Streamable HTTP routing — Server-to-client requests now route through the originating POST stream when no standalone SSE stream is available.
Structured tool output — Tool output shapes are preserved when truncating older messages or oversized persisted rows.
Non-chat Think tool steps — Think agent-tool children can complete without emitting assistant text and can return structured output through getAgentToolOutput.
Sub-agent schedules — Stale sub-agent schedule rows are pruned when their owning facet registry entry no longer exists.
@cloudflare/codemode — Adds a browser-safe export with an iframe sandbox executor and resolves OpenAPI specs inside the sandbox to avoid Worker Loader RPC size limits.
Upgrade
To update to the latest version:
npm i agents@latest @cloudflare/ai-chat@latest @cloudflare/think@latest @cloudflare/voice@latest
We are excited to announce two major capability upgrades for Agent Lee, the AI co-pilot built directly into the Cloudflare dashboard. Agent Lee is designed to understand your specific account configuration, and with this release, it moves from a passive advisor to an active assistant that can help you manage your infrastructure and visualize your data through natural language.
Take action with Write Operations
Agent Lee can now perform changes on your behalf across your Cloudflare account. Whether you need to update DNS records, modify SSL/TLS settings, or configure Workers routes, you can simply ask.
To ensure security and accuracy, every write operation requires explicit user approval. Before any change is committed, Agent Lee will present a summary of the proposed action in plain language. No action is taken until you select Confirm, and this approval requirement is enforced at the infrastructure level to prevent unauthorized changes.
Example requests:
"Add an A record for blog.example.com pointing to 192.0.2.10."
"Enable Always Use HTTPS on my zone."
"Set the SSL mode for example.com to Full (strict)."
Visualize data with Generative UI
Understanding your traffic and security trends is now as easy as asking a question. Agent Lee now features Generative UI, allowing it to render inline charts and structured data visualizations directly within the chat interface using your actual account telemetry.
Example requests:
"Show me a chart of my traffic over the last 7 days."
"What does my error rate look like for the past 24 hours?"
"Graph my cache hit rate for example.com this week."
Availability
These features are currently available in Beta for all users on the Free plan. To get started, log in to the Cloudflare dashboard ↗︎ and select Ask AI in the upper right corner.
To learn more about how to interact with your account using AI, refer to the Agent Lee documentation.
Outbound Workers for Sandboxes and Containers now support zero-trust credential injection, TLS interception, allow/deny lists, and dynamic per-instance egress policies. These features give platforms running agentic workloads full control over what leaves the sandbox, without exposing secrets to untrusted workloads, like user-generated code or coding agents.
Credential injection
Because outbound handlers run in the Workers runtime, outside the sandbox, they can hold secrets the sandbox never sees. A sandboxed workload can make a plain request, and credentials are transparently attached before a request is forwarded upstream.
For instance, you could run an agent in a sandbox and ensure that any requests it makes to Github are authenticated.
But it will never be able to access the credentials:
No token is ever passed into the sandbox. You can rotate secrets in the Worker environment
and every request will pick them up immediately.
TLS interception
Outbound Workers now intercept HTTPS traffic. A unique ephemeral certificate authority (CA) and private key are created for each sandbox instance. The CA is placed into the sandbox and trusted by default. The ephemeral private key never leaves the container runtime sidecar process and is never shared across instances.
With TLS interception active, outbound Workers can act as a transparent proxy for both HTTP and HTTPS traffic.
Allow and deny hosts
Easily filter outbound traffic with allowedHosts and deniedHosts. When allowedHosts is set, it becomes a deny-by-default allowlist. Both properties support glob patterns.
export class MySandbox extends Sandbox { allowedHosts = ["github.com", "npmjs.org"];}
Dynamic outbound handlers
Define named outbound handlers then apply or remove them at runtime using setOutboundHandler() or setOutboundByHost(). This lets you change egress policy for a running sandbox without restarting it.