releases.shpreview

Background sub-agents get durable progress and milestones

3 features1 enhancementThis release3 featuresNew capabilities1 enhancementImprovements to existing featuresAI-tallied from the release notes
From the original release noteView original ↗

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.

<span class="line"><span class="nb-shiki-1itgoe">class</span><span class="nb-shiki-1t8gfj"> OrdersAgent</span><span class="nb-shiki-1itgoe"> extends</span><span class="nb-shiki-1t8gfj"> Think</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	async</span><span class="nb-shiki-1t8gfj"> startImport</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">input</span><span class="nb-shiki-140thh">) {</span></span>
<span class="line"><span class="nb-shiki-21nrsd">		// Fire-and-forget, or wire a durable completion callback</span></span>
<span class="line"><span class="nb-shiki-21nrsd">		// (by method name, like schedule()):</span></span>
<span class="line"><span class="nb-shiki-1itgoe">		await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">runAgentTool</span><span class="nb-shiki-140thh">(ImportAgent, {</span></span>
<span class="line"><span class="nb-shiki-140thh">			input,</span></span>
<span class="line"><span class="nb-shiki-140thh">			detached: { onFinish: </span><span class="nb-shiki-mdbnqw">"onImportDone"</span><span class="nb-shiki-140thh">, maxBudgetMs: </span><span class="nb-shiki-dzsirb">60</span><span class="nb-shiki-1itgoe"> *</span><span class="nb-shiki-dzsirb"> 60</span><span class="nb-shiki-1itgoe"> *</span><span class="nb-shiki-dzsirb"> 1000</span><span class="nb-shiki-140thh"> },</span></span>
<span class="line"><span class="nb-shiki-140thh">		});</span></span>
<span class="line"><span class="nb-shiki-140thh">	}</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-21nrsd">	// result.status: "completed" | "error" | "aborted" | "interrupted"</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	async</span><span class="nb-shiki-1t8gfj"> onImportDone</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">run</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1jdh33">result</span><span class="nb-shiki-140thh">) {}</span></span>
<span class="line"><span class="nb-shiki-140thh">}</span></span>
<span class="line"><span class="nb-shiki-1itgoe">class</span><span class="nb-shiki-1t8gfj"> OrdersAgent</span><span class="nb-shiki-1itgoe"> extends</span><span class="nb-shiki-1t8gfj"> Think</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	async</span><span class="nb-shiki-1t8gfj"> startImport</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">input</span><span class="nb-shiki-140thh">) {</span></span>
<span class="line"><span class="nb-shiki-21nrsd">		// Fire-and-forget, or wire a durable completion callback</span></span>
<span class="line"><span class="nb-shiki-21nrsd">		// (by method name, like schedule()):</span></span>
<span class="line"><span class="nb-shiki-1itgoe">		await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">runAgentTool</span><span class="nb-shiki-140thh">(ImportAgent, {</span></span>
<span class="line"><span class="nb-shiki-140thh">			input,</span></span>
<span class="line"><span class="nb-shiki-140thh">			detached: { onFinish: </span><span class="nb-shiki-mdbnqw">"onImportDone"</span><span class="nb-shiki-140thh">, maxBudgetMs: </span><span class="nb-shiki-dzsirb">60</span><span class="nb-shiki-1itgoe"> *</span><span class="nb-shiki-dzsirb"> 60</span><span class="nb-shiki-1itgoe"> *</span><span class="nb-shiki-dzsirb"> 1000</span><span class="nb-shiki-140thh"> },</span></span>
<span class="line"><span class="nb-shiki-140thh">		});</span></span>
<span class="line"><span class="nb-shiki-140thh">	}</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-21nrsd">	// result.status: "completed" | "error" | "aborted" | "interrupted"</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	async</span><span class="nb-shiki-1t8gfj"> onImportDone</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">run</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1jdh33">result</span><span class="nb-shiki-140thh">) {}</span></span>
<span class="line"><span class="nb-shiki-140thh">}</span></span>

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 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:

