Skip to content

fix(think): deliver the recovered answer to the messenger thread (#2106) - #2347

Merged
threepointone merged 7 commits into
fix/2166-observer-snapshot-healfrom
fix/2106-messenger-recovered-delivery
Sep 24, 2026
Merged

threepointone merged 7 commits into
fix/2166-observer-snapshot-healfrom
fix/2106-messenger-recovered-delivery

Conversation

@threepointone

@threepointone threepointone commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2106.

When a messenger turn stalls or hits a transient stream error and bounded chat recovery takes over, the messenger posted the interruption apology and dropped the reply. Recovery then finished the answer in the Think transcript, but nothing sent it to the thread.

Now, before recovery is scheduled for a messenger turn, Think writes a pending-delivery record with the messenger id, thread id and the partial text already posted. The streaming callback is told (onInterrupted({ deliversRecoveredReply: true })) so the messenger skips its apology. When recovery settles:

  • completed: the recovered text, minus the partial prefix already posted, goes to the thread.
  • exhausted, skipped or failed: the interruption apology goes to the thread.

The agent that owns the messenger runtime delivers the post. That is the host, reached from a sub-agent through parentAgent(...)._cf_deliverRecoveredMessengerReply. The record is deleted after delivery and replayed from onStart, so delivery is at-least-once.

Deviation from the agreed design. The record lives on the agent that runs recovery (the sub-agent for thread conversations), not on the host. The sub-agent writes it before scheduling recovery, so a fast recovery cannot settle before the host has registered anything.

Limitation. This covers interruptions inside the isolate on the RPC path. If the Durable Object restarts mid-turn, host fiber recovery still posts the apology as before.

Also fixed: resolveTarget now wraps the sub-agent stub and exposes only the methods the messenger calls. Before, bindActiveDeliverySurface sent a chat-sdk ThreadImpl over RPC and threw a DataCloneError.

  • Tests: messengers.test.ts covers self, thread and exhausted recovery end to end, plus a delivery unit test. The self and thread tests fail without the fix.

Delivery edge cases. Repeated interruptions rely on #2343, which leaves the incident to the next scheduled attempt instead of emitting chat:recovery:failed early. If the stream post itself was rejected, the whole streamed text is posted before the recovered reply. A recovered reply with no text posts the empty-response text, unless a partial was already posted. Text past the messenger's visibleSoftLimit that was buffered at the interruption is now posted before the recovered reply.

@threepointone
threepointone added this pull request to stack #2345 September 23, 2026 20:30
@changeset-bot

changeset-bot Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e84e6b0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@cloudflare/think Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@agent-think

agent-think Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

⚪ agents import sizes

Measured 344 runtime imports as minified bundles. The primary size is gzip; raw minified size is included for diagnosis. An existing import growing by more than 10% is marked red. This report is informational.

Red Yellow Green Unchanged New Removed
0 0 0 344 0 0

Compared b1e8c58c with e84e6b0c. Open workflow run.

No import sizes changed.

All 344 current runtime imports
Status Import Gzip Raw minified
⚪ agents#__DO_NOT_USE_WILL_BREAK__agentContext 261.1 KiB 1141.0 KiB
⚪ agents#__DO_NOT_USE_WILL_BREAK__withInvocationScope 261.1 KiB 1141.0 KiB
⚪ agents#Agent 261.1 KiB 1141.0 KiB
⚪ agents#AGENT_TOOL_MILESTONE_PART 261.1 KiB 1141.0 KiB
⚪ agents#AGENT_TOOL_PROGRESS_PART 261.1 KiB 1141.0 KiB
⚪ agents#buildAgentPath 261.7 KiB 1143.3 KiB
⚪ agents#buildAgentUrl 261.8 KiB 1143.7 KiB
⚪ agents#callable 261.1 KiB 1141.0 KiB
⚪ agents#camelCaseToKebabCase 261.1 KiB 1141.0 KiB
⚪ agents#createHeaderBasedEmailResolver 261.3 KiB 1141.4 KiB
⚪ agents#DEFAULT_AGENT_STATIC_OPTIONS 261.1 KiB 1141.0 KiB
⚪ agents#DurableObjectOAuthClientProvider 261.1 KiB 1141.0 KiB
⚪ agents#getAgentByName 261.1 KiB 1141.0 KiB
⚪ agents#getCurrentAgent 261.1 KiB 1141.0 KiB
⚪ agents#getSubAgentByName 261.4 KiB 1141.6 KiB
⚪ agents#isDurableObjectCodeUpdateReset 261.1 KiB 1141.0 KiB
⚪ agents#isDurableObjectMemoryLimitReset 261.1 KiB 1141.0 KiB
⚪ agents#isDurableObjectStorageReset 261.1 KiB 1141.0 KiB
⚪ agents#isPlatformTransientError 261.1 KiB 1141.0 KiB
⚪ agents#MCP_SERVER_ID_MAX_LENGTH 261.1 KiB 1141.0 KiB
⚪ agents#MessageType 261.2 KiB 1141.3 KiB
⚪ agents#normalizeServerId 261.1 KiB 1141.0 KiB
⚪ agents#parseSubAgentPath 261.1 KiB 1141.0 KiB
⚪ agents#routeAgentEmail 261.4 KiB 1141.7 KiB
⚪ agents#routeAgentRequest 261.7 KiB 1142.9 KiB
⚪ agents#routeSubAgentRequest 261.3 KiB 1141.5 KiB
⚪ agents#SqlError 261.1 KiB 1141.0 KiB
⚪ agents#StreamingResponse 261.1 KiB 1141.0 KiB
⚪ agents#SUB_PREFIX 261.1 KiB 1141.0 KiB
⚪ agents#unstable_callable 261.2 KiB 1141.2 KiB
⚪ agents/agent-tools#agentTool 112.5 KiB 538.2 KiB
⚪ agents/browser#BrowserConnector 50.5 KiB 176.6 KiB
⚪ agents/browser#browserContent 36.3 KiB 127.4 KiB
⚪ agents/browser#browserExtract 36.3 KiB 127.4 KiB
⚪ agents/browser#browserLinks 36.3 KiB 127.4 KiB
⚪ agents/browser#browserMarkdown 36.3 KiB 127.4 KiB
⚪ agents/browser#browserPdf 36.3 KiB 127.3 KiB
⚪ agents/browser#BrowserRenderingError 36.0 KiB 126.7 KiB
⚪ agents/browser#browserScrape 36.3 KiB 127.4 KiB
⚪ agents/browser#browserScreenshot 36.3 KiB 127.3 KiB
⚪ agents/browser#browserSnapshot 36.3 KiB 127.4 KiB
⚪ agents/browser#CdpSession 37.2 KiB 129.8 KiB
⚪ agents/browser#CodemodeRuntime 39.6 KiB 139.0 KiB
⚪ agents/browser#connectBrowser 37.8 KiB 131.4 KiB
⚪ agents/browser#connectBrowserSession 37.5 KiB 130.4 KiB
⚪ agents/browser#connectUrl 37.6 KiB 130.5 KiB
⚪ agents/browser#createBrowserSession 36.3 KiB 127.5 KiB
⚪ agents/browser#DEFAULT_EXEC_SWEEP_IDLE_MS 36.0 KiB 126.6 KiB
⚪ agents/browser#DEFAULT_SWEEP_IDLE_MS 36.0 KiB 126.6 KiB
⚪ agents/browser#deleteBrowserSession 36.1 KiB 126.9 KiB
⚪ agents/browser#DurableBrowserSessionStore 36.4 KiB 127.6 KiB
⚪ agents/browser#getBrowserRecording 36.2 KiB 127.1 KiB
⚪ agents/browser#listBrowserTargets 36.1 KiB 126.9 KiB
⚪ agents/browser#loadCdpSpec 36.6 KiB 128.3 KiB
⚪ agents/browser#runQuickAction 36.0 KiB 126.6 KiB
⚪ agents/browser/ai#createBrowserRuntime 147.0 KiB 632.8 KiB
⚪ agents/browser/ai#createBrowserTools 147.0 KiB 632.9 KiB
⚪ agents/browser/ai#createQuickActionTools 122.5 KiB 554.3 KiB
⚪ agents/browser/tanstack-ai#createBrowserTools 162.7 KiB 701.7 KiB
⚪ agents/channels#ChannelHost 3.3 KiB 9.1 KiB
⚪ agents/channels#consumeChunks 237 B 321 B
⚪ agents/channels#createUserIdentityStore 1.3 KiB 3.2 KiB
⚪ agents/channels#fallback 155 B 160 B
⚪ agents/channels#fallbackChannel 893 B 1.9 KiB
⚪ agents/channels#fanout 151 B 156 B
⚪ agents/channels#fanoutChannel 875 B 1.9 KiB
⚪ agents/channels#identityKey 178 B 223 B
⚪ agents/channels#isChannelMessageSurface 271 B 452 B
⚪ agents/channels#linkChannelIdentities 113 B 99 B
⚪ agents/channels#matchesPath 110 B 96 B
⚪ agents/channels#routes 307 B 507 B
⚪ agents/channels#UserIdentityConflictError 248 B 335 B
⚪ agents/channels/ai-sdk#createSendMessageTool 112.2 KiB 537.4 KiB
⚪ agents/channels/ai-sdk#toChannelChunks 112.5 KiB 538.4 KiB
⚪ agents/channels/email#email 23.5 KiB 75.4 KiB
⚪ agents/channels/email#inboundEmail 22.3 KiB 72.3 KiB
⚪ agents/channels/slack#slack 5.5 KiB 14.6 KiB
⚪ agents/channels/slack#slackWebhook 2.2 KiB 5.1 KiB
⚪ agents/channels/tanstack-ai#createSendMessageTool 16.2 KiB 68.4 KiB
⚪ agents/channels/telegram#telegram 3.8 KiB 10.3 KiB
⚪ agents/channels/telegram#telegramWebhook 1.6 KiB 3.6 KiB
⚪ agents/channels/voice#browserVoice 1010 B 2.2 KiB
⚪ agents/chat#AbortRegistry 2.5 KiB 8.9 KiB
⚪ agents/chat#AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#AgentToolProgressEmitter 2.7 KiB 9.5 KiB
⚪ agents/chat#AgentToolStreamProgressThrottle 2.4 KiB 8.3 KiB
⚪ agents/chat#aiSdkRecoveryCodec 2.3 KiB 8.2 KiB
⚪ agents/chat#applyAgentToolEvent 3.2 KiB 11.0 KiB
⚪ agents/chat#applyChunkToParts 2.3 KiB 8.2 KiB
⚪ agents/chat#applyToolUpdate 2.4 KiB 8.4 KiB
⚪ agents/chat#AutoContinuationController 2.3 KiB 8.2 KiB
⚪ agents/chat#awaitWithDeadline 2.4 KiB 8.4 KiB
⚪ agents/chat#broadcastTransition 3.2 KiB 11.6 KiB
⚪ agents/chat#buildChatRecoveringFrame 2.4 KiB 8.4 KiB
⚪ agents/chat#buildInClauseStrings 2.4 KiB 8.4 KiB
⚪ agents/chat#bumpChatRecoveryProgress 2.4 KiB 8.3 KiB
⚪ agents/chat#byteLength 2.5 KiB 8.5 KiB
⚪ agents/chat#CHAT_LAST_TERMINAL_KEY 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_MESSAGE_TYPES 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERING_FLAG_TTL_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERING_KEY 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_ALARM_DEBOUNCE_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_INCIDENT_KEY_PREFIX 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_INCIDENT_TTL_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_PROGRESS_KEY 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_TASK_NAME 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#ChatRecoveryEngine 4.5 KiB 15.5 KiB
⚪ agents/chat#chatRecoveryTaskRunOptions 2.4 KiB 8.6 KiB
⚪ agents/chat#ChatStreamStalledError 2.4 KiB 8.3 KiB
⚪ agents/chat#classifyAgentToolChildRecovery 2.4 KiB 8.5 KiB
⚪ agents/chat#clearChatTerminal 2.3 KiB 8.3 KiB
⚪ agents/chat#clientResolvableToolNames 2.3 KiB 8.3 KiB
⚪ agents/chat#ContinuationState 2.7 KiB 9.8 KiB
⚪ agents/chat#createAgentToolEventState 2.3 KiB 8.3 KiB
⚪ agents/chat#createChatFiberSnapshot 2.5 KiB 8.6 KiB
⚪ agents/chat#createChatRecoveryTaskDefinition 2.6 KiB 9.0 KiB
⚪ agents/chat#createChatStreams 6.4 KiB 22.1 KiB
⚪ agents/chat#createChatTurnTaskDefinition 2.7 KiB 8.9 KiB
⚪ agents/chat#createToolsFromClientSchemas 114.3 KiB 545.4 KiB
⚪ agents/chat#crossMessageToolResultUpdate 2.4 KiB 8.6 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_MAX_WORK 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE 2.4 KiB 8.3 KiB
⚪ agents/chat#dispatchChatRecoveryToHandoff 3.0 KiB 9.9 KiB
⚪ agents/chat#drainInteractionApplies 2.3 KiB 8.3 KiB
⚪ agents/chat#enforceRowSizeLimit 3.5 KiB 11.2 KiB
⚪ agents/chat#hasIncompleteToolBatch 2.4 KiB 8.6 KiB
⚪ agents/chat#interceptAgentToolBroadcast 2.5 KiB 8.7 KiB
⚪ agents/chat#isPlatformFailure 2.6 KiB 8.9 KiB
⚪ agents/chat#isReplayChunk 2.4 KiB 8.7 KiB
⚪ agents/chat#iterateWithStallWatchdog 2.6 KiB 8.8 KiB
⚪ agents/chat#KV_DELETE_MAX_KEYS 2.3 KiB 8.2 KiB
⚪ agents/chat#listActiveChatRecoveryIncidents 2.4 KiB 8.4 KiB
⚪ agents/chat#MAX_BOUND_PARAMS 2.3 KiB 8.2 KiB
⚪ agents/chat#MessageType 2.4 KiB 9.0 KiB
⚪ agents/chat#normalizeToolInput 2.3 KiB 8.2 KiB
⚪ agents/chat#parseProtocolMessage 2.5 KiB 9.0 KiB
⚪ agents/chat#partAwaitsClientInteraction 2.4 KiB 8.6 KiB
⚪ agents/chat#pausedExecutionUpdate 2.4 KiB 8.4 KiB
⚪ agents/chat#pendingChatTerminal 2.3 KiB 8.3 KiB
⚪ agents/chat#persistReconstructedOrphan 3.1 KiB 11.0 KiB
⚪ agents/chat#PreStreamTurns 2.6 KiB 9.3 KiB
⚪ agents/chat#readChatRecoveryProgress 2.3 KiB 8.3 KiB
⚪ agents/chat#reconcileMessages 2.8 KiB 9.6 KiB
⚪ agents/chat#reconcileOrphanPartial 2.4 KiB 8.5 KiB
⚪ agents/chat#recordChatTerminal 2.4 KiB 8.3 KiB
⚪ agents/chat#repairInterruptedToolParts 2.6 KiB 9.1 KiB
⚪ agents/chat#resolveChatRecoveryConfig 2.6 KiB 8.9 KiB
⚪ agents/chat#resolveToolMergeId 2.4 KiB 8.5 KiB
⚪ agents/chat#ResumableStream 5.1 KiB 16.8 KiB
⚪ agents/chat#ResumeHandshake 3.0 KiB 10.4 KiB
⚪ agents/chat#ROW_MAX_BYTES 2.3 KiB 8.2 KiB
⚪ agents/chat#runChatRecoveryExhaustion 2.6 KiB 8.9 KiB
⚪ agents/chat#sanitizeMessage 2.5 KiB 8.8 KiB
⚪ agents/chat#sendIfOpen 2.4 KiB 8.4 KiB
⚪ agents/chat#setChatRecovering 2.5 KiB 8.5 KiB
⚪ agents/chat#shouldCreditStreamProgress 2.4 KiB 8.3 KiB
⚪ agents/chat#STREAM_RESUME_NONE_REASONS 2.3 KiB 8.2 KiB
⚪ agents/chat#StreamAccumulator 2.9 KiB 10.7 KiB
⚪ agents/chat#StreamProgressCreditThrottle 2.4 KiB 8.3 KiB
⚪ agents/chat#SubmitConcurrencyController 2.9 KiB 10.3 KiB
⚪ agents/chat#sweepStaleChatRecoveryIncidents 2.4 KiB 8.5 KiB
⚪ agents/chat#TextSegmentJoiner 2.7 KiB 9.2 KiB
⚪ agents/chat#TIMED_OUT 2.3 KiB 8.2 KiB
⚪ agents/chat#toolApprovalUpdate 2.4 KiB 8.5 KiB
⚪ agents/chat#toolPartHasSettledResult 2.3 KiB 8.4 KiB
⚪ agents/chat#toolResultUpdate 2.4 KiB 8.5 KiB
⚪ agents/chat#truncateOlderMessages 3.2 KiB 10.5 KiB
⚪ agents/chat#truncateOlderToolResults 3.3 KiB 10.9 KiB
⚪ agents/chat#TurnQueue 2.6 KiB 9.2 KiB
⚪ agents/chat#unwrapChatFiberSnapshot 2.4 KiB 8.5 KiB
⚪ agents/chat#wrapChatFiberSnapshot 2.3 KiB 8.2 KiB
⚪ agents/chat-sdk#ChatSdkStateAdapter 263.5 KiB 1152.6 KiB
⚪ agents/chat-sdk#ChatSdkStateAgent 262.8 KiB 1150.0 KiB
⚪ agents/chat-sdk#createChatSdkState 263.5 KiB 1152.6 KiB
⚪ agents/chat-sdk#defaultKeyShard 261.2 KiB 1141.2 KiB
⚪ agents/chat-sdk#defaultThreadShard 261.1 KiB 1141.0 KiB
⚪ agents/chat/react#detectToolsRequiringConfirmation 3.3 KiB 8.3 KiB
⚪ agents/chat/react#extractClientToolSchemas 3.2 KiB 8.3 KiB
⚪ agents/chat/react#getAgentMessages 3.4 KiB 8.6 KiB
⚪ agents/chat/react#getToolApproval 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolCallId 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolInput 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolOutput 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolPartState 3.2 KiB 8.2 KiB
⚪ agents/chat/react#useAgentChat 132.9 KiB 609.9 KiB
⚪ agents/chat/react#WebSocketChatTransport 5.7 KiB 17.1 KiB
⚪ agents/chat/transport#WebSocketChatTransport 2.8 KiB 9.2 KiB
⚪ agents/client#AgentClient 21.8 KiB 75.2 KiB
⚪ agents/client#AgentConnectionError 16.3 KiB 57.8 KiB
⚪ agents/client#agentFetch 19.7 KiB 69.0 KiB
⚪ agents/client#createStubProxy 16.4 KiB 57.8 KiB
⚪ agents/client#DEFAULT_CALL_TIMEOUT_MS 16.2 KiB 57.6 KiB
⚪ agents/client#isTerminalCloseEvent 16.3 KiB 57.6 KiB
⚪ agents/client#nativeCall 16.5 KiB 58.3 KiB
⚪ agents/client#NativeCallQueue 16.7 KiB 58.9 KiB
⚪ agents/client#splitCallOptions 16.3 KiB 57.7 KiB
⚪ agents/context#AgentContextProvider 412 B 792 B
⚪ agents/context#AgentSearchProvider 640 B 1.3 KiB
⚪ agents/context#ContextBlocks 87.4 KiB 429.7 KiB
⚪ agents/email#createAddressBasedEmailResolver 193 B 227 B
⚪ agents/email#createCatchAllEmailResolver 110 B 97 B
⚪ agents/email#createHeaderBasedEmailResolver 334 B 492 B
⚪ agents/email#createSecureReplyEmailResolver 718 B 1.3 KiB
⚪ agents/email#DEFAULT_MAX_AGE_SECONDS 56 B 39 B
⚪ agents/email#isAutoReplyEmail 201 B 249 B
⚪ agents/email#signAgentHeaders 424 B 812 B
⚪ agents/experimental/webmcp#registerWebMcp 85.2 KiB 295.8 KiB
⚪ agents/lifecycle#getCurrentAgent 376 B 798 B
⚪ agents/lifecycle#Lifecycle 8.4 KiB 26.1 KiB
⚪ agents/lifecycle#LifecycleCapability 497 B 999 B
⚪ agents/mcp#createLegacyMcpHandler 378.6 KiB 1583.3 KiB
⚪ agents/mcp#createMcpHandler 390.9 KiB 1628.5 KiB
⚪ agents/mcp#DurableObjectEventStore 345.1 KiB 1441.8 KiB
⚪ agents/mcp#ElicitRequestSchema 345.1 KiB 1441.8 KiB
⚪ agents/mcp#experimental_createMcpHandler 378.7 KiB 1583.6 KiB
⚪ agents/mcp#getMcpAuthContext 345.1 KiB 1441.9 KiB
⚪ agents/mcp#MCP_SERVER_ID_MAX_LENGTH 345.1 KiB 1441.9 KiB
⚪ agents/mcp#McpAgent 345.1 KiB 1441.8 KiB
⚪ agents/mcp#normalizeServerId 345.1 KiB 1441.8 KiB
⚪ agents/mcp#RPC_DO_PREFIX 345.1 KiB 1441.8 KiB
⚪ agents/mcp#RPCClientTransport 345.1 KiB 1441.8 KiB
⚪ agents/mcp#RPCServerTransport 345.1 KiB 1441.8 KiB
⚪ agents/mcp#SSEEdgeClientTransport 345.2 KiB 1442.1 KiB
⚪ agents/mcp#StreamableHTTPEdgeClientTransport 345.2 KiB 1442.1 KiB
⚪ agents/mcp#WorkerTransport 348.5 KiB 1458.7 KiB
⚪ agents/mcp/client#getNamespacedData 62.9 KiB 240.0 KiB
⚪ agents/mcp/client#MCP_SERVER_ID_MAX_LENGTH 62.9 KiB 239.9 KiB
⚪ agents/mcp/client#MCPClientManager 158.6 KiB 702.6 KiB
⚪ agents/mcp/client#normalizeServerId 63.0 KiB 240.2 KiB
⚪ agents/mcp/do-oauth-client-provider#DurableObjectOAuthClientProvider 2.1 KiB 6.6 KiB
⚪ agents/mcp/server#createMcpHandler 80.5 KiB 307.2 KiB
⚪ agents/mcp/server#getMcpAuthContext 64.0 KiB 245.5 KiB
⚪ agents/observability#channels 259 B 549 B
⚪ agents/observability#genericObservability 470 B 1.2 KiB
⚪ agents/observability#subscribe 324 B 668 B
⚪ agents/observability/ai#wrapAISDK 8.8 KiB 30.5 KiB
⚪ agents/queue#Queue 3.1 KiB 9.6 KiB
⚪ agents/react#_testUtils 19.3 KiB 66.4 KiB
⚪ agents/react#useAgent 27.1 KiB 89.9 KiB
⚪ agents/react#useAgentToolEvents 21.1 KiB 73.7 KiB
⚪ agents/routing#getAgentByName 795 B 1.7 KiB
⚪ agents/routing#routeAgentRequest 1.6 KiB 3.6 KiB
⚪ agents/routing#RoutedAgents 2.4 KiB 6.2 KiB
⚪ agents/schedule#getSchedulePrompt 85.8 KiB 424.7 KiB
⚪ agents/schedule#scheduleSchema 85.3 KiB 423.6 KiB
⚪ agents/schedule#unstable_getSchedulePrompt 85.9 KiB 424.9 KiB
⚪ agents/schedule#unstable_scheduleSchema 85.3 KiB 423.6 KiB
⚪ agents/schedules#Scheduler 6.8 KiB 22.0 KiB
⚪ agents/schedules/parser#getSchedulePrompt 85.8 KiB 424.7 KiB
⚪ agents/schedules/parser#scheduleSchema 85.3 KiB 423.6 KiB
⚪ agents/sessions#COMPACTION_PREFIX 147 B 160 B
⚪ agents/sessions#createCompactFunction 1.7 KiB 4.0 KiB
⚪ agents/sessions#isCompactionMessage 177 B 200 B
⚪ agents/sessions#Session 2.0 KiB 6.3 KiB
⚪ agents/sessions#Sessions 9.6 KiB 34.3 KiB
⚪ agents/skills#fromManifest 309.8 KiB 1084.0 KiB
⚪ agents/skills#parseSkillFrontmatter 328.4 KiB 1146.2 KiB
⚪ agents/skills#parseSkillMarkdown 328.6 KiB 1146.5 KiB
⚪ agents/skills#r2 330.2 KiB 1150.4 KiB
⚪ agents/skills#runner 369.0 KiB 1297.8 KiB
⚪ agents/skills#SkillRegistry 416.4 KiB 1581.7 KiB
⚪ agents/skills/compile#compileSkillScript 15.4 KiB 43.4 KiB
⚪ agents/skills/compile#isCompilableSkillScript 15.4 KiB 43.3 KiB
⚪ agents/state#State 1.1 KiB 2.4 KiB
⚪ agents/streams#DEFAULT_MAX_CHUNK_BYTES 94 B 96 B
⚪ agents/streams#sseResponse 857 B 1.6 KiB
⚪ agents/streams#StreamClosedError 175 B 212 B
⚪ agents/streams#StreamNotFoundError 215 B 276 B
⚪ agents/streams#Streams 4.3 KiB 13.9 KiB
⚪ agents/streams#StreamSerializationError 171 B 201 B
⚪ agents/tasks#DuplicateTaskStepError 328 B 463 B
⚪ agents/tasks#MAX_SERIALIZED_BYTES 190 B 232 B
⚪ agents/tasks#MissingTaskDefinitionError 358 B 536 B
⚪ agents/tasks#NonRetryableError 238 B 308 B
⚪ agents/tasks#TaskReplayDivergedError 341 B 483 B
⚪ agents/tasks#Tasks 8.9 KiB 31.8 KiB
⚪ agents/tasks#TaskSerializationError 258 B 339 B
⚪ agents/types#MessageType 211 B 365 B
⚪ agents/vite#default 353.8 KiB 1356.1 KiB
⚪ agents/voice#addSFUTracks 324 B 424 B
⚪ agents/voice#createSFUSession 255 B 306 B
⚪ agents/voice#createSFUWebSocketAdapter 331 B 429 B
⚪ agents/voice#decodeVarint 157 B 160 B
⚪ agents/voice#downsample48kStereoTo16kMono 238 B 324 B
⚪ agents/voice#encodePayloadToProtobuf 189 B 279 B
⚪ agents/voice#encodeVarint 129 B 122 B
⚪ agents/voice#extractPayloadFromProtobuf 271 B 425 B
⚪ agents/voice#iterateText 1.6 KiB 3.8 KiB
⚪ agents/voice#renegotiateSFUSession 329 B 428 B
⚪ agents/voice#SentenceChunker 550 B 1.1 KiB
⚪ agents/voice#sfuFetch 294 B 358 B
⚪ agents/voice#upsample16kMonoTo48kStereo 211 B 272 B
⚪ agents/voice#VOICE_PROTOCOL_VERSION 51 B 31 B
⚪ agents/voice#withVoice 9.5 KiB 32.7 KiB
⚪ agents/voice#withVoiceInput 4.8 KiB 16.0 KiB
⚪ agents/voice#WorkersAIFluxSTT 1.5 KiB 4.3 KiB
⚪ agents/voice#WorkersAINova3STT 1.6 KiB 4.3 KiB
⚪ agents/voice#WorkersAITTS 682 B 1.4 KiB
⚪ agents/voice/client#VOICE_PROTOCOL_VERSION 473 B 768 B
⚪ agents/voice/client#VoiceClient 10.1 KiB 31.6 KiB
⚪ agents/voice/client#WebSocketVoiceTransport 4.6 KiB 13.3 KiB
⚪ agents/voice/errors#logVoiceError 132 B 173 B
⚪ agents/voice/errors#toVoiceError 94 B 80 B
⚪ agents/voice/errors#voiceErrorMessage 125 B 111 B
⚪ agents/voice/errors#VoiceProviderError 190 B 345 B
⚪ agents/voice/react#useVoiceAgent 13.6 KiB 42.5 KiB
⚪ agents/voice/react#useVoiceInput 13.3 KiB 41.4 KiB
⚪ agents/voice/react#WebSocketVoiceTransport 7.4 KiB 21.3 KiB
⚪ agents/voice/sfu#addSFUTracks 323 B 424 B
⚪ agents/voice/sfu#createSFUSession 254 B 306 B
⚪ agents/voice/sfu#createSFUWebSocketAdapter 329 B 429 B
⚪ agents/voice/sfu#decodeVarint 155 B 160 B
⚪ agents/voice/sfu#downsample48kStereoTo16kMono 236 B 324 B
⚪ agents/voice/sfu#encodePayloadToProtobuf 188 B 279 B
⚪ agents/voice/sfu#encodeVarint 129 B 122 B
⚪ agents/voice/sfu#extractPayloadFromProtobuf 270 B 425 B
⚪ agents/voice/sfu#renegotiateSFUSession 329 B 428 B
⚪ agents/voice/sfu#sfuFetch 292 B 358 B
⚪ agents/voice/sfu#upsample16kMonoTo48kStereo 209 B 272 B
⚪ agents/voice/text#iterateText 1.6 KiB 3.8 KiB
⚪ agents/voice/text#SentenceChunker 549 B 1.1 KiB
⚪ agents/voice/types#VOICE_PROTOCOL_VERSION 51 B 31 B
⚪ agents/voice/workers-ai#WorkersAIFluxSTT 1.5 KiB 4.3 KiB
⚪ agents/voice/workers-ai#WorkersAINova3STT 1.6 KiB 4.3 KiB
⚪ agents/voice/workers-ai#WorkersAITTS 682 B 1.4 KiB
⚪ agents/websockets#CAPNWEB_TRANSPORT_QUERY 12.4 KiB 43.3 KiB
⚪ agents/websockets#CAPNWEB_TRANSPORT_VALUE 12.4 KiB 43.3 KiB
⚪ agents/websockets#capnWebTransportUrl 12.5 KiB 43.5 KiB
⚪ agents/websockets#CF_NO_PROTOCOL_KEY 12.4 KiB 43.3 KiB
⚪ agents/websockets#CF_READONLY_KEY 12.4 KiB 43.3 KiB
⚪ agents/websockets#isCapnWebTransportUpgrade 12.5 KiB 43.4 KiB
⚪ agents/websockets#registerInternalConnectionKeys 12.4 KiB 43.4 KiB
⚪ agents/websockets#WebSockets 20.0 KiB 69.3 KiB
⚪ agents/workflows#AgentWorkflow 262.4 KiB 1145.7 KiB
⚪ agents/workflows#WorkflowRejectedError 261.2 KiB 1141.2 KiB
⚪ agents/x402#normalizeNetwork 14.7 KiB 61.1 KiB
⚪ agents/x402#withX402 23.0 KiB 89.2 KiB
⚪ agents/x402#withX402Client 104.1 KiB 346.5 KiB

Reported by agent-think[bot].

@threepointone
threepointone marked this pull request as ready for review September 23, 2026 20:33

@devin-ai-integration devin-ai-integration Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

Devin Review found 4 potential issues.

Devin Review

Comment thread packages/think/src/think.ts Outdated
Comment thread packages/think/src/think.ts Outdated
Comment on lines +17692 to +17696
await host._cf_deliverRecoveredMessengerReply(input);
} else {
await this._cf_deliverRecoveredMessengerReply(input);
}
await this.ctx.storage.delete(key);

