Skip to content

feat(streams): rollover block log and atomic stream → session cutover - #2216

Merged
mattzcarey merged 7 commits into
mainfrom
feat/streams-block-log-atomic-cutover
Sep 8, 2026
Merged

feat(streams): rollover block log and atomic stream → session cutover#2216
mattzcarey merged 7 commits into
mainfrom
feat/streams-block-log-atomic-cutover

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

What

The stream data and the final session message live in the same Durable Object storage, and the handoff between them is one transaction:

temporary stream blocks
        ↓
session message
        ↓
delete temporary blocks

Three changes make that cheap and crash-safe:

  1. Rollover block log. cf_agents_stream_chunks (one row per append) becomes cf_agents_stream_blocks: an append grows the open block row with an UPDATE until it reaches 256 KB, then opens the next. Still one billed row per append, but a stream of thousands of chunks is a handful of rows, so deleting it is a handful of writes instead of thousands. Schema v2 folds existing chunk rows into blocks on startup.
  2. Atomic cutover. writer.close({ commit, discard }) (and error(reason, { … })) settles the stream, runs the caller's synchronous writes and deletes the stream's rows in one transactionSync. Session.__DO_NOT_USE_WILL_BREAK__sync().upsert() is the matching synchronous message write; it returns a notify() to dispatch the change feed after the commit.
  3. Hosts persist inside the cutover, and the sweep is gone. When the producer finishes, ResumableStream.finish() leaves the row live; the host's final persist then runs cutover(): message write, settlement and row deletion in one transaction (persistMessages { _cutover } in AIChatAgent, _persistAssistantMessageWithCutover in Think). A turn with nothing to persist is settled by finalizePending(). start() reclaims whatever a crash left behind: finished streams immediately, abandoned in-flight rows after an hour without a chunk. No cleanup alarm is armed any more; cleanupStreamBuffers and STREAM_CLEANUP_DELAY_SECONDS are removed from agents/chat, and the hosts' _cleanupStreamBuffers callback stays as a no-op so alarms persisted by earlier versions still resolve. Agent-tool child turns cut over without discarding (the parent tails their stored chunks after completion); those rows go at the next start.

Why

Measured on a real DO (src/tests/streams/sqlite-strategies-bench.test.ts, kept in the PR): a 400-chunk chat turn packed 10 per write.

Log shape Write phase Cutover / cleanup Lifetime rows
Immutable packed rows (before) 42 42 deleted by a sweep later 84
Reusable generational slots 42 2 44
Mutable rollover blocks 42 3 45

Rollover was chosen over slots because it has no shared state: every row belongs to one stream, is addressed by its primary key, and dies with the stream. Slots need a pool that never shrinks (5.6 MB left behind at 20k chunks), an index that doubles the per-flush write, and a free list rebuilt on every restart.

Crash matrix (ctx.abort() at each point; in-repo as src/e2e-tests/stream-cutover-crash.test.ts against wrangler dev, and reproduced on a deployed DO)

Crash point Restart finds
Before a block append commits exact prefix (10 chunks), the uncommitted append absent
Immediately after it commits exact prefix (11 chunks)
During rollover, before commit block 0 only, cursor 1
During rollover, after commit blocks 0 and 1, cursor 2, contiguous
After session persist, before discard, with I/O between (the non-atomic path hosts use today) message present once, stream completed with its exact prefix; the next start() reclaims it
After session persist, before discard, no I/O between stream live with its prefix, no message (the runtime commits at I/O boundaries, so both rolled back together)
Inside the atomic cutover's commit stream live with its prefix, no message
Right after the atomic cutover message present once, stream gone

Never neither, never both.

Tests

  • src/tests/streams/cutover.test.ts: cutover commits message + settle + discard together; a throwing commit rolls the settle back and the stream stays live; blocks roll over and replay across the boundary; delete() removes every block.
  • Write-accounting probe pins block open, block append and block delete at 1 row each; the storage-ops bench pins the hot path unchanged and the inline reclaim at 2 rows per turn.
  • agents streams/sessions/chat suites (107), agents chat unit project (545), ai-chat workers suite (637), Think stream/session/recovery suites (224), the ai-chat e2e stream-buffer test (a turn leaves no rows and arms no alarm) all green. Test workers that seeded the old chunk table now seed blocks; getStreamChunkRowCount reports appended segments from the block tail; the alarm-driven cleanup tests became reclaim tests.
  • Real-R2 spike and the R2 backend (feat(streams): R2 chunk log — segment write-ahead log behind new Streams({ r2 }) #2215) are superseded by this PR.

…ge cutover

Compares immutable packed rows, reusable generational slots and mutable
rollover blocks on billed rows, write time, page growth, replay, and the
cursor a restarted isolate recovers, with an atomic transactionSync
cutover to the message row. Numbers logged, invariants asserted.
- The Streams chunk log is mutable rollover blocks: an append grows the open
  block row (UPDATE) until 256 KB, then opens the next. Same one billed row
  per append; a stream of thousands of chunks is a handful of rows to
  delete. Schema v2 folds existing cf_agents_stream_chunks rows into blocks.
- writer.close({ commit, discard }) / error(reason, { ... }) settle, run the
  caller's synchronous writes and delete the stream's rows in one SQLite
  transaction. Session.__DO_NOT_USE_WILL_BREAK__sync().upsert() is the
  matching synchronous message write.
- ResumableStream.complete(id, { persist }) exposes the cutover;
  discardCompleted() drops a completed stream's rows once its message is
  persisted; start() reclaims completed streams a crash left behind.
- AIChatAgent and Think discard right after persisting (agent-tool child
  turns excepted: the parent tails their stored chunks), so the retention
  sweep no longer finds completed streams.
@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 42d73a0

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

This PR includes changesets to release 4 packages
Name Type
agents Minor
@cloudflare/ai-chat Minor
@cloudflare/think Minor
@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 4, 2026

Copy link
Copy Markdown
Contributor

🔴 agents import sizes

Measured 267 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
3 34 29 199 0 2

Compared ec93caf6 with 73eca75b. Open workflow run.

Changed imports (68)
Status Import Base gzip Head gzip Delta
🔴 agents/streams#Streams 3.4 KiB 4.1 KiB +797 B (+23.2%)
🔴 agents/chat#createChatStreams 5.5 KiB 6.3 KiB +781 B (+13.85%)
🔴 agents/streams#DEFAULT_MAX_CHUNK_BYTES 83 B 94 B +11 B (+13.25%)
🟡 agents/streams#StreamClosedError 161 B 175 B +14 B (+8.7%)
🟡 agents/streams#StreamSerializationError 158 B 171 B +13 B (+8.23%)
🟡 agents/streams#StreamNotFoundError 201 B 215 B +14 B (+6.97%)
🟡 agents/sessions#Session 1.9 KiB 2.0 KiB +126 B (+6.53%)
🟡 agents/streams#sseResponse 843 B 855 B +12 B (+1.42%)
🟡 agents/sessions#Sessions 8.8 KiB 9.0 KiB +122 B (+1.35%)
🟡 agents/chat#ResumableStream 4.6 KiB 4.6 KiB +55 B (+1.18%)
🟡 agents/chat#truncateOlderMessages 3.2 KiB 3.2 KiB +3 B (+0.09%)
🟡 agents/chat#toolPartHasSettledResult 2.3 KiB 2.3 KiB +2 B (+0.08%)
🟡 agents/chat#STREAM_RESUME_NONE_REASONS 2.3 KiB 2.3 KiB +2 B (+0.08%)
🟡 agents/chat#awaitWithDeadline 2.4 KiB 2.4 KiB +2 B (+0.08%)
🟡 agents/chat#listActiveChatRecoveryIncidents 2.4 KiB 2.4 KiB +2 B (+0.08%)
🟡 agents/chat#classifyAgentToolChildRecovery 2.4 KiB 2.4 KiB +2 B (+0.08%)
🟡 agents/chat#sanitizeMessage 2.5 KiB 2.5 KiB +2 B (+0.08%)
🟡 agents/chat#StreamAccumulator 2.9 KiB 2.9 KiB +2 B (+0.07%)
🟡 agents/chat#persistReconstructedOrphan 3.1 KiB 3.1 KiB +2 B (+0.06%)
🟡 agents/chat#broadcastTransition 3.1 KiB 3.2 KiB +2 B (+0.06%)
🟡 agents/chat#enforceRowSizeLimit 3.5 KiB 3.5 KiB +2 B (+0.06%)
🟡 agents/chat#CHAT_RECOVERY_TASK_NAME 2.3 KiB 2.3 KiB +1 B (+0.04%)
🟡 agents/chat#TIMED_OUT 2.3 KiB 2.3 KiB +1 B (+0.04%)
🟡 agents/chat#clientResolvableToolNames 2.3 KiB 2.3 KiB +1 B (+0.04%)
🟡 agents/chat#createAgentToolEventState 2.3 KiB 2.3 KiB +1 B (+0.04%)
🟡 agents/chat#applyToolUpdate 2.4 KiB 2.4 KiB +1 B (+0.04%)
🟡 agents/chat#pausedExecutionUpdate 2.4 KiB 2.4 KiB +1 B (+0.04%)
🟡 agents/chat#toolResultUpdate 2.4 KiB 2.4 KiB +1 B (+0.04%)
🟡 agents/chat#partAwaitsClientInteraction 2.4 KiB 2.4 KiB +1 B (+0.04%)
🟡 agents/chat#MessageType 2.4 KiB 2.4 KiB +1 B (+0.04%)
🟡 agents/chat#sweepStaleChatRecoveryIncidents 2.4 KiB 2.4 KiB +1 B (+0.04%)
🟡 agents/chat#setChatRecovering 2.5 KiB 2.5 KiB +1 B (+0.04%)
🟡 agents/chat#parseProtocolMessage 2.5 KiB 2.5 KiB +1 B (+0.04%)
🟡 agents/chat#AbortRegistry 2.5 KiB 2.5 KiB +1 B (+0.04%)
🟡 agents/chat#TurnQueue 2.6 KiB 2.6 KiB +1 B (+0.04%)
🟡 agents/chat#SubmitConcurrencyController 2.9 KiB 2.9 KiB +1 B (+0.03%)
🟡 agents/chat#createToolsFromClientSchemas 114.3 KiB 114.3 KiB +1 B (+0%)
🟢 agents/chat#CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS 2.3 KiB 2.3 KiB -2 B (-0.08%)
🟢 agents/chat#CHAT_LAST_TERMINAL_KEY 2.3 KiB 2.3 KiB -2 B (-0.08%)
🟢 agents/chat#CHAT_RECOVERY_INCIDENT_KEY_PREFIX 2.3 KiB 2.3 KiB -2 B (-0.08%)
🟢 agents/chat#repairInterruptedToolParts 2.6 KiB 2.6 KiB -2 B (-0.07%)
🟢 agents/chat#ContinuationState 2.7 KiB 2.7 KiB -2 B (-0.07%)
🟢 agents/chat#dispatchChatRecoveryToHandoff 3.0 KiB 3.0 KiB -2 B (-0.06%)
🟢 agents/chat#CHAT_RECOVERING_FLAG_TTL_MS 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#CHAT_RECOVERY_INCIDENT_TTL_MS 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#applyChunkToParts 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#AutoContinuationController 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#CHAT_MESSAGE_TYPES 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#KV_DELETE_MAX_KEYS 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#DEFAULT_CHAT_RECOVERY_MAX_WORK 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#CHAT_RECOVERING_KEY 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#CHAT_RECOVERY_PROGRESS_KEY 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#wrapChatFiberSnapshot 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#pendingChatTerminal 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#clearChatTerminal 2.3 KiB 2.3 KiB -1 B (-0.04%)
🟢 agents/chat#recordChatTerminal 2.4 KiB 2.4 KiB -1 B (-0.04%)
🟢 agents/chat#ChatStreamStalledError 2.4 KiB 2.4 KiB -1 B (-0.04%)
🟢 agents/chat#shouldCreditStreamProgress 2.4 KiB 2.4 KiB -1 B (-0.04%)
🟢 agents/chat#sendIfOpen 2.4 KiB 2.4 KiB -1 B (-0.04%)
🟢 agents/chat#resolveToolMergeId 2.4 KiB 2.4 KiB -1 B (-0.04%)
🟢 agents/chat#PreStreamTurns 2.6 KiB 2.6 KiB -1 B (-0.04%)
🟢 agents/chat#isPlatformFailure 2.6 KiB 2.6 KiB -1 B (-0.04%)
🟢 agents/chat#ResumeHandshake 3.0 KiB 3.0 KiB -1 B (-0.03%)
🟢 agents/chat#cleanupStreamBuffers 2.3 KiB
🟢 agents/chat#STREAM_CLEANUP_DELAY_SECONDS 2.3 KiB
All 265 current runtime imports
Status Import Gzip Raw minified
agents#__DO_NOT_USE_WILL_BREAK__agentContext 258.6 KiB 1130.0 KiB
agents#__DO_NOT_USE_WILL_BREAK__withInvocationScope 258.6 KiB 1130.0 KiB
agents#Agent 258.6 KiB 1130.0 KiB
agents#AGENT_TOOL_MILESTONE_PART 258.6 KiB 1130.0 KiB
agents#AGENT_TOOL_PROGRESS_PART 258.6 KiB 1130.0 KiB
agents#buildAgentPath 259.1 KiB 1132.3 KiB
agents#buildAgentUrl 259.3 KiB 1132.7 KiB
agents#callable 258.6 KiB 1130.1 KiB
agents#camelCaseToKebabCase 258.6 KiB 1130.0 KiB
agents#createHeaderBasedEmailResolver 258.8 KiB 1130.4 KiB
agents#DEFAULT_AGENT_STATIC_OPTIONS 258.6 KiB 1130.0 KiB
agents#DurableObjectOAuthClientProvider 258.6 KiB 1130.0 KiB
agents#getAgentByName 258.6 KiB 1130.0 KiB
agents#getCurrentAgent 258.6 KiB 1130.0 KiB
agents#getSubAgentByName 258.9 KiB 1130.7 KiB
agents#isDurableObjectCodeUpdateReset 258.6 KiB 1130.0 KiB
agents#isDurableObjectMemoryLimitReset 258.6 KiB 1130.0 KiB
agents#isDurableObjectStorageReset 258.6 KiB 1130.1 KiB
agents#isPlatformTransientError 258.6 KiB 1130.0 KiB
agents#MCP_SERVER_ID_MAX_LENGTH 258.6 KiB 1130.0 KiB
agents#MessageType 258.8 KiB 1130.3 KiB
agents#normalizeServerId 258.6 KiB 1130.0 KiB
agents#parseSubAgentPath 258.6 KiB 1130.0 KiB
agents#routeAgentEmail 258.9 KiB 1130.7 KiB
agents#routeAgentRequest 259.2 KiB 1131.9 KiB
agents#routeSubAgentRequest 258.8 KiB 1130.6 KiB
agents#SqlError 258.6 KiB 1130.0 KiB
agents#StreamingResponse 258.6 KiB 1130.0 KiB
agents#SUB_PREFIX 258.6 KiB 1130.0 KiB
agents#unstable_callable 258.7 KiB 1130.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 146.0 KiB 630.3 KiB
agents/browser/ai#createBrowserTools 146.0 KiB 630.3 KiB
agents/browser/ai#createQuickActionTools 122.5 KiB 554.3 KiB
agents/browser/tanstack-ai#createBrowserTools 161.7 KiB 699.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.4 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.4 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.4 KiB 15.3 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.3 KiB 21.7 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 4.6 KiB 15.6 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.4 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 261.0 KiB 1141.6 KiB
agents/chat-sdk#ChatSdkStateAgent 260.4 KiB 1139.1 KiB
agents/chat-sdk#createChatSdkState 261.0 KiB 1141.6 KiB
agents/chat-sdk#defaultKeyShard 258.8 KiB 1130.2 KiB
agents/chat-sdk#defaultThreadShard 258.7 KiB 1130.1 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.7 KiB
agents/chat/react#WebSocketChatTransport 5.7 KiB 17.1 KiB
agents/chat/transport#WebSocketChatTransport 2.8 KiB 9.2 KiB
agents/client#AgentClient 5.7 KiB 16.6 KiB
agents/client#AgentConnectionError 582 B 993 B
agents/client#agentFetch 4.2 KiB 12.3 KiB
agents/client#createStubProxy 638 B 1.0 KiB
agents/client#DEFAULT_CALL_TIMEOUT_MS 473 B 770 B
agents/client#isTerminalCloseEvent 509 B 822 B
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.3 KiB 25.8 KiB
agents/lifecycle#LifecycleCapability 484 B 975 B
agents/mcp#createLegacyMcpHandler 375.9 KiB 1572.2 KiB
agents/mcp#createMcpHandler 388.2 KiB 1617.4 KiB
agents/mcp#DurableObjectEventStore 342.5 KiB 1430.7 KiB
agents/mcp#ElicitRequestSchema 342.5 KiB 1430.7 KiB
agents/mcp#experimental_createMcpHandler 376.1 KiB 1572.5 KiB
agents/mcp#getMcpAuthContext 342.5 KiB 1430.8 KiB
agents/mcp#MCP_SERVER_ID_MAX_LENGTH 342.5 KiB 1430.7 KiB
agents/mcp#McpAgent 342.5 KiB 1430.7 KiB
agents/mcp#normalizeServerId 342.5 KiB 1430.7 KiB
agents/mcp#RPC_DO_PREFIX 342.5 KiB 1430.7 KiB
agents/mcp#RPCClientTransport 342.5 KiB 1430.7 KiB
agents/mcp#RPCServerTransport 342.5 KiB 1430.7 KiB
agents/mcp#SSEEdgeClientTransport 342.6 KiB 1431.0 KiB
agents/mcp#StreamableHTTPEdgeClientTransport 342.6 KiB 1431.0 KiB
agents/mcp#WorkerTransport 345.8 KiB 1447.6 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/react#_testUtils 3.8 KiB 9.5 KiB
agents/react#useAgent 10.8 KiB 31.1 KiB
agents/react#useAgentToolEvents 5.6 KiB 16.8 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#createCompactFunction 1.7 KiB 4.0 KiB
🟡 agents/sessions#Session 2.0 KiB 6.0 KiB
🟡 agents/sessions#Sessions 9.0 KiB 31.8 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/streams#DEFAULT_MAX_CHUNK_BYTES 94 B 96 B
🟡 agents/streams#sseResponse 855 B 1.6 KiB
🟡 agents/streams#StreamClosedError 175 B 212 B
🟡 agents/streams#StreamNotFoundError 215 B 276 B
🔴 agents/streams#Streams 4.1 KiB 13.5 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/websockets#CALLABLES_RPC_QUERY 12.4 KiB 43.3 KiB
agents/websockets#CALLABLES_RPC_VALUE 12.4 KiB 43.3 KiB
agents/websockets#callablesFromDecorated 12.7 KiB 44.3 KiB
agents/websockets#callablesRpcUrl 12.5 KiB 43.5 KiB
agents/websockets#isCallablesRpcUpgrade 12.4 KiB 43.4 KiB
agents/websockets#WebSockets 17.7 KiB 62.2 KiB
agents/workflows#AgentWorkflow 260.0 KiB 1134.8 KiB
agents/workflows#WorkflowRejectedError 258.7 KiB 1130.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].

devin-ai-integration[bot]

This comment was marked as resolved.

ctx.abort() at each point that matters — before an append commits, right
after, during rollover (before/after commit), after persist before discard,
and inside/after the atomic cutover — then inspect the fresh instance. Each
restart finds either the exact committed prefix or the finished message.
devin-ai-integration[bot]

This comment was marked as resolved.

- ResumableStream.finish() leaves a finished stream live until cutover();
  cutover() settles it, runs the message write and deletes its rows in one
  transaction; finalizePending() settles when nothing is persisted;
  reclaim() on every start() replaces the alarm-driven sweep.
- AIChatAgent persists the turn's messages inside the cutover
  (persistMessages { _cutover }); Think does the same through
  _persistAssistantMessageWithCutover. The session change feed and
  auto-compaction run once the transaction commits.
- cleanupStreamBuffers / STREAM_CLEANUP_DELAY_SECONDS removed; the host
  _cleanupStreamBuffers callbacks stay as no-ops for persisted alarms.

@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

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 1 new potential issue.

3 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

});
return streamResult;
} finally {
this._resumableStream.finalizePending();

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.

🔴 Failed persistence discards recovery evidence

When message persistence throws, finalizePending() settles the still-live stream in AIChatAgent and Think. Recovery then ignores its only durable response copy.

Prompt for agents
Preserve a pending stream when the cutover's message write fails. AIChatAgent in packages/ai-chat/src/index.ts and Think in packages/think/src/think.ts currently call ResumableStream.finalizePending() after catching or propagating a failed cutover. That changes the transaction's rolled-back live stream to completed without persisting its message, preventing orphan recovery. Only finalize when there was intentionally no message to persist. Leave the stream live when cutover persistence throws, and add failure-path tests for both hosts.
Devin Review

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

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

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

@cloudflare/ai-chat

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

@cloudflare/codemode

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

hono-agents

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

@cloudflare/shell

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

@cloudflare/think

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

@cloudflare/voice

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

@cloudflare/worker-bundler

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

commit: 42d73a0

…er commit

- Fold cf_agents_stream_chunks rows into blocks one stream at a time on
  first touch (paged reads, whole-block inserts) instead of the whole log
  at startup; drop the table once empty.
- A settle with commit/discard is a no-op on a stream already terminal or
  deleted: commit does not run, nothing is discarded. ResumableStream's
  cutover falls back to a plain persist in that case.
- Terminal, deleted events and reader wakeups fire after the cutover
  transaction returns, never for a rolled-back commit.
A subclass that overrides persistMessages and calls super with only the
messages dropped the internal _cutover option, so the message write and
the stream settlement fell back to two commits. The pending cutover now
lives on the instance keyed by the turn's message id: the first persist
that carries that message runs the cutover, a persist of other messages
takes the plain path, and the turn's end clears it. persistMessages'
public signature is unchanged.

@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.

Devin Review found 1 new potential issue.

6 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment on lines +925 to +926
this.#sqlWrite("DROP TABLE cf_agents_stream_chunks", []);
this.#legacyChunkTable = false;

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.

🔴 Rolled-back migration hides old chunks

When a v1 stream's first cutover rolls back, #legacyChunkTable stays false although SQLite restores the legacy table. Later reads and appends ignore every old chunk and restart the cursor at zero.

Prompt for agents
Make lazy legacy-table state consistent with transaction rollback in packages/agents/src/streams/streams.ts. foldLegacyChunks can run inside the outer cutover transaction. dropLegacyChunkTableIfEmpty currently mutates #legacyChunkTable before that outer transaction is known to commit. If the caller's commit callback throws, SQLite restores the legacy table but the instance flag remains false. Avoid retaining transaction-dependent schema state in memory, or refresh it after rollback. Add a test that seeds a v1 live stream, runs close({ commit: throwing, discard: true }), then verifies the old cursor and chunks remain visible and a new append continues at the old cursor.
Devin Review

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

@mattzcarey
mattzcarey merged commit dd09d44 into main Sep 8, 2026
24 of 25 checks passed
@mattzcarey
mattzcarey deleted the feat/streams-block-log-atomic-cutover branch September 8, 2026 16:37
@github-actions github-actions Bot mentioned this pull request Sep 8, 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.

1 participant