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

mattzcarey merged 7 commits into
mainfrom
feat/streams-block-log-atomic-cutover

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Sep 4, 2026 •

Copy link
Copy Markdown
Member

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
tombeckenham added a commit to tombeckenham/agents-tom that referenced this pull request Oct 7, 2026
…mically

AGUIChatAgent keeps its AG-UI transcript in the Sessions capability (session "agui") instead of cf_ai_chat_agent_messages, following upstream cloudflare#2196 and cloudflare#2216.

- Each row is one AG-UI message wrapped in a single Sessions part; inline media is re-keyed so Sessions lifts it into the attachment store.
- On start the legacy table (AI SDK rows or AG-UI rows) and upstream's UIMessage rows in the default session are lifted into the transcript and their source removed.
- Messages are no longer truncated or skipped for size; Sessions splits them.
- A finished turn's messages are written in the transaction that settles its stream and drops its rows (_finishStream + pending cutover).
- /get-messages serves the stored mirror through a _serializeTranscript seam.
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