<span class="line"><span class="nb-shiki-21nrsd">// Inside the child sub-agent:</span></span>
<span class="line"><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">reportProgress</span><span class="nb-shiki-140thh">({</span></span>
<span class="line"><span class="nb-shiki-140thh">	fraction: </span><span class="nb-shiki-dzsirb">0.6</span><span class="nb-shiki-140thh">,</span></span>
<span class="line"><span class="nb-shiki-140thh">	phase: </span><span class="nb-shiki-mdbnqw">"deploying"</span><span class="nb-shiki-140thh">,</span></span>
<span class="line"><span class="nb-shiki-140thh">	message: </span><span class="nb-shiki-mdbnqw">"Generating menu page…"</span><span class="nb-shiki-140thh">,</span></span>
<span class="line"><span class="nb-shiki-140thh">});</span></span>
<span class="line"><span class="nb-shiki-21nrsd">// Inside the child sub-agent:</span></span>
<span class="line"><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">reportProgress</span><span class="nb-shiki-140thh">({</span></span>
<span class="line"><span class="nb-shiki-140thh">	fraction: </span><span class="nb-shiki-dzsirb">0.6</span><span class="nb-shiki-140thh">,</span></span>
<span class="line"><span class="nb-shiki-140thh">	phase: </span><span class="nb-shiki-mdbnqw">"deploying"</span><span class="nb-shiki-140thh">,</span></span>
<span class="line"><span class="nb-shiki-140thh">	message: </span><span class="nb-shiki-mdbnqw">"Generating menu page…"</span><span class="nb-shiki-140thh">,</span></span>
<span class="line"><span class="nb-shiki-140thh">});</span></span>

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:

<span class="line"><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">runTurn</span><span class="nb-shiki-140thh">({ mode: </span><span class="nb-shiki-mdbnqw">"wait"</span><span class="nb-shiki-140thh">, messages }); </span><span class="nb-shiki-21nrsd">// saveMessages / continueLastTurn</span></span>
<span class="line"><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">runTurn</span><span class="nb-shiki-140thh">({ mode: </span><span class="nb-shiki-mdbnqw">"submit"</span><span class="nb-shiki-140thh">, messages }); </span><span class="nb-shiki-21nrsd">// durable submitMessages</span></span>
<span class="line"><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">runTurn</span><span class="nb-shiki-140thh">({ mode: </span><span class="nb-shiki-mdbnqw">"stream"</span><span class="nb-shiki-140thh">, messages }); </span><span class="nb-shiki-21nrsd">// chat()</span></span>
<span class="line"><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">runTurn</span><span class="nb-shiki-140thh">({ mode: </span><span class="nb-shiki-mdbnqw">"wait"</span><span class="nb-shiki-140thh">, messages }); </span><span class="nb-shiki-21nrsd">// saveMessages / continueLastTurn</span></span>
<span class="line"><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">runTurn</span><span class="nb-shiki-140thh">({ mode: </span><span class="nb-shiki-mdbnqw">"submit"</span><span class="nb-shiki-140thh">, messages }); </span><span class="nb-shiki-21nrsd">// durable submitMessages</span></span>
<span class="line"><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">runTurn</span><span class="nb-shiki-140thh">({ mode: </span><span class="nb-shiki-mdbnqw">"stream"</span><span class="nb-shiki-140thh">, messages }); </span><span class="nb-shiki-21nrsd">// chat()</span></span>

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:

if (!customElements.get("nb-pm-restore")) { customElements.define( "nb-pm-restore", class extends HTMLElement { connectedCallback() { const card = this.closest("[data-nb-pm]"); if (!card) return; let saved; try { saved = sessionStorage.getItem("ui-pm-tab"); } catch { return; } if (!saved) return; const tabs = card.querySelectorAll("[data-nb-pm-tab]"); let idx = -1; tabs.forEach(function (t, i) { if (t.textContent.trim() === saved) idx = i; }); if (idx < 1) return; tabs.forEach(function (t, i) { t.setAttribute("aria-selected", String(i === idx)); }); card.querySelectorAll("[data-nb-pm-panel]").forEach(function (p, i) { p.hidden = i !== idx; }); } }, ); }

npmyarnpnpmbun

<span class="text-success">npm</span><span class="text-warning"> i agents@latest @cloudflare/think@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/voice@latest</span>
<span class="text-success">yarn</span><span class="text-warning"> add agents@latest @cloudflare/think@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/voice@latest</span>
<span class="text-success">pnpm</span><span class="text-warning"> add agents@latest @cloudflare/think@latest @cloudflare/ai-chat@latest @cloudflare/codemode@latest @cloudflare/voice@latest</span>
<span class="text-success">bun</span><span class="text-warning"> 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 July 22, 2026

Background sub-agents get durable progress and milestones… — releases.sh