@devin-ai-integration devin-ai-integration Bot Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Recovery delivery remains at-least-once

Deletion follows the provider post, so a reset between them can duplicate recovered text. The documentation explicitly records this tradeoff.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, at-least-once is the intended tradeoff and is documented.

Comment thread docs/think/messengers.md
Comment on lines +226 to +233
A reply can also be interrupted without a restart, for example by the stream
stall watchdog or by a model error that `classifyChatError` marks as
`"transient"` or `"rate_limit"`. Think then continues the turn through chat
recovery and posts the rest of the answer to the thread as a new message once
the continuation finishes. This works whether the conversation target is the
root agent or a per-thread sub-agent. If recovery gives up, Think posts the
configured interruption message instead. A reset after recovery finishes but
before the post lands can post the recovered text twice.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Upstream documentation needs synchronization

Repository rules require manually porting this messenger documentation change to the corresponding cloudflare/cloudflare-docs MDX file.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted; the cloudflare-docs port happens when the stack lands.

@pkg-pr-new

pkg-pr-new Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2347

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2347

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2347

hono-agents

npm i https://pkg.pr.new/hono-agents@2347

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2347

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2347

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2347

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2347

commit: e84e6b0

@threepointone
threepointone force-pushed the fix/2106-messenger-recovered-delivery branch from 2c7ee65 to 8947afa Compare September 23, 2026 21:07
devin-ai-integration[bot]

