Skip to content

sessions: optional R2 tier for the attachment store #2204

Description

@mattzcarey

#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.
  • Configuration. The previous R2 tier was removed in feat(agents): move sessions into a Lifecycle capability #2196 partly because it was configuration nobody had switched on. This should not come back until there is a host that wants it.

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions