Background sub-agents with progress; unified runTurn entry point
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.
-
JavaScript
<div><div><span>class</span><span> </span><span>OrdersAgent</span><span> </span><span>extends</span><span> </span><span>Think</span><span> </span><span>{</span></div></div><div><div><span> </span><span>async</span><span> </span><span>startImport</span><span>(</span><span>input</span><span>)</span><span> </span><span>{</span></div></div><div><div><span> </span><span>// Fire-and-forget, or wire a durable completion callback</span></div></div><div><div><span> </span><span>// (by method name, like schedule()):</span></div></div><div><div><span> </span><span>await</span><span> </span><span>this</span><span>.</span><span>runAgentTool</span><span>(</span><span>ImportAgent</span><span>,</span><span> </span><span>{</span></div></div><div><div><span> </span><span>input</span><span>,</span></div></div><div><div><span><span> </span></span><span>detached</span><span>:</span><span> </span><span>{</span><span> onFinish</span><span>:</span><span> </span><span>"onImportDone"</span><span>,</span><span> maxBudgetMs</span><span>:</span><span> </span><span>60</span><span> </span><span>*</span><span> </span><span>60</span><span> </span><span>*</span><span> </span><span>1000</span><span> </span><span>},</span></div></div><div><div><span> </span><span>}</span><span>)</span><span>;</span></div></div><div><div><span> </span><span>}</span></div></div><div><div> </div></div><div><div><span> </span><span>// result.status: "completed" | "error" | "aborted" | "interrupted"</span></div></div><div><div><span> </span><span>async</span><span> </span><span>onImportDone</span><span>(</span><span>run</span><span>,</span><span> </span><span>result</span><span>)</span><span> </span><span>{}</span></div></div><div><div><span>}</span></div></div> -
TypeScript
<div><div><span>class</span><span> </span><span>OrdersAgent</span><span> </span><span>extends</span><span> </span><span>Think</span><span> </span><span>{</span></div></div><div><div><span> </span><span>async</span><span> </span><span>startImport</span><span>(</span><span>input</span><span>)</span><span> </span><span>{</span></div></div><div><div><span> </span><span>// Fire-and-forget, or wire a durable completion callback</span></div></div><div><div><span> </span><span>// (by method name, like schedule()):</span></div></div><div><div><span> </span><span>await</span><span> </span><span>this</span><span>.</span><span>runAgentTool</span><span>(</span><span>ImportAgent</span><span>,</span><span> </span><span>{</span></div></div><div><div><span> </span><span>input</span><span>,</span></div></div><div><div><span><span> </span></span><span>detached</span><span>:</span><span> </span><span>{</span><span> onFinish</span><span>:</span><span> </span><span>"onImportDone"</span><span>,</span><span> maxBudgetMs</span><span>:</span><span> </span><span>60</span><span> </span><span>*</span><span> </span><span>60</span><span> </span><span>*</span><span> </span><span>1000</span><span> </span><span>},</span></div></div><div><div><span> </span><span>}</span><span>)</span><span>;</span></div></div><div><div><span> </span><span>}</span></div></div><div><div> </div></div><div><div><span> </span><span>// result.status: "completed" | "error" | "aborted" | "interrupted"</span></div></div><div><div><span> </span><span>async</span><span> </span><span>onImportDone</span><span>(</span><span>run</span><span>,</span><span> </span><span>result</span><span>)</span><span> </span><span>{}</span></div></div><div><div><span>}</span></div></div>
Highlights:
- 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
maxBudgetMsceiling (default 24h) andcancelAgentTool(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-wiredonFinishneeded.
Sub-agents can also report mid-run progress that rides their own turn stream back to the parent's connected clients:
-
JavaScript
<div><div><span>// Inside the child sub-agent:</span></div></div><div><div><span>await</span><span> </span><span>this</span><span>.</span><span>reportProgress</span><span>(</span><span>{</span></div></div><div><div><span><span> </span></span><span>fraction</span><span>:</span><span> </span><span>0.6</span><span>,</span></div></div><div><div><span><span> </span></span><span>phase</span><span>:</span><span> </span><span>"deploying"</span><span>,</span></div></div><div><div><span><span> </span></span><span>message</span><span>:</span><span> </span><span>"Generating menu page…"</span><span>,</span></div></div><div><div><span>}</span><span>)</span><span>;</span></div></div> -
TypeScript
<div><div><span>// Inside the child sub-agent:</span></div></div><div><div><span>await</span><span> </span><span>this</span><span>.</span><span>reportProgress</span><span>(</span><span>{</span></div></div><div><div><span><span> </span></span><span>fraction</span><span>:</span><span> </span><span>0.6</span><span>,</span></div></div><div><div><span><span> </span></span><span>phase</span><span>:</span><span> </span><span>"deploying"</span><span>,</span></div></div><div><div><span><span> </span></span><span>message</span><span>:</span><span> </span><span>"Generating menu page…"</span><span>,</span></div></div><div><div><span>}</span><span>)</span><span>;</span></div></div>
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:
-
JavaScript
<div><div><span>await</span><span> </span><span>this</span><span>.</span><span>runTurn</span><span>(</span><span>{</span><span> mode</span><span>:</span><span> </span><span>"wait"</span><span>,</span><span> </span><span>messages</span><span> </span><span>}</span><span>)</span><span>;</span><span> </span><span>// saveMessages / continueLastTurn</span></div></div><div><div><span>await</span><span> </span><span>this</span><span>.</span><span>runTurn</span><span>(</span><span>{</span><span> mode</span><span>:</span><span> </span><span>"submit"</span><span>,</span><span> </span><span>messages</span><span> </span><span>}</span><span>)</span><span>;</span><span> </span><span>// durable submitMessages</span></div></div><div><div><span>await</span><span> </span><span>this</span><span>.</span><span>runTurn</span><span>(</span><span>{</span><span> mode</span><span>:</span><span> </span><span>"stream"</span><span>,</span><span> </span><span>messages</span><span> </span><span>}</span><span>)</span><span>;</span><span> </span><span>// chat()</span></div></div> -
TypeScript
<div><div><span>await</span><span> </span><span>this</span><span>.</span><span>runTurn</span><span>(</span><span>{</span><span> mode</span><span>:</span><span> </span><span>"wait"</span><span>,</span><span> </span><span>messages</span><span> </span><span>}</span><span>)</span><span>;</span><span> </span><span>// saveMessages / continueLastTurn</span></div></div><div><div><span>await</span><span> </span><span>this</span><span>.</span><span>runTurn</span><span>(</span><span>{</span><span> mode</span><span>:</span><span> </span><span>"submit"</span><span>,</span><span> </span><span>messages</span><span> </span><span>}</span><span>)</span><span>;</span><span> </span><span>// durable submitMessages</span></div></div><div><div><span>await</span><span> </span><span>this</span><span>.</span><span>runTurn</span><span>(</span><span>{</span><span> mode</span><span>:</span><span> </span><span>"stream"</span><span>,</span><span> </span><span>messages</span><span> </span><span>}</span><span>)</span><span>;</span><span> </span><span>// chat()</span></div></div>
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.
AIChatAgentcan detect and recover from a hung model/transport stream via the opt-inchatStreamStallTimeoutMswatchdog. WithchatRecoveryenabled 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.
AIChatAgentnow repairs a transcript with a dead server-tool call before re-entering inference (parity with@cloudflare/think), so a recovered turn no longer fails withAI_MissingToolResultsError. An overridablerepairInterruptedToolPart(part)hook lets apps customize the repaired shape. - Stuck status after reconnect. Fixed AI SDK
statusgetting 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 onready. - Live "recovering…" on connect.
AIChatAgentnow replays the recovering status to a client that connects mid-recovery, souseAgentChat'sisRecoveringreflects 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/onConnectionErroronAgentClient,useAgent, anduseAgentChat. - Agent-tool child recovery. A healthy long-running sub-agent run is no longer abandoned as
interruptedafter a deploy (both@cloudflare/thinkandAIChatAgent). - 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/reactentry exposesuseAgentChat, transport helpers, and shared wire types, withsyncMessagesToServerfor server-authoritative transcript storage.@cloudflare/think/reactand@cloudflare/ai-chat/reactare now thin wrappers over it. - Optional
aipeer. The rootagentsand@cloudflare/codemoderuntimes no longer reference AI SDK types, so they bundle withoutai/zodinstalled; AI-specific entry points still require the peer when imported.just-bashlikewise moves to an optional peer used only by the skills bash runner. - Code Mode. The default
DynamicWorkerExecutortimeout 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 toopenApiMcpServerrequest callbacks. - Voice. Voice turns now support AI SDK
fullStreamresponses (and warn whentextStreamis used). - MCP.
McpAgentserver-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:
<span>npm</span><span> i agents@latest @cloudflare/think@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/voice@latest</span>
<span>yarn</span><span> add agents@latest @cloudflare/think@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/voice@latest</span>
<span>pnpm</span><span> add agents@latest @cloudflare/think@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/voice@latest</span>
<span>bun</span><span> add agents@latest @cloudflare/think@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/voice@latest</span>
Refer to the Think documentation, Code Mode documentation, and Agents documentation for more information.
Fetched June 26, 2026