This comment was marked as resolved.

@threepointone
threepointone force-pushed the fix/2106-messenger-recovered-delivery branch from 8947afa to 422f2a2 Compare September 23, 2026 21:39
devin-ai-integration[bot]

This comment was marked as resolved.

@threepointone
threepointone force-pushed the fix/2106-messenger-recovered-delivery branch from 422f2a2 to eec566b Compare September 23, 2026 22:09
devin-ai-integration[bot]

This comment was marked as resolved.

@threepointone
threepointone force-pushed the fix/2106-messenger-recovered-delivery branch from eec566b to 92b4992 Compare September 23, 2026 22:47

@devin-ai-integration devin-ai-integration Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

Devin Review found 2 new potential issues.

Devin Review

): void {
const pinnedText = this._recoveredReplyText.get(incidentId);
this._recoveredReplyText.delete(incidentId);
void this.keepAliveWhile(async () => {

@devin-ai-integration devin-ai-integration Bot Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Reset strands recovered messenger delivery

A terminal event starts _settleMessengerRecovery without awaiting its outcome write. A reset can leave an outcome-less record that startup skips forever.

Learn more

The recovery engine persists the terminal transition before emitting the event; completed incidents are deleted in updateIncident. _settleMessengerRecovery then starts a detached task whose first durable action happens later. A reset in that gap cannot recreate the terminal event because the incident is already terminal or gone. Startup finds the pending messenger record, but _replayMessengerRecoveryDeliveries ignores records without outcome.

Example: Recovery completes and deletes its incident. The object resets before the detached task writes outcome: "completed". On every later start, the pending record remains without an outcome, so the thread never receives the recovered text.

Recommended fix: Make terminal recovery settlement await the durable outcome write before completing the incident transition, or persist enough terminal delivery state atomically before the incident becomes unrecoverable. Delivery itself can remain asynchronous and at-least-once after that write.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid, fixed. The recovery callbacks now durably write the messenger reply outcome (and the recovered text) before the incident transition that completes or ends it. Settlement then delivers a record that already has an outcome instead of skipping it, so a reset after the incident settles leaves a deliverable record for startup replay. As a backstop for transitions that settle without that staging, startup replay also gives an outcome-less record whose incident is gone, skipped or exhausted the interrupted apology. A failed incident is left alone, since the platform may still re-run it. Covered by "stores the reply outcome before recovery reports completion" and "apologizes on start for a pending reply whose incident is gone".

Comment on lines +650 to +661
const stub = (await this.host.subAgent(
agentClass,
target.name
)) as unknown as MessengerDeliveryTarget;
)) as unknown as Required<MessengerThinkTarget>;
// A live thread cannot cross the sub-agent RPC boundary, so the stub's
// `bindActiveDeliverySurface` must not be offered to delivery.
return {
cancelChat: (requestId, reason) => stub.cancelChat(requestId, reason),
chat: (userMessage, callback) => stub.chat(userMessage, callback),
chatWithMessengerContext: (userMessage, callback, context) =>
stub.chatWithMessengerContext(userMessage, callback, context)
};

@devin-ai-integration devin-ai-integration Bot Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Sub-agent wrapper preserves RPC boundary

The wrapper omits bindActiveDeliverySurface, preventing live thread serialization while retaining chat, cancellation, and messenger context RPCs.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Yes, that is intended: the live ThreadImpl stays on the host side, and the sub-agent only receives the chat, cancellation and messenger-context calls.

@threepointone
threepointone force-pushed the fix/2106-messenger-recovered-delivery branch from 92b4992 to 4008b23 Compare September 23, 2026 23:20
devin-ai-integration[bot]

This comment was marked as resolved.

threepointone and others added 7 commits September 24, 2026 00:55
…ptions and the visible limit

Co-authored-by: Cursor <cursoragent@cursor.com>
…se text for a blank recovered reply

Co-authored-by: Cursor <cursoragent@cursor.com>
… recovery

Co-authored-by: Cursor <cursoragent@cursor.com>
…ident settles

Co-authored-by: Cursor <cursoragent@cursor.com>
…ed key

Co-authored-by: Cursor <cursoragent@cursor.com>
@threepointone
threepointone force-pushed the fix/2106-messenger-recovered-delivery branch from 4dc50f8 to e84e6b0 Compare September 23, 2026 23:58
@threepointone
threepointone merged commit 00a07bd into main Sep 24, 2026
8 of 14 checks passed
@threepointone
threepointone deleted the fix/2106-messenger-recovered-delivery branch September 24, 2026 00:13
@github-actions github-actions Bot mentioned this pull request Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Think: chat recovery completes an interrupted messenger turn, but the recovered answer is never delivered to the messenger thread

1 participant