Skip to content

feat(streams): R2 chunk log — segment write-ahead log behind new Streams({ r2 }) - #2215

Closed
mattzcarey wants to merge 2 commits into
mainfrom
feat/streams-r2-wal
Closed

feat(streams): R2 chunk log — segment write-ahead log behind new Streams({ r2 })#2215
mattzcarey wants to merge 2 commits into
mainfrom
feat/streams-r2-wal

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

What

agents/streams gets a second chunk log. Hand Streams an R2 binding and chunks leave DO SQLite:

readonly streams = new Streams({
  r2: this.env.BUCKET,
  r2Prefix: `streams/${this.ctx.id}/`,              // default "streams/"
  r2Checkpoint: { everyChunks: 25, everyMs: 1000 }  // the defaults
});

Nothing else changes: open, append, read, status, list, delete behave as before and append() stays synchronous. Stream rows (state, tag index, metadata, cursor) stay in SQLite, where a point read or tag lookup costs nothing. Chunks go to the bucket.

Why

Cost. SQLite bills one row per stored chunk and again to delete it. R2 bills one Class A op per checkpoint, deletes are free, and storage is about 13× cheaper with no 10 GB ceiling. One R2 put costs the same as 4.5 SQLite row writes, so the R2 log is cheaper whenever a checkpoint covers more than ~4.5 stored rows: unpacked producers, or a loss window of a few seconds. Against chat's 10-to-1 packed SQLite at a 1 s cadence it is a wash on ops and wins only on storage. The docs say this in the same words.

Per 400-chunk chat turn: $0.0008 SQLite unpacked, $0.00008 packed, $0.00009 R2 at the default cadence, $0.00002 at 5 s.

How

R2 has no append, rejects bodies of unknown length, and stores nothing from a put that has not completed (all measured on real R2, see below). So the log is a write-ahead log of segment objects:

  • Appends go into an in-memory line log that live readers tail, exactly like the SQLite log's in-isolate wakeups. Memory holds only the unflushed tail plus a 256 KB hot window of landed lines; once a segment's put resolves its lines are evicted and a reader further behind reads that segment from R2. A stream's length never grows isolate memory.
  • Every everyChunks appends or everyMs after the first unflushed one, the new lines are put as one immutable segment <prefix><id>/seg/<epoch>/<from>-<to>. Each landed segment is the durability: when the isolate dies, everything up to the last landed segment is in R2. Puts are chained so they land in order, retried with backoff, and a range that still fails folds into the next checkpoint so nothing is skipped.
  • The row's cursor is stamped from landed segments, throttled to one row write per 5 s, so status() never reports more than R2 holds and the stamp costs a fraction of the puts. open() after a death re-stamps it exactly.
  • open() on a stream whose isolate died lists the segments once, keeps the contiguous chain, deletes keys it does not cover, and continues in a new epoch from the chain's end without loading it into memory. A resumed producer starts at the durable cursor, so the Tasks resume contract holds and a discarded generation can never be spliced back in.
  • close()/error() settle the row synchronously, then in the background stream the segments back through one exact-size put at <id>/body and drop them (keys are tracked, no list; deletes batched at 1000). Segments stay readable until the body lands. await streams.flush(id) waits for it.
  • Replay of a settled stream caches bodies up to 1 MiB whole; larger bodies get a line-offset index and ranged gets per page. The read cache is an 8 MiB LRU.

SQLite accounting on the R2 backend: 100 appends read and write 0 rows; the cursor stamp is 1 row write per 5 s of streaming; status() reads 1, tag lookup 2, settle reads 2 and writes 2.

Chat's ResumableStream uses the synchronous SQLite aperture and is unaffected; it throws if given an R2-backed Streams.

Measured on real R2 (2026-09-03)

Experiment Result
put(key, unknownLengthStream) rejected: "Provided readable stream must have a known length"
FixedLengthStream put, slow writes streams as written; a 150 s put from a DO completed
object visible mid-put? no; not in list/head until complete
producer aborts or closes short put rejects, nothing stored
multipart with tiny parts complete rejects (10011, below 5 MiB minimum)
small put latency p50 145 ms, p95 195 ms
tag lookup, 1000 streams R2 prefix scan 542 ms, key-encoded index 41 ms, SQLite 0 ms

Those are why there is no streaming file put (it never contributes durability) and why the rows stay in SQLite.

Tests

  • src/tests/streams/r2-backend.test.ts (miniflare R2 binding STREAMS_R2): cursor + live tail + body at close; no chunk rows in SQLite; error() path; resume from the segment chain after a simulated restart; replay before recovery never pins a truncated log; delete; SQLite accounting; aperture refusal.
  • Existing streams, write-accounting, bench, migration and chat suites unchanged and green (46 tests).
  • r2-backend.test.ts also covers memory: 2000 × 500 B chunks keep under half the lines in memory while a reader far behind is served from segments, and the 1 MB settled body replays from any cursor through the offset index.
  • Real R2 with the package build: 200 chunks tailed over sseResponse while streaming, body landed, segments cleaned; ctx.abort() at chunk 120 left five segments and a durable cursor of 100, resume continued from 100, final body 200 lines. A 4000 × 500 B stream (2.2 MB) settled through the streamed compaction with no segments left, and the same stream killed at chunk 925 resumed from 925 and settled to 4000 lines.

Not in this PR

  • A SIGKILL e2e for the R2 backend (the workers-pool restart simulation covers the same path).
  • Per-stream cadence overrides; r2Checkpoint is capability-wide.

…ams({ r2 })

Stream rows (state, tag index, metadata, cursor) stay in DO SQLite; chunks
go to R2 as immutable segment objects checkpointed every 25 chunks or 1 s
(r2Checkpoint), so a Durable Object that dies mid-stream leaves everything
up to its last checkpoint in R2 and a resumed producer continues from the
durable cursor in a new epoch. Settlement compacts the segments into one
exact-size object; Streams.flush() awaits it. The synchronous storage
aperture used by chat stays SQLite-only.
@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f2ac0ff

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

This PR includes changesets to release 2 packages
Name Type
agents 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

@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 5 potential issues.

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

Devin Review

Comment on lines +334 to +336
const rows = this.#r2
? await this.#r2.readPage(streamId, next, batchSize)
: this.#sql<StreamChunkRow>`

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.

🔴 Backend changes erase existing histories

When R2 is enabled after SQLite streams exist, readPage ignores their stored chunks. Settled histories become empty, and live writers resume at zero.

Prompt for agents
Add an explicit per-stream backend marker and migration strategy in packages/agents/src/streams/streams.ts. Existing rows may own chunks in cf_agents_stream_chunks when an installation first enables r2, while rows created under R2 may outlive a later configuration change. Reads, resume, settlement, and deletion must select the backend that owns each stream. Either migrate existing chunk logs safely before switching the marker or preserve backend-specific reads until migration completes.
Devin Review

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

Comment on lines 635 to +638
if (settled > 0) {
// The row is terminal now; the R2 file and WAL cleanup finish in the
// background (`flush()` awaits them) while memory keeps serving reads.
void this.#r2?.settle(streamId);

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.

🔴 Closing can discard final chunks

settle starts R2 persistence after marking the stream terminal. An isolate death before the put completes permanently drops the final chunks.

Prompt for agents
Redesign R2 settlement so a stream cannot become irreversibly terminal before its complete chunk log is durable. The current synchronous Streams writer API updates cf_agents_streams first, then starts R2ChunkLog.settle in an unawaited promise. A restart can therefore see a terminal row with neither the final segment nor body available and has no recovery path. Introduce a durable settling phase or equivalent recovery metadata, ensure background work is attached to the invocation lifetime where possible, and make restart finish or resume settlement before exposing a terminal result.
Devin Review

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

Comment thread packages/agents/src/streams/r2-log.ts
Comment thread packages/agents/src/streams/r2-log.ts Outdated
Comment on lines +334 to +336
const rows = this.#r2
? await this.#r2.readPage(streamId, next, batchSize)
: this.#sql<StreamChunkRow>`

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.

🟡 Live readers can miss wakeups

The awaited readPage breaks atomic poll-and-wait registration. An append during that gap leaves a live reader asleep despite available data.

Prompt for agents
Restore a lost-wakeup-safe tailing protocol for asynchronous R2 reads. The SQLite implementation relied on polling, checking state, and registering the waiter in one synchronous block. R2ChunkLog.readPage introduces a suspension before waiter registration. Register a generation-aware waiter before the asynchronous poll, or recheck a monotonic in-memory generation after registration, while preserving abort cleanup and settlement handling.
Devin Review

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

@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
6 96 0 165 0 0

Compared ec93caf6 with f2ac0ff6. Open workflow run.

Changed imports (102)
Status Import Base gzip Head gzip Delta
🔴 agents/streams#Streams 3.4 KiB 6.2 KiB +2.9 KiB (+85.12%)
🔴 agents/chat#createChatStreams 5.5 KiB 8.3 KiB +2.8 KiB (+51.04%)
🔴 agents/streams#DEFAULT_MAX_CHUNK_BYTES 83 B 112 B +29 B (+34.94%)
🔴 agents/streams#StreamClosedError 161 B 193 B +32 B (+19.88%)
🔴 agents/streams#StreamSerializationError 158 B 189 B +31 B (+19.62%)
🔴 agents/streams#StreamNotFoundError 201 B 235 B +34 B (+16.92%)
🟡 agents/streams#sseResponse 843 B 869 B +26 B (+3.08%)
🟡 agents/chat#TIMED_OUT 2.3 KiB 2.3 KiB +26 B (+1.09%)
🟡 agents/chat#cleanupStreamBuffers 2.3 KiB 2.4 KiB +26 B (+1.09%)
🟡 agents/chat#createAgentToolEventState 2.3 KiB 2.4 KiB +26 B (+1.09%)
🟡 agents/chat#parseProtocolMessage 2.5 KiB 2.6 KiB +28 B (+1.08%)
🟡 agents/chat#STREAM_RESUME_NONE_REASONS 2.3 KiB 2.4 KiB +26 B (+1.08%)
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE 2.4 KiB 2.4 KiB +26 B (+1.08%)
🟡 agents/chat#toolResultUpdate 2.4 KiB 2.4 KiB +26 B (+1.06%)
🟡 agents/chat#STREAM_CLEANUP_DELAY_SECONDS 2.3 KiB 2.3 KiB +25 B (+1.06%)
🟡 agents/chat#CHAT_RECOVERY_TASK_NAME 2.3 KiB 2.3 KiB +25 B (+1.05%)
🟡 agents/chat#toolPartHasSettledResult 2.3 KiB 2.4 KiB +25 B (+1.05%)
🟡 agents/chat#clientResolvableToolNames 2.3 KiB 2.4 KiB +25 B (+1.04%)
🟡 agents/chat#drainInteractionApplies 2.3 KiB 2.4 KiB +25 B (+1.04%)
🟡 agents/chat#shouldCreditStreamProgress 2.4 KiB 2.4 KiB +25 B (+1.03%)
🟡 agents/chat#isReplayChunk 2.4 KiB 2.4 KiB +25 B (+1.02%)
🟡 agents/chat#pausedExecutionUpdate 2.4 KiB 2.4 KiB +25 B (+1.02%)
🟡 agents/chat#aiSdkRecoveryCodec 2.3 KiB 2.3 KiB +24 B (+1.02%)
🟡 agents/chat#CHAT_RECOVERY_ALARM_DEBOUNCE_MS 2.3 KiB 2.3 KiB +24 B (+1.02%)
🟡 agents/chat#CHAT_RECOVERY_INCIDENT_TTL_MS 2.3 KiB 2.3 KiB +24 B (+1.02%)
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS 2.3 KiB 2.3 KiB +24 B (+1.02%)
🟡 agents/chat#normalizeToolInput 2.3 KiB 2.3 KiB +24 B (+1.02%)
🟡 agents/chat#applyChunkToParts 2.3 KiB 2.3 KiB +24 B (+1.02%)
🟡 agents/chat#AutoContinuationController 2.3 KiB 2.3 KiB +24 B (+1.02%)
🟡 agents/chat#CHAT_MESSAGE_TYPES 2.3 KiB 2.3 KiB +24 B (+1.02%)
🟡 agents/chat#listActiveChatRecoveryIncidents 2.4 KiB 2.4 KiB +25 B (+1.01%)
🟡 agents/chat#CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#KV_DELETE_MAX_KEYS 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_MAX_WORK 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#MAX_BOUND_PARAMS 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#ROW_MAX_BYTES 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#CHAT_RECOVERING_KEY 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#CHAT_RECOVERY_PROGRESS_KEY 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#wrapChatFiberSnapshot 2.3 KiB 2.3 KiB +24 B (+1.01%)
🟡 agents/chat#readChatRecoveryProgress 2.3 KiB 2.4 KiB +24 B (+1%)
🟡 agents/chat#ChatStreamStalledError 2.4 KiB 2.4 KiB +24 B (+1%)
🟡 agents/chat#AgentToolStreamProgressThrottle 2.4 KiB 2.4 KiB +24 B (+1%)
🟡 agents/chat#StreamProgressCreditThrottle 2.4 KiB 2.4 KiB +24 B (+1%)
🟡 agents/chat#bumpChatRecoveryProgress 2.4 KiB 2.4 KiB +24 B (+0.99%)
🟡 agents/chat#applyToolUpdate 2.4 KiB 2.4 KiB +24 B (+0.98%)
🟡 agents/chat#buildChatRecoveringFrame 2.4 KiB 2.4 KiB +24 B (+0.98%)
🟡 agents/chat#awaitWithDeadline 2.4 KiB 2.4 KiB +24 B (+0.98%)
🟡 agents/chat#CHAT_RECOVERING_FLAG_TTL_MS 2.3 KiB 2.3 KiB +23 B (+0.97%)
🟡 agents/chat#toolApprovalUpdate 2.4 KiB 2.4 KiB +24 B (+0.97%)
🟡 agents/chat#CHAT_LAST_TERMINAL_KEY 2.3 KiB 2.3 KiB +23 B (+0.97%)
🟡 agents/chat#classifyAgentToolChildRecovery 2.4 KiB 2.5 KiB +24 B (+0.97%)
🟡 agents/chat#CHAT_RECOVERY_INCIDENT_KEY_PREFIX 2.3 KiB 2.4 KiB +23 B (+0.96%)
🟡 agents/chat#hasIncompleteToolBatch 2.4 KiB 2.5 KiB +24 B (+0.96%)
🟡 agents/chat#sanitizeMessage 2.5 KiB 2.6 KiB +25 B (+0.96%)
🟡 agents/chat#pendingChatTerminal 2.3 KiB 2.4 KiB +23 B (+0.96%)
🟡 agents/chat#clearChatTerminal 2.3 KiB 2.4 KiB +23 B (+0.96%)
🟡 agents/chat#setChatRecovering 2.5 KiB 2.5 KiB +24 B (+0.96%)
🟡 agents/chat#byteLength 2.5 KiB 2.5 KiB +24 B (+0.96%)
🟡 agents/chat#createChatFiberSnapshot 2.5 KiB 2.5 KiB +24 B (+0.95%)
🟡 agents/chat#interceptAgentToolBroadcast 2.5 KiB 2.5 KiB +24 B (+0.95%)
🟡 agents/chat#sendIfOpen 2.4 KiB 2.4 KiB +23 B (+0.94%)
🟡 agents/chat#resolveToolMergeId 2.4 KiB 2.4 KiB +23 B (+0.94%)
🟡 agents/chat#partAwaitsClientInteraction 2.4 KiB 2.4 KiB +23 B (+0.93%)
🟡 agents/chat#crossMessageToolResultUpdate 2.4 KiB 2.4 KiB +23 B (+0.93%)
🟡 agents/chat#buildInClauseStrings 2.4 KiB 2.4 KiB +23 B (+0.93%)
🟡 agents/chat#unwrapChatFiberSnapshot 2.4 KiB 2.5 KiB +23 B (+0.92%)
🟡 agents/chat#MessageType 2.4 KiB 2.5 KiB +23 B (+0.92%)
🟡 agents/chat#AbortRegistry 2.5 KiB 2.6 KiB +24 B (+0.92%)
🟡 agents/chat#chatRecoveryTaskRunOptions 2.4 KiB 2.5 KiB +23 B (+0.92%)
🟡 agents/chat#createChatTurnTaskDefinition 2.7 KiB 2.7 KiB +25 B (+0.92%)
🟡 agents/chat#runChatRecoveryExhaustion 2.6 KiB 2.6 KiB +24 B (+0.92%)
🟡 agents/chat#recordChatTerminal 2.4 KiB 2.4 KiB +22 B (+0.91%)
🟡 agents/chat#iterateWithStallWatchdog 2.6 KiB 2.6 KiB +24 B (+0.9%)
🟡 agents/chat#TextSegmentJoiner 2.7 KiB 2.7 KiB +25 B (+0.9%)
🟡 agents/chat#createChatRecoveryTaskDefinition 2.6 KiB 2.6 KiB +24 B (+0.9%)
🟡 agents/chat#repairInterruptedToolParts 2.6 KiB 2.6 KiB +24 B (+0.89%)
🟡 agents/chat#reconcileOrphanPartial 2.4 KiB 2.4 KiB +22 B (+0.89%)
🟡 agents/chat#PreStreamTurns 2.6 KiB 2.7 KiB +24 B (+0.89%)
🟡 agents/chat#ContinuationState 2.7 KiB 2.7 KiB +24 B (+0.88%)
🟡 agents/chat#AgentToolProgressEmitter 2.7 KiB 2.7 KiB +24 B (+0.88%)
🟡 agents/chat#sweepStaleChatRecoveryIncidents 2.4 KiB 2.5 KiB +22 B (+0.88%)
🟡 agents/chat#resolveChatRecoveryConfig 2.6 KiB 2.6 KiB +23 B (+0.88%)
🟡 agents/chat#TurnQueue 2.6 KiB 2.6 KiB +23 B (+0.86%)
🟡 agents/chat#isPlatformFailure 2.6 KiB 2.7 KiB +23 B (+0.85%)
🟡 agents/chat#SubmitConcurrencyController 2.9 KiB 2.9 KiB +24 B (+0.81%)
🟡 agents/chat#reconcileMessages 2.8 KiB 2.8 KiB +23 B (+0.81%)
🟡 agents/chat#applyAgentToolEvent 3.2 KiB 3.2 KiB +26 B (+0.8%)
🟡 agents/chat#ResumeHandshake 3.0 KiB 3.0 KiB +24 B (+0.79%)
🟡 agents/chat#truncateOlderMessages 3.2 KiB 3.2 KiB +25 B (+0.77%)
🟡 agents/chat#persistReconstructedOrphan 3.1 KiB 3.1 KiB +24 B (+0.77%)
🟡 agents/chat#StreamAccumulator 2.9 KiB 3.0 KiB +23 B (+0.76%)
🟡 agents/chat#broadcastTransition 3.1 KiB 3.2 KiB +24 B (+0.74%)
🟡 agents/chat#dispatchChatRecoveryToHandoff 3.0 KiB 3.1 KiB +23 B (+0.74%)
🟡 agents/chat#enforceRowSizeLimit 3.5 KiB 3.5 KiB +23 B (+0.64%)
🟡 agents/chat#ResumableStream 4.6 KiB 4.6 KiB +27 B (+0.58%)
🟡 agents/chat#ChatRecoveryEngine 4.4 KiB 4.4 KiB +19 B (+0.42%)
🟡 agents/chat#createToolsFromClientSchemas 114.3 KiB 114.3 KiB +22 B (+0.02%)
All 267 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.6 KiB 9.0 KiB
🟡 agents/chat#AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS 2.3 KiB 8.3 KiB
🟡 agents/chat#AgentToolProgressEmitter 2.7 KiB 9.6 KiB
🟡 agents/chat#AgentToolStreamProgressThrottle 2.4 KiB 8.4 KiB
🟡 agents/chat#aiSdkRecoveryCodec 2.3 KiB 8.3 KiB
🟡 agents/chat#applyAgentToolEvent 3.2 KiB 11.0 KiB
🟡 agents/chat#applyChunkToParts 2.3 KiB 8.3 KiB
🟡 agents/chat#applyToolUpdate 2.4 KiB 8.5 KiB
🟡 agents/chat#AutoContinuationController 2.3 KiB 8.3 KiB
🟡 agents/chat#awaitWithDeadline 2.4 KiB 8.5 KiB
🟡 agents/chat#broadcastTransition 3.2 KiB 11.5 KiB
🟡 agents/chat#buildChatRecoveringFrame 2.4 KiB 8.4 KiB
🟡 agents/chat#buildInClauseStrings 2.4 KiB 8.5 KiB
🟡 agents/chat#bumpChatRecoveryProgress 2.4 KiB 8.4 KiB
🟡 agents/chat#byteLength 2.5 KiB 8.5 KiB
🟡 agents/chat#CHAT_LAST_TERMINAL_KEY 2.3 KiB 8.3 KiB
🟡 agents/chat#CHAT_MESSAGE_TYPES 2.3 KiB 8.3 KiB
🟡 agents/chat#CHAT_RECOVERING_FLAG_TTL_MS 2.3 KiB 8.3 KiB
🟡 agents/chat#CHAT_RECOVERING_KEY 2.3 KiB 8.3 KiB
🟡 agents/chat#CHAT_RECOVERY_ALARM_DEBOUNCE_MS 2.3 KiB 8.3 KiB
🟡 agents/chat#CHAT_RECOVERY_INCIDENT_KEY_PREFIX 2.4 KiB 8.3 KiB
🟡 agents/chat#CHAT_RECOVERY_INCIDENT_TTL_MS 2.3 KiB 8.3 KiB
🟡 agents/chat#CHAT_RECOVERY_PROGRESS_KEY 2.3 KiB 8.3 KiB
🟡 agents/chat#CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS 2.3 KiB 8.3 KiB
🟡 agents/chat#CHAT_RECOVERY_TASK_NAME 2.3 KiB 8.3 KiB
🟡 agents/chat#CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS 2.3 KiB 8.3 KiB
🟡 agents/chat#ChatRecoveryEngine 4.4 KiB 15.3 KiB
🟡 agents/chat#chatRecoveryTaskRunOptions 2.5 KiB 8.6 KiB
🟡 agents/chat#ChatStreamStalledError 2.4 KiB 8.4 KiB
🟡 agents/chat#classifyAgentToolChildRecovery 2.5 KiB 8.5 KiB
🟡 agents/chat#cleanupStreamBuffers 2.4 KiB 8.3 KiB
🟡 agents/chat#clearChatTerminal 2.4 KiB 8.3 KiB
🟡 agents/chat#clientResolvableToolNames 2.4 KiB 8.3 KiB
🟡 agents/chat#ContinuationState 2.7 KiB 9.9 KiB
🟡 agents/chat#createAgentToolEventState 2.4 KiB 8.3 KiB
🟡 agents/chat#createChatFiberSnapshot 2.5 KiB 8.7 KiB
🟡 agents/chat#createChatRecoveryTaskDefinition 2.6 KiB 9.1 KiB
🔴 agents/chat#createChatStreams 8.3 KiB 28.1 KiB
🟡 agents/chat#createChatTurnTaskDefinition 2.7 KiB 9.0 KiB
🟡 agents/chat#createToolsFromClientSchemas 114.3 KiB 545.4 KiB
🟡 agents/chat#crossMessageToolResultUpdate 2.4 KiB 8.7 KiB
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS 2.3 KiB 8.3 KiB
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES 2.3 KiB 8.3 KiB
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_MAX_WORK 2.3 KiB 8.3 KiB
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS 2.3 KiB 8.3 KiB
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS 2.3 KiB 8.3 KiB
🟡 agents/chat#DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE 2.4 KiB 8.3 KiB
🟡 agents/chat#dispatchChatRecoveryToHandoff 3.1 KiB 9.9 KiB
🟡 agents/chat#drainInteractionApplies 2.4 KiB 8.4 KiB
🟡 agents/chat#enforceRowSizeLimit 3.5 KiB 11.3 KiB
🟡 agents/chat#hasIncompleteToolBatch 2.5 KiB 8.7 KiB
🟡 agents/chat#interceptAgentToolBroadcast 2.5 KiB 8.7 KiB
🟡 agents/chat#isPlatformFailure 2.7 KiB 9.0 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.3 KiB
🟡 agents/chat#listActiveChatRecoveryIncidents 2.4 KiB 8.5 KiB
🟡 agents/chat#MAX_BOUND_PARAMS 2.3 KiB 8.3 KiB
🟡 agents/chat#MessageType 2.5 KiB 9.0 KiB
🟡 agents/chat#normalizeToolInput 2.3 KiB 8.3 KiB
🟡 agents/chat#parseProtocolMessage 2.6 KiB 9.1 KiB
🟡 agents/chat#partAwaitsClientInteraction 2.4 KiB 8.6 KiB
🟡 agents/chat#pausedExecutionUpdate 2.4 KiB 8.5 KiB
🟡 agents/chat#pendingChatTerminal 2.4 KiB 8.3 KiB
🟡 agents/chat#persistReconstructedOrphan 3.1 KiB 11.1 KiB
🟡 agents/chat#PreStreamTurns 2.7 KiB 9.3 KiB
🟡 agents/chat#readChatRecoveryProgress 2.4 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.4 KiB
🟡 agents/chat#repairInterruptedToolParts 2.6 KiB 9.2 KiB
🟡 agents/chat#resolveChatRecoveryConfig 2.6 KiB 8.9 KiB
🟡 agents/chat#resolveToolMergeId 2.4 KiB 8.6 KiB
🟡 agents/chat#ResumableStream 4.6 KiB 15.3 KiB
🟡 agents/chat#ResumeHandshake 3.0 KiB 10.5 KiB
🟡 agents/chat#ROW_MAX_BYTES 2.3 KiB 8.3 KiB
🟡 agents/chat#runChatRecoveryExhaustion 2.6 KiB 9.0 KiB
🟡 agents/chat#sanitizeMessage 2.6 KiB 8.9 KiB
🟡 agents/chat#sendIfOpen 2.4 KiB 8.4 KiB
🟡 agents/chat#setChatRecovering 2.5 KiB 8.6 KiB
🟡 agents/chat#shouldCreditStreamProgress 2.4 KiB 8.4 KiB
🟡 agents/chat#STREAM_CLEANUP_DELAY_SECONDS 2.3 KiB 8.3 KiB
🟡 agents/chat#STREAM_RESUME_NONE_REASONS 2.4 KiB 8.3 KiB
🟡 agents/chat#StreamAccumulator 3.0 KiB 10.8 KiB
🟡 agents/chat#StreamProgressCreditThrottle 2.4 KiB 8.4 KiB
🟡 agents/chat#SubmitConcurrencyController 2.9 KiB 10.3 KiB
🟡 agents/chat#sweepStaleChatRecoveryIncidents 2.5 KiB 8.5 KiB
🟡 agents/chat#TextSegmentJoiner 2.7 KiB 9.3 KiB
🟡 agents/chat#TIMED_OUT 2.3 KiB 8.3 KiB
🟡 agents/chat#toolApprovalUpdate 2.4 KiB 8.6 KiB
🟡 agents/chat#toolPartHasSettledResult 2.4 KiB 8.4 KiB
🟡 agents/chat#toolResultUpdate 2.4 KiB 8.5 KiB
🟡 agents/chat#truncateOlderMessages 3.2 KiB 10.5 KiB
🟡 agents/chat#TurnQueue 2.6 KiB 9.2 KiB
🟡 agents/chat#unwrapChatFiberSnapshot 2.5 KiB 8.6 KiB
🟡 agents/chat#wrapChatFiberSnapshot 2.3 KiB 8.3 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 1.9 KiB 5.4 KiB
agents/sessions#Sessions 8.8 KiB 31.2 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 112 B 148 B
🟡 agents/streams#sseResponse 869 B 1.6 KiB
🔴 agents/streams#StreamClosedError 193 B 264 B
🔴 agents/streams#StreamNotFoundError 235 B 328 B
🔴 agents/streams#Streams 6.2 KiB 19.9 KiB
🔴 agents/streams#StreamSerializationError 189 B 253 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].

…d stamp ops

- Evict landed lines from memory beyond a 256 KB hot window; readers behind
  it read segments from R2. resume() lists once and points at the chain
  without loading it. Settle streams segments back through one exact-size
  put, so a stream's length never grows isolate memory.
- Segment puts retry with backoff; a failed range folds into the next
  checkpoint and the chain picker prefers the wider key, so nothing is
  skipped. Cursor stamps are throttled to one row write per 5 s and
  re-stamped exactly on resume.
- No list at settle (landed keys are tracked). Large settled bodies replay
  through a line-offset index with ranged gets; small ones are cached
  whole in an 8 MiB LRU.

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

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

Devin Review

Comment on lines +245 to +257
async resume(streamId: string): Promise<number> {
const existing = this.#sinks.get(streamId);
if (existing) return existing.cursor;
this.#uncache(streamId);
const segments = await this.#listSegments(streamId);
const chain = contiguousChain(segments);
const covered = new Set(chain.map((s) => s.key));
await this.#deleteKeys(
segments.filter((s) => !covered.has(s.key)).map((s) => s.key)
);
const epoch = segments.reduce((max, s) => Math.max(max, s.epoch), -1) + 1;
const sink = new Sink(epoch, chain);
this.#sinks.set(streamId, sink);

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.

🔴 Concurrent recovery discards new chunks

Concurrent open() calls can both enter resume after restart. The later recovery replaces the live sink and discards chunks appended after the first.

Prompt for agents
R2ChunkLog.resume checks #sinks only before awaiting R2 list/delete operations. Concurrent open() calls can therefore build separate sinks and the later completion overwrites the first. Serialize recovery per stream or share an in-flight resume promise, then re-check the live sink before installing recovered state. Ensure deletion of uncovered segments also occurs only once and cannot race with newly appended checkpoints.
Devin Review

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

@mattzcarey

Copy link
Copy Markdown
Contributor Author

Superseded by #2216: the R2 segment log was measured against the SQLite alternatives and lost on cost (a Class A put is 4.5 SQLite row writes; the row-write cost was the cleanup, not the append). #2216 keeps stream blocks in the same DO storage as the session message and makes the handoff one transaction. The R2 measurements and the spike stay on this branch for reference.

@mattzcarey mattzcarey closed this Sep 4, 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