You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#2196 gives Sessions a content-addressed attachment store: media declared with a non-text media type is kept out of the message row, stored under its SHA-256 in cf_agents_session_attachment_chunks, and inlined again on read. That store is SQLite-only, so the bytes live in the same Durable Object as the conversation.
The problem
A Durable Object holds 10 GB and degrades noticeably past roughly 5. Attachments are the only thing in a session that can approach that: measured, roughly 39,000 200 KB images fills an object. Everything else — prose, tool output, compaction overlays — is orders of magnitude smaller.
So the object's ceiling is effectively an attachment ceiling, and a conversation that accumulates media has no way to shed bytes without deleting history.
Shape
The store is already the right seam for this. AttachmentStore addresses payloads by content hash and derives their lifetime from reference rows; where the bytes physically sit is behind put / get / #collect and nothing above it knows.
An R2 tier would:
write payloads above some size to R2 under a key derived from the hash, keeping metadata and reference rows in SQLite
read them back through the same get, so resolveAttachments and every caller stay unchanged
delete the object when the last reference goes, the same way chunk rows are collected today
Content addressing makes this easier than it would otherwise be: the key is a pure function of the bytes, so a retried write is idempotent and there is no rename or move to coordinate.
Open questions
Where the threshold sits, and whether there is one at all. Sending everything to R2 is simpler and makes the SQLite footprint of media effectively zero; a threshold keeps small images fast to read but adds a size-dependent branch of the kind feat(agents): move sessions into a Lifecycle capability #2196 deliberately removed from extraction.
Read latency. A SQLite chunk read is local; an R2 read is a network round trip on the hydration path. This likely wants pointer-mode reads to stay the default for context assembly so payloads are fetched only when something actually needs the bytes.
Durability ordering. Today bytes and the message row commit in one synchronous transaction, so a stored pointer always has bytes behind it. An R2 write cannot join that transaction, which reintroduces the ordering problem the current design avoids — write bytes first and risk orphans on failure, or write the row first and risk a pointer with nothing behind it.
An R2 tier existed earlier in #2196 and was removed along with the reactive offload layer. This is not a revert of that: the old one existed to make an over-budget row fit, and it competed with row chunking. This one would be a storage tier under a store that already exists and already works, which is a much smaller thing.
#2196 gives Sessions a content-addressed attachment store: media declared with a non-text media type is kept out of the message row, stored under its SHA-256 in
cf_agents_session_attachment_chunks, and inlined again on read. That store is SQLite-only, so the bytes live in the same Durable Object as the conversation.The problem
A Durable Object holds 10 GB and degrades noticeably past roughly 5. Attachments are the only thing in a session that can approach that: measured, roughly 39,000 200 KB images fills an object. Everything else — prose, tool output, compaction overlays — is orders of magnitude smaller.
So the object's ceiling is effectively an attachment ceiling, and a conversation that accumulates media has no way to shed bytes without deleting history.
Shape
The store is already the right seam for this.
AttachmentStoreaddresses payloads by content hash and derives their lifetime from reference rows; where the bytes physically sit is behindput/get/#collectand nothing above it knows.An R2 tier would:
get, soresolveAttachmentsand every caller stay unchangedContent addressing makes this easier than it would otherwise be: the key is a pure function of the bytes, so a retried write is idempotent and there is no rename or move to coordinate.
Open questions
Prior art
An R2 tier existed earlier in #2196 and was removed along with the reactive offload layer. This is not a revert of that: the old one existed to make an over-budget row fit, and it competed with row chunking. This one would be a storage tier under a store that already exists and already works, which is a much smaller thing.