@cloudflare/think
0.21.0 · 20 Oct 2026MIT97k / weekRepositorynpmAtom feed
52 stable releases, newest first, by release line. Notes are quoted as the maintainers published them.
- 0.21.x · latest line
0.21.0
20 Oct 2026- deprecated
createExecuteToolandcreateExecuteRuntimeare deprecated in favour ofexecwith aws:toolsmodule. See Calling the agent's tools from code. - deprecated
Chat recovery now seals a turn after
chatRecovery.maxInterruptedAttemptsinterrupted attempts (default 5), so a turn that exceeds the Durable Object memory limit on every run no longer retries forever;maxOomRetriesis deprecated. See Stream Recovery. Think's workspace tools are now
@cloudflare/computer'screateAITools, andexecoffers every backend the Workspace has;createWorkspaceTools,workspaceBash, and@cloudflare/think/tools/workspaceare removed. See Built-in workspace tools.
Minor Changes
Think's workspace tools are now
@cloudflare/computer'screateAITools, andexecoffers every backend the Workspace has;createWorkspaceTools,workspaceBash, and@cloudflare/think/tools/workspaceare removed. See Built-in workspace tools.Think's
workspaceis now aWorkspacefrom@cloudflare/computer, a new peer dependency. See Upgrading to a Computer workspace.Think no longer depends on
@cloudflare/shell, and the execute tool'sstateoption is removed. See Code Execution Tool.- deprecated
createExecuteToolandcreateExecuteRuntimeare deprecated in favour ofexecwith aws:toolsmodule. See Calling the agent's tools from code.
Patch Changes
- deprecated
Chat recovery now seals a turn after
chatRecovery.maxInterruptedAttemptsinterrupted attempts (default 5), so a turn that exceeds the Durable Object memory limit on every run no longer retries forever;maxOomRetriesis deprecated. See Stream Recovery. Reuse one
Intl.DateTimeFormatper timezone when computing wall-clock scheduled task occurrences, cutting the CPU cost of each occurrence substantially. See #2583; thanks @Bowiemb.
- 0.20.x
0.20.2
9 Oct 2026fetch_urlnow blocks private and local hosts using the shared policy fromagents/webfetch. See Fetch the Web: URL policy.
0.20.1
7 Oct 2026Fix a chat that could stall when the model chains fast client tool calls across steps.
0.20.0
2 Oct 2026- breaking
declared scheduled tasks now run on the root agent only; override
getScheduledTasksScope()to return"all"for per-facet scheduling. See Think scheduled tasks. - deprecated
Keep assistant messages and tool outputs attached to the correct turn when a provider reuses a
toolCallId;resolveToolMergeIdis deprecated in favour ofreconcileMessages. See Chat agents. Require
ai@^7and@ai-sdk/react@^4, because Think bundles providers that return AI SDK v7 models. See Think dependencies.
Minor Changes
Require
ai@^7and@ai-sdk/react@^4, because Think bundles providers that return AI SDK v7 models. See Think dependencies.- breaking
declared scheduled tasks now run on the root agent only; override
getScheduledTasksScope()to return"all"for per-facet scheduling. See Think scheduled tasks. Browser chat turns now use the
webchannel, so its configured tools and turn limit apply. See Think channels.
Patch Changes
Report a failed agent-tool child as failed even when it was evicted before recording the failure. See Agent tools.
Fix agent-tool chunks being duplicated or dropped on reconnect, child re-attach, and fiber recovery. See Agent tools.
Add
eventDelivery: "terminal"torunAgentToolto forward only lifecycle, progress, and milestone events for a run. See Agent tools.AI Chat and Think send the terminal
doneframe after persisting and broadcasting the assistant reply, so later sends are not overwritten. See Chat agents.Keep a tool call's input when its approval request arrives before the input finishes streaming (#1872). See Human in the loop.
Fix two Think warnings that fired, or failed to fire, because of a false method-override check. See Think lifecycle hooks.
Require
agents@>=0.25.0so Think and AI Chat can import the chat helpers provided by that release.useAgentChatgainsonTurnEnd, and terminal chat frames carry outcomes and user message IDs so clients can settle sends correctly across recovery and reconnects. Replay, tool callbacks, and turn state now remain consistent through terminal delivery; see Chat agents.Native RPC calls to async Agent and Think methods now start lifecycle initialization first; address Agents by name because raw IDs from
newUniqueId()andidFromString()now fail their first async RPC. See Lifecycle.Stop storing compaction summaries that clients echo back, and hide duplicates already stored (#1984). See Sessions.
Context blocks can set
whenChanged: "remind"to send changes as a reminder after the cached prompt instead of rewriting it. See Context.Continuation turns that fail before streaming now report an error to clients and
onChatResponseinstead of appearing to hang (#2381). See Client tools.Messenger attachments load when
fetchDatareturns a plainArrayBuffer. See Messengers.Messenger replies now see every message in a quick burst, not just the last one. See Messengers.
Messenger replies no longer post a
...placeholder on adapters without native streaming. See Messengers.A recovered messenger turn delivers its answer to the thread instead of an "interrupted" apology. See Messengers.
rejectExecution()accepts{ autoContinue: false }to record a rejection without starting another turn. See Actions.Interfaces can now be used as
PropsonAgent,AIChatAgent,Think, and routing helpers. See Routing.- deprecated
Keep assistant messages and tool outputs attached to the correct turn when a provider reuses a
toolCallId;resolveToolMergeIdis deprecated in favour ofreconcileMessages. See Chat agents. useAgentChatkeeps messages sent while disconnected when reconnecting, including Think transcripts, instead of erasing them (#1983). See Chat agents.Fix text appearing twice when reconnecting during a tool continuation. See Resumable streaming.
onChatResponsestill fires, withrecovered: true, for a turn persisted just before a Durable Object reset (#2266, #1842). See Think lifecycle hooks.Add
session.mirror()to keep an in-memory transcript in sync with a session's change feed. See Sessions.Settle approved tool calls that never ran once the conversation moves past them, so later turns don't send unresolved tool calls (#2382). See Human in the loop.
Think can write server-authored
messageMetadataonto assistant messages, per agent or per turn, and recovery preserves metadata even when keys match built-in object properties. See Think lifecycle hooks.Recovery continuations extend the interrupted assistant message instead of adding a second one. See Think.
Resolving a paused execution removes the model's now-stale "waiting for approval" text from the transcript (#2054). See Actions.
Add
getGateway(model)to set AI Gateway options such as metadata and caching for string models (#2262). See Think.resolveModel()rejects malformed model ids up front, and@hf/...ids get the same Workers AI settings as@cf/.... See Think.Add
messengerConcurrencyto choose the Chat SDK concurrency strategy for messenger replies (#2313). See Messengers.Messenger replies hold the thread lock and retain queued follow-ups during slow delivery; typing updates are serialized and settle before reply text arrives. See Think messengers.
Fix regenerate continuing the old response instead of replacing it (#2028). See Multi-chat.
A regeneration interrupted by a restart now recovers on its own branch. See Multi-chat.
Older-message truncation moves in steps (
truncationStep), so the prompt-cache prefix stays stable across turns (#2200). See Think.Stream-stall recovery retries stalls before the first chunk and now calls
onChatRecovery. See Think lifecycle hooks.step.prompt()turns interrupted mid-stream now recover with their structured output instead of failing (#1727). See Workflows.Add
waitForSubmission()for durable submissions and harden cancellation, status-hook, reset-turn, and paused-tool lifecycle handling so waits settle and terminal status is reported once. See Programmatic submissions.telegramMessengeradds anativeStreamingoption and works withverifyWebhookon@chat-adapter/telegram4.38 (#2397). See Messengers.Think hooks and tools can read the running turn identity, and
activeChannelstays scoped to its owning turn. See Lifecycle hooks.Harden transient chat recovery across Think and AI Chat, including retry budgeting, cancellation, terminal error delivery, and recovery after restarts. See the
agents/chatrecovery helpers.Truncate older tool results after model conversion so
toModelOutputand provider-executed tools keep valid outputs (#2014). See Sessions.runTurn({ mode: "wait" })returns the assistant message from its own turn, even if other turns persist in the meantime. See Programmatic submissions.Wait-mode
TurnResult.outputreturns the parsed structured output, and submission inspections include the persistedmessageId(#2263, #2264). See Programmatic submissions.
- 0.19.x
0.19.0
18 Sep 2026Add the
QueueLifecycle capability (agents/queue) for durable background work. Each pushed item is a job in the Lifecycle job queue, due immediately, run from the alarm loop one at a time in push order with Lifecycle's retry, deadman, and memory-limit policy. Callbacks are registered in the constructor and typed at declaration and push;push()accepts a stableid(upsert) and per-itemretry.Agent.queue()and friends now delegate to the capability. Queued callbacks run from the alarm loop in a fresh invocation, so they no longer see the enqueuing request'sconnectionorrequestthroughgetCurrentAgent()(the agent itself is still available). Thecf_agents_queuestable and the in-isolate drain are gone; legacy rows migrate into the job queue on the next start.queue()acceptsoptions.id;dequeue,dequeueAll,dequeueAllByCallback,getQueue, andgetQueuesare now asynchronous; andQueueItem.created_atis renamedcreatedAt.LifecycleServices.starting()is replaced bystatus(), which returns"zero" | "starting" | "started".Think's workflow-notification outbox and submission drain now run as queue items; the
cf_think_workflow_notificationstable migrates and is dropped on start.Both one-shot migrations (
cf_agents_queuesin Queue,cf_think_workflow_notificationsin Think) are temporary upgrade paths and will be removed in the next minor release, by which point every started object has migrated. Deployments skipping this release should upgrade through it. Workflow-notification delivery retries with backoff capped at ten minutes (previously five) and gives up after twelve hours of continuous failure (previously never), reporting the failure throughonError.Require
agents >=0.24.0for Think's background work, which relies on the Queue capability's stable item IDs and alarm-driven execution.Recover an interrupted Think turn that had opened its stream but persisted no content by re-running its user message instead of trying to continue it. Think opens the resumable stream row before inference, so a Durable Object reset in the window before the first chunk left a turn with a stream id, no partial, and a user message as the latest leaf; recovery classified it as
continue, found no assistant message to continue from, and marked the incidentskipped. A parent tailing such a child through agent tools then sealed the run as an error. This mirrorsAIChatAgent's empty-partial new-turn rule (#1691).
Minor Changes
Add the
QueueLifecycle capability (agents/queue) for durable background work. Each pushed item is a job in the Lifecycle job queue, due immediately, run from the alarm loop one at a time in push order with Lifecycle's retry, deadman, and memory-limit policy. Callbacks are registered in the constructor and typed at declaration and push;push()accepts a stableid(upsert) and per-itemretry.Agent.queue()and friends now delegate to the capability. Queued callbacks run from the alarm loop in a fresh invocation, so they no longer see the enqueuing request'sconnectionorrequestthroughgetCurrentAgent()(the agent itself is still available). Thecf_agents_queuestable and the in-isolate drain are gone; legacy rows migrate into the job queue on the next start.queue()acceptsoptions.id;dequeue,dequeueAll,dequeueAllByCallback,getQueue, andgetQueuesare now asynchronous; andQueueItem.created_atis renamedcreatedAt.LifecycleServices.starting()is replaced bystatus(), which returns"zero" | "starting" | "started".Think's workflow-notification outbox and submission drain now run as queue items; the
cf_think_workflow_notificationstable migrates and is dropped on start.Both one-shot migrations (
cf_agents_queuesin Queue,cf_think_workflow_notificationsin Think) are temporary upgrade paths and will be removed in the next minor release, by which point every started object has migrated. Deployments skipping this release should upgrade through it. Workflow-notification delivery retries with backoff capped at ten minutes (previously five) and gives up after twelve hours of continuous failure (previously never), reporting the failure throughonError.
Patch Changes
Require
agents >=0.24.0for Think's background work, which relies on the Queue capability's stable item IDs and alarm-driven execution.Recover an interrupted Think turn that had opened its stream but persisted no content by re-running its user message instead of trying to continue it. Think opens the resumable stream row before inference, so a Durable Object reset in the window before the first chunk left a turn with a stream id, no partial, and a user message as the latest leaf; recovery classified it as
continue, found no assistant message to continue from, and marked the incidentskipped. A parent tailing such a child through agent tools then sealed the run as an error. This mirrorsAIChatAgent's empty-partial new-turn rule (#1691).Preserve running durable submissions during startup when either a retry or continuation chat-recovery callback is still pending. This applies to both current Tasks recovery attempts and legacy scheduled callbacks, allowing interrupted empty streams to retry and complete after a restart instead of being marked as errors.
Keep recovery ownership until the successor chat turn is durably accepted, and bind the submission to that successor before handing off. Scope the handoff signal to the successor turn so concurrent turns cannot claim it, including facet-hosted turns after their legacy successor fiber is durably created. New recovery payloads carry stable submission identity; exact submission IDs take precedence over request-ID fallback, preventing collisions from recovering or terminalizing another submission. Record each turn's actual outcome and structured output on the submission row in the same transaction that settles its stream, so a restart between turn completion and ledger settlement records the completed, aborted, or errored outcome from recorded fact instead of inferring from stream state — without rerunning the turn or duplicating response callbacks: aborted turns stay aborted, recovered workflow completions keep their output, an overflow retry segment is never mistaken for an answer, and the recorded fact survives unrelated turns reclaiming stream rows.
- 0.18.x
0.18.0
11 Sep 2026Replatform Think conversation storage onto
agents/sessionsand prompt context ontoagents/context.Existing subclasses keep compiling and running.
configureSession(session)still accepts thewithContext()/withCachedPrompt()chain,this.sessionstill carriesaddContext,getContextBlock,replaceContextBlock,refreshSystemPrompt,freezeSystemPrompt,tools(), and the rest, andappendMessage(message, parentId)/getHistory(leafId)still take their positional arguments. Those context methods are deprecated forwards to the newthis.context; new code should declare blocks inconfigureContext()and readthis.contextdirectly.withCachedPrompt()is a no-op because the frozen prompt is always persisted now.What you may want to change
Behaviour changes on upgrade
Removed
Lifecycle owns a durable job queue, driven as an alarm event loop.
The thing in the queue is a job: a serialisable callback address — the
owning capability plus a function name — with a due time and a payload.
Capabilities and the host push jobs through the scopedjobssurface;
Lifecycle drives due jobs in timestamp order when the alarm fires, owns
dispatch retries and platform-failure deferral, arms a deadman pre-alarm
before driving so an isolate death mid-drive still wakes the object, and
derives the physical alarm purely from queue state (queue mutations re-arm
automatically; an exclusive job suppresses ordinary candidates).class Cleanup extends LifecycleCapability { async scheduleSweep(time: number) { await this.lifecycle.jobs.push({ id: "sweep", fn: "sweep", time }); } onJob({ job }: LifecycleJobContext) { // drive result: nothing = complete, { rescheduleAt } = suspend, // "yield" = leave due and wake again immediately } }The pull-based alarm-contribution model is removed: capability
getNextAlarm()/onAlarm(), hostgetNextAlarm(),LifecycleServices.alarms(rearm/disabled), andAlarmContribution
are gone. HostonAlarm()remains and runs once per alarm invocation
after due jobs are driven. Terminal application failures reach the
owner'sonJobError(), whose drive result decides advancement.The alarm memory-limit circuit breaker (#1825) moves from
Agent.alarm()
into the Lifecycle event loop, targeting the exact executing job; Agent
contributes domain policy through the newonAlarmMemoryLimit()host
hook, and Scheduler's__DO_NOT_USE_WILL_BREAK__handleAlarmMemoryLimit
escape hatch is gone. After recording a strike the breaker now finishes by
resetting the isolate withctx.abort(reason, { retryAlarm: false })
(retry of the handled alarm suppressed; the backoff alarm owns the next
wake), andAgent.destroy()uses the same no-retry abort so a completed
teardown's alarm cannot be retried into a fresh constructor that recreates
the deleted schema.Scheduler keeps its entire public API and loses its storage and due-row
loop: a schedule is one job whosefnis the callback name, and interval
schedules are single-flight jobs. Existingcf_agents_schedulesrows are
migrated into thecf_agents_jobsqueue on startup and the legacy table
is dropped. Agent's public scheduling andkeepAlive()APIs are
unchanged; its keep-alive, fiber-recovery/facet housekeeping, and
deferred-destroy wakes are now host jobs, and Think's
workflow-notification wake replaces the removed_getExtensionAlarm().Keep media eviction a context-window technique, separate from how Sessions stores a message.
How Sessions lays a message out in rows is invisible and lossless: a message too large for one row is split across continuation rows and reassembled byte for byte. Media eviction is a decision about the model's context: once media has aged past
mediaEviction.keepRecentMessageson the active path, Think removes it from the conversation so the model stops re-reading a large image every turn, and leaves[evicted image/png, 812004 bytes; preserved at /attachments/evicted/<messageId>-<n>.png]in its place. The raw bytes are written to the Workspace at that path with their real mime type, so the workspacereadtool puts the actual image back in context when the agent deliberately reads it. The rewritten message no longer carries the payload, so the bytes live in exactly one place.mediaEviction: falsenow means the model keeps seeing aged media. It no longer changes where Sessions keeps the bytes.minPartBytesis Think's context threshold and is no longer passed to Sessions as a storage setting. The marker text and the/attachments/evicted/<id>-<n>.<ext>paths are unchanged, so old markers keep resolving.MediaEvictionConfig.externalizeToWorkspaceis deprecated and ignored: evicted bytes are always preserved. Existing configurations keep compiling.WorkspaceLike.writeFileBytesis optional. Eviction and skills projection need it to write raw bytes; a custom workspace without it keeps working and those two features log once and stand down.
Minor Changes
Replatform Think conversation storage onto
agents/sessionsand prompt context ontoagents/context.Existing subclasses keep compiling and running.
configureSession(session)still accepts thewithContext()/withCachedPrompt()chain,this.sessionstill carriesaddContext,getContextBlock,replaceContextBlock,refreshSystemPrompt,freezeSystemPrompt,tools(), and the rest, andappendMessage(message, parentId)/getHistory(leafId)still take their positional arguments. Those context methods are deprecated forwards to the newthis.context; new code should declare blocks inconfigureContext()and readthis.contextdirectly.withCachedPrompt()is a no-op because the frozen prompt is always persisted now.What you may want to change
Declare prompt blocks with the new
configureContext()hook instead ofwithContext(). Blocks from both are merged,configureContext()first.Read context through
this.context(aContextBlocksfromagents/context) instead ofthis.session.Import
Sessionfrom@cloudflare/thinkwhen you annotate aconfigureSessionoverride. The class exported fromagents/sessionsis the raw storage handle and is not assignable to Think's.
Behaviour changes on upgrade
Storage migrates on first wake and cannot be rolled back. Each Durable Object lifts its
assistant_messages,assistant_compactions, andassistant_configrows into thecf_agents_session_*tables, verifies every row landed, and drops the old tables. An object that has woken on this version has an empty conversation if you roll back to the previous release; rolling forward again is safe. Deploy behind a canary if you need a rollback path.hydrationByteBudgetdefaults to 32 MiB (was 24 MiB) and is now a hard ceiling that charges each row its full stored size, attachments included. There is no message-count floor, so an unusually large recent window can hydrate fewer than four messages.getHistory()still reads the full path.Context blocks load during
onStart, as before the replatform, sothis.context.getBlock()answers as soon as the object has started.session.search()still works. Its index is now built by the first search on an object rather than maintained on every append, so a Think that never searches stops paying a second billed row per message.Think no longer reads Sessions tables with raw SQL. If you queried
assistant_messagesyourself, usethis.session.history()orgetHistory().
Removed
sessionAttachments. There is nothing to configure about how Sessions stores a message: media leaves the row into a content-addressed attachment store and a message larger than one SQLite row is split across continuation rows, losslessly.getRecentHistory(budget, minRecentMessages): the second argument is accepted and ignored.compactAfter(threshold, { tokenCounter }): the counter option is accepted and ignored; the trigger reads the estimate Sessions stamps on each row.
Lifecycle owns a durable job queue, driven as an alarm event loop.
The thing in the queue is a job: a serialisable callback address — the
owning capability plus a function name — with a due time and a payload.
Capabilities and the host push jobs through the scopedjobssurface;
Lifecycle drives due jobs in timestamp order when the alarm fires, owns
dispatch retries and platform-failure deferral, arms a deadman pre-alarm
before driving so an isolate death mid-drive still wakes the object, and
derives the physical alarm purely from queue state (queue mutations re-arm
automatically; an exclusive job suppresses ordinary candidates).class Cleanup extends LifecycleCapability { async scheduleSweep(time: number) { await this.lifecycle.jobs.push({ id: "sweep", fn: "sweep", time }); } onJob({ job }: LifecycleJobContext) { // drive result: nothing = complete, { rescheduleAt } = suspend, // "yield" = leave due and wake again immediately } }The pull-based alarm-contribution model is removed: capability
getNextAlarm()/onAlarm(), hostgetNextAlarm(),LifecycleServices.alarms(rearm/disabled), andAlarmContribution
are gone. HostonAlarm()remains and runs once per alarm invocation
after due jobs are driven. Terminal application failures reach the
owner'sonJobError(), whose drive result decides advancement.The alarm memory-limit circuit breaker (#1825) moves from
Agent.alarm()
into the Lifecycle event loop, targeting the exact executing job; Agent
contributes domain policy through the newonAlarmMemoryLimit()host
hook, and Scheduler's__DO_NOT_USE_WILL_BREAK__handleAlarmMemoryLimit
escape hatch is gone. After recording a strike the breaker now finishes by
resetting the isolate withctx.abort(reason, { retryAlarm: false })
(retry of the handled alarm suppressed; the backoff alarm owns the next
wake), andAgent.destroy()uses the same no-retry abort so a completed
teardown's alarm cannot be retried into a fresh constructor that recreates
the deleted schema.Scheduler keeps its entire public API and loses its storage and due-row
loop: a schedule is one job whosefnis the callback name, and interval
schedules are single-flight jobs. Existingcf_agents_schedulesrows are
migrated into thecf_agents_jobsqueue on startup and the legacy table
is dropped. Agent's public scheduling andkeepAlive()APIs are
unchanged; its keep-alive, fiber-recovery/facet housekeeping, and
deferred-destroy wakes are now host jobs, and Think's
workflow-notification wake replaces the removed_getExtensionAlarm().Keep media eviction a context-window technique, separate from how Sessions stores a message.
How Sessions lays a message out in rows is invisible and lossless: a message too large for one row is split across continuation rows and reassembled byte for byte. Media eviction is a decision about the model's context: once media has aged past
mediaEviction.keepRecentMessageson the active path, Think removes it from the conversation so the model stops re-reading a large image every turn, and leaves[evicted image/png, 812004 bytes; preserved at /attachments/evicted/<messageId>-<n>.png]in its place. The raw bytes are written to the Workspace at that path with their real mime type, so the workspacereadtool puts the actual image back in context when the agent deliberately reads it. The rewritten message no longer carries the payload, so the bytes live in exactly one place.mediaEviction: falsenow means the model keeps seeing aged media. It no longer changes where Sessions keeps the bytes.minPartBytesis Think's context threshold and is no longer passed to Sessions as a storage setting. The marker text and the/attachments/evicted/<id>-<n>.<ext>paths are unchanged, so old markers keep resolving.MediaEvictionConfig.externalizeToWorkspaceis deprecated and ignored: evicted bytes are always preserved. Existing configurations keep compiling.WorkspaceLike.writeFileBytesis optional. Eviction and skills projection need it to write raw bytes; a custom workspace without it keeps working and those two features log once and stand down.streams: rollover block log and an atomic stream → message cutover; no more stream-buffer sweeps.
The Streams chunk log is now mutable rollover blocks: an append grows the open block row (an UPDATE) until it reaches 256 KB, then opens the next. Same one billed row per append as before, but a stream of thousands of chunks is a handful of rows to delete instead of thousands. Existing
cf_agents_stream_chunksrows are folded into blocks lazily, one stream at a time on first touch, so startup never reads the whole legacy log; the table is dropped once it is empty.writer.close({ commit, discard })(anderror(reason, { … })) settles the stream, runs the caller's synchronous writes and deletes the stream's rows in one SQLite transaction.Session.__DO_NOT_USE_WILL_BREAK__sync().upsert()is the matching synchronous message write; itsafter()dispatches the change feed and auto-compaction once the transaction commits.Chat hosts (
AIChatAgent,Think) now persist the finished turn's assistant message inside that cutover: the message, the stream's settlement and the deletion of its temporary rows commit together, so a crash leaves either the live stream (recovery rebuilds the message from it) or the message, never neither.ResumableStream.start()reclaims anything a crash left behind. The_cleanupStreamBuffersalarm is no longer armed (cleanupStreamBuffersandSTREAM_CLEANUP_DELAY_SECONDSare removed fromagents/chat; the host callback is kept as a no-op so alarms persisted by earlier versions still resolve).
Patch Changes
Run root-agent chat recovery continuations as chained Tasks instead of schedule rows. Initial recovery attempts deduplicate by incident, delayed retries use durable Task sleeps, and platform failures replay through Task claims. AI Chat and Think share one reserved recovery definition and preserve their existing bounded callback handoff behavior: a failure before handoff stays with the current queue execution, while a detached post-handoff platform failure enqueues exactly one replacement.
Tasks now propagate condemned-isolate failures out of journaled steps and apply alarm memory-limit backoff and sealing to the run whose wake struck — claim stripped and deadline pushed, so startup reconciliation cannot resurrect it and the reclaim still sees an interrupted attempt. Task wake jobs are pushed with a single dispatch attempt so a platform failure rejects the alarm instead of being retried into a silent reschedule of the still-claimed run. Lifecycle gains
trackAlarmWork(): work a job hands off at a bounded return stays inside that alarm's memory-limit breaker domain after the alarm returns, so other jobs stay live while a memory reset from the handoff still records a strike — one strike per reset however many flows observe it — and strikes clear only once no handed-off work is outstanding and the last of it settled clean.retain: falsenow removes failed and cancelled runs as well as completed runs, releasing journals and idempotency keys after every terminal outcome. Routed dynamic agents temporarily retain the root-owned schedule transport until Tasks supports routed child wakes.AI Chat and Think require
agents >=0.23.0, the pending release batch containing the shared recovery Task definition and internal enqueue support.Replatform chat's resumable streams onto the
agents/streamscapability.ResumableStreamis now a thin adapter overStreams: chat's in-flight turn output lives in the shared durable chunk log (cf_agents_streams/cf_agents_stream_chunks), packed ~10 wire chunks per stored segment for write economy, with completion/error mapped onto stream settlement and retention keyed off the stream row'supdated_at(sweeps no longer scan the chunk table). Existingcf_ai_chat_stream_*tables migrate wholesale — including an in-flight stream — on first construction after upgrade, then are dropped.AIChatAgentandThinkexpose the backing capability asreadonly streams, so anystreams.read()consumer on the same Durable Object can observe chat streams. The chat wire protocol, replay handshake, and recovery behavior are unchanged.chat: derive the recovery forward-progress marker from the stream log instead of bumping a KV counter per credited chunk.
ResumableStream.progressMarker()counts durably flushed segments — live streams from their log tails, deleted streams from a retired total folded in as their rows are removed — so the marker stays monotonic across cutover and reclaim, never moves on a reconnect replay or a recovery re-persist, and ignores compaction. A parent forwarding a sub-agent's output credits it explicitly throughcreditProgress(). Nothing is written per chunk any more; one row is written per stream retired. The old KV counter is read once per isolate and seeded into the marker so an in-flight incident never sees it drop, and the hosts mirror the marker's durable part back to that key per stream retired, so a rollback reads no lower either. The Streams sync aperture gains anonDeletehook so a chat row deleted through the public capability is retired like any other.AIChatAgentnow flushes a settled tool result to SQLite the moment it is stored, asThinkalready did, so it is durable before the next packed flush and counts as progress immediately. The work budget's unit is now the durable segment, andDEFAULT_CHAT_RECOVERY_MAX_WORKmoves from 1000 to 10000 to stay as generous as before for delta-heavy turns. Two cutover fixes ride along: a Think agent-tool child now keeps its stream rows for the parent to tail after completion, as ai-chat already did, and the Streams capability re-derives its legacy-table flag after a rolled-back cutover.think: stop re-reading the transcript during a turn. A tool update (client result, approval, cross-message result, execution outcome) used to read the whole persisted history to find its one target message; it now resolves the owner from the in-flight accumulator and the live cache and reads that row alone, walking storage newest-first only when the cache does not cover the active path. A chat request no longer reads the path twice and upserts every echoed message: the cache is the server transcript, unchanged messages are skipped before Sessions sees them, and only what changed is written. The cache re-windows itself once appends carry it past
hydrationByteBudget, and marks itself stale on Sessionsimportandcompactionevents. Media eviction is scheduled from an in-memory check on each linear append instead of a stored-path scan after every cache refresh,think_configrewrites of an unchanged request body or client-tool schemas are skipped, and the agent-tool child-run DDL runs once per isolate.Make the alarm memory-limit circuit breaker (#1825) a self-contained
Lifecycle concern instead of an Agent-mediated one.Recovery-loop membership is now a property of the job row
(LifecycleJobPushOptions.recoveryLoop): flagged jobs are backed off by
the breaker on a strike and purged when it seals at the strike budget,
without disturbing unrelated rows — a recovery schedule can no longer
silently escape the breaker. The publicScheduleOptionsvocabulary is
unchanged: schedules only shape future work, and chat recovery reaches the
flag through internal scaffolding (RecoveryLoopScheduleOptions) retained
only for legacy rows and routed dynamic agents. Root recovery moves to Tasks;
the scaffolding can be deleted when Tasks supports routed child wakes.
Capabilities can react to a strike through the new optionalonMemoryLimit
hook, hosts throughonAlarmMemoryLimit, and the context identifies the job
that was executing when one exists. The strike budget is real Lifecycle
configuration (Lifecycle.install(host, { maxAlarmMemoryLimitStrikes }))
rather than a composition-root side channel. Until Tasks supports routed
child wakes, a sealed recovery schedule also forwards the seal to its owning
dynamic agent so a chat child under a plain Agent root persists its exhausted
incident and terminal notification.Removed accordingly:
Agent.onAlarmMemoryLimit's policy relay, the_cf_recoveryAlarmCallbackstemplate hook,Scheduler.applyMemoryLimitPolicy,
andsetLifecycleAlarmMemoryLimitStrikes.AIChatAgentandThinkflag their
routed recovery fallback viachatRecoverySchedulePolicyand seal in-flight
incidents from their own protectedonAlarmMemoryLimithooks; both now
requireagents >= 0.23.0from the pending release batch (they consume its
newagents/chatrecovery exports and no longer implement the old
template-method breaker hooks). Agent retains a
sealed-only call to_cf_sealMemoryLimitedRecoveryso already-published chat
packages whose peer ranges accept agents 0.23 keep terminal notifications;
that fallback carries no callback-name or queue policy.Pass proactively compacted messages to
beforeStepso hooks that append context do not restore the original history. Explicit message overrides still take precedence.
- 0.17.x
0.17.0
27 Aug 2026Make durable chat recovery unconditional for
AIChatAgentandThink.Every chat turn now runs in a recovery fiber, including WebSocket, programmatic, retry, and continuation paths.
chatRecoveryacceptstrueor a configuration object;falseis no longer supported. Previously compiled JavaScript that still suppliesfalsesafely receives the default recovery configuration.To keep durable bookkeeping while preventing automatic inference after an interruption, return
{ continue: false }fromonChatRecovery(). Use durable cancellation, side-effect, or spend state in that hook and tunechatRecoverybudgets when retries must be bounded.Preserve orphaned durable execution outcomes as framework-authored notes, then project them to user context for inference so provider transcript validation cannot reject their arbitrary position. Existing outcome notes receive the same projection without rewriting stored history.
Add
Scheduler, a reusable Lifecycle capability for persistent delayed, dated, cron, and interval callbacks, underagents/schedules. Scheduled callbacks are registered on the Scheduler itself (new Scheduler({ callbacks: { ... } })), andset()/every()type both the callback name and the payload against that registration, so the typed scheduling surface and the runtime dispatch target are the same object.LifecycleCapabilitysupplies every capability with storage, readiness, startup state, alarm coordination, a host invocation boundary, best-effort events, and generic capability routing — Scheduler consumes only that standard surface plus its callbacks and policy options, so any host that installs it configures nothing else. Lifecycle owns the physical Durable Object alarm and routes matching capability messages between Agent facets through one internal transport aperture, preserving existing root-owned facet schedule rows without Scheduler-specific Agent RPC methods or an Agent adapter.Agentuses the same Scheduler behind its existing APIs — name-basedthis.schedule(60, "methodName")keeps dispatching to Agent methods through a composition-root resolver — and preserves callback context, observability, retries, OOM handling, and alarm behavior. MCP now receives storage from Lifecycle when installed. Explicit destruction disposes live capability resources once, then clears shared Durable Object storage withdeleteAll(). Think workflow notifications now contribute their wake time through Lifecycle instead of writing the physical alarm directly. The previousagents/scheduleparser entry point remains as a deprecated compatibility alias. Agent exposes the composition root as experimentalthis.lifecycleandthis.schedulerproperties. Theagents/lifecycleentry point and the capability surfaces built on it (Scheduler, installingMCPClientManagerdirectly as a capability) are experimental and may change between releases; Agent's established APIs are unaffected.Compatibility notes:
MCPClientManagerOptions.storageis removed — the manager receives storage from the Lifecycle it is installed on, so standalone construction with an explicitDurableObjectStorageis no longer supported. Scheduled callbacks now receive the documented parsedScheduleobject as their second argument (previously the raw storage row, whosepayloadwas an unparsed JSON string). The internal_cf_*ForFacetschedule RPC methods are replaced by the generic_cf_routeLifecyclecapability aperture; facets always run the same deployed script, so no coordination is required.
- 0.16.x
0.16.0
18 Aug 2026Remove the convention-driven Think framework, including the Vite plugin, generated Worker entry and virtual modules, CLI, Studio, framework helpers, and server-entry helpers. Remove the separate
create-thinkscaffolder and its starter templates. Think remains available as an explicit runtime for hand-written Worker entries.Migration for existing framework users
If you only use the
Thinkruntime, React integration, messengers, workflows, extensions, or tools, no migration is required.Existing use Required change @cloudflare/think/viteUse agents/vitefor decorators andagents:skills, alongside@cloudflare/vite-plugin.virtual:think/entryAdd a real Worker entry, such as src/server.ts, that explicitly exports each Think class.Generated bindings, migrations, and routing Declare top-level Durable Object bindings and migrations in wrangler.jsonc; route withrouteAgentRequest()and, for custom sub-agent routing,routeSubAgentRequest().@cloudflare/think/frameworkMove generated configuration and class exports into application code. @cloudflare/think/server-entryUse Agents SDK routing helpers and application-owned request handlers. think typesUse wrangler types.think initorcreate-thinkUse npx create-cloudflare@latest --template cloudflare/agents-starter, or manually configure a Think Worker.think studio,think state, orthink inspectNo direct replacement. Existing deployments must preserve generated
ThinkAgent_*andThinkSubAgent_*constructor names and existing migration history. Sub-agent registries useClass.name, so export aliases alone do not preserve compatibility. If renaming classes, use a data-preserving Durable Object rename migration.Preserve spacing between streamed text segments separated by tool calls. Think messenger delivery and Voice now share the same boundary-aware text joining logic from
agents/chat.Existing users must:
Emit workspace image reads as
image-datamodel content so AI SDK v6 routes them as images, while preservingfile-dataoutput for PDFs.Existing consumers that inspect raw workspace tool content must handle
image-datafor images; PDFs continue to usefile-data. Normal tool callers require no change.
Minor Changes
Remove the convention-driven Think framework, including the Vite plugin, generated Worker entry and virtual modules, CLI, Studio, framework helpers, and server-entry helpers. Remove the separate
create-thinkscaffolder and its starter templates. Think remains available as an explicit runtime for hand-written Worker entries.Migration for existing framework users
If you only use the
Thinkruntime, React integration, messengers, workflows, extensions, or tools, no migration is required.Existing use Required change @cloudflare/think/viteUse agents/vitefor decorators andagents:skills, alongside@cloudflare/vite-plugin.virtual:think/entryAdd a real Worker entry, such as src/server.ts, that explicitly exports each Think class.Generated bindings, migrations, and routing Declare top-level Durable Object bindings and migrations in wrangler.jsonc; route withrouteAgentRequest()and, for custom sub-agent routing,routeSubAgentRequest().@cloudflare/think/frameworkMove generated configuration and class exports into application code. @cloudflare/think/server-entryUse Agents SDK routing helpers and application-owned request handlers. think typesUse wrangler types.think initorcreate-thinkUse npx create-cloudflare@latest --template cloudflare/agents-starter, or manually configure a Think Worker.think studio,think state, orthink inspectNo direct replacement. Existing deployments must preserve generated
ThinkAgent_*andThinkSubAgent_*constructor names and existing migration history. Sub-agent registries useClass.name, so export aliases alone do not preserve compatibility. If renaming classes, use a data-preserving Durable Object rename migration.Preserve spacing between streamed text segments separated by tool calls. Think messenger delivery and Voice now share the same boundary-aware text joining logic from
agents/chat.Existing users must:
Replace imports of
textDeltaFromStreamChunk()from@cloudflare/think/messengerswithTextStreamCallback, passing it the complete structured stream events.Upgrade to
agents@0.21.0when installing@cloudflare/think@0.16.0or@cloudflare/voice@0.3.6; both now requireagents >=0.20.2.Update exact-text expectations if they relied on segments around tool calls being concatenated without a space.
Patch Changes
Emit workspace image reads as
image-datamodel content so AI SDK v6 routes them as images, while preservingfile-dataoutput for PDFs.Existing consumers that inspect raw workspace tool content must handle
image-datafor images; PDFs continue to usefile-data. Normal tool callers require no change.Treat
useAgentChatobserver error frames as terminal responses.Plain-text error bodies are no longer parsed as stream chunks or merged into an empty assistant message. Error frames now clear observer streaming, replay, recovery, and tool-continuation state even when they omit
done, matching the transport-owned stream behavior.Existing observer UIs that displayed error bodies as assistant messages must move those diagnostics to a dedicated error surface.
Update PartyServer and the Cloudflare Workers development toolchain for
@cloudflare/workers-typesv5 compatibility. New Think projects now use Workers Types v5 with matching Wrangler and Vite plugin versions.Expose
TurnConfig.repairToolCallso callers can repair malformed tool calls before tool execution.
- 0.15.x
0.15.1
28 Jul 2026Fix AI SDK v7 telemetry, which produced spans with no token counts, no finish reason, no tool results and zero durations.
Spans that must not outlive their invocation now close at the end of it rather than at the first
await. Closing at the handoff ended every WebSocket-turn span before its result existed, so every finish-time attribute was dropped. A span still open when its invocation ends is closed and markedcloudflare.agents.span.truncatedinstead of passing as complete, approval spans decided asynchronously included. A chat turn owns its own boundary rather than its caller's, so a turn that is not awaited — an ack-and-return submit, or an auto-continuation fired from a timer — is no longer cut short by the handler that started it.generateTextis bounded on the same terms asstreamText, and a turn that fails or is cancelled keeps the usage it already reported.chatandexecute_toolspans sit under theirinvoke_agentoperation span on v7, where they were previously emitted as unrelated roots, andtool_approvalsegments sit underexecute_tool.v7 has no telemetry metadata bag, so identity and turn context arrive through
runtimeContextandtelemetry.includeRuntimeContext. Reserved keys project onto the attributes v6 already emits, so a query written against v6 traces still matches v7 ones; other included keys pass through ascloudflare.agents.runtime_context.{key}, and context the caller did not mark stays off the span.
0.15.0
23 Jul 2026Reading the note…
- 0.14.x
0.14.0
22 Jul 2026Reading the note…
- 0.13.x
0.13.0
13 Jul 2026Reading the note…
- 0.12.x
0.12.1
30 Jun 2026Reading the note…
- 0.11.x
0.11.1
28 Jun 2026Reading the note…
0.11.0
26 Jun 2026Reading the note…
- 0.10.x
0.10.0
17 Jun 2026Reading the note…
- 0.9.x
0.9.1
16 Jun 2026Reading the note…
0.9.0
12 Jun 2026Reading the note…
- 0.8.x
0.8.8
8 Jun 2026Reading the note…
0.8.7
8 Jun 2026Reading the note…
0.8.6
7 Jun 2026Reading the note…
0.8.5
7 Jun 2026Reading the note…
0.8.4
5 Jun 2026Reading the note…
0.8.3
5 Jun 2026Reading the note…
0.8.2
3 Jun 2026Reading the note…
0.8.1
3 Jun 2026Reading the note…
0.8.0
2 Jun 2026Reading the note…
- 0.7.x
0.7.3
26 May 2026Reading the note…
0.7.2
21 May 2026Reading the note…
0.7.1
20 May 2026Reading the note…
0.7.0
19 May 2026Reading the note…
- 0.6.x
0.6.1
14 May 2026Reading the note…
0.6.0
13 May 2026Reading the note…
- 0.5.x
0.5.3
2 May 2026Reading the note…
0.5.2
1 May 2026Reading the note…
0.5.1
1 May 2026Reading the note…
0.5.0
30 Apr 2026Reading the note…
- 0.4.x
0.4.2
28 Apr 2026Reading the note…
0.4.1
27 Apr 2026Reading the note…
0.4.0
23 Apr 2026Reading the note…
- 0.3.x
0.3.0
18 Apr 2026Reading the note…
- 0.2.x
0.2.5
18 Apr 2026Reading the note…
0.2.4
15 Apr 2026Reading the note…
0.2.3
15 Apr 2026Reading the note…
0.2.2
14 Apr 2026Reading the note…
0.2.1
11 Apr 2026Reading the note…
0.2.0
7 Apr 2026Reading the note…
- 0.1.x
0.1.2
1 Apr 2026Reading the note…
0.1.1
30 Mar 2026Reading the note…
0.1.0
23 Mar 2026Reading the note…