cloudflare / cloudflare/agents

sessions: optional R2 tier for the attachment store

Open
#2,204 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
TypeScript
Stars
5.6k
Forks
711
Avg merge
1d 20h
Merged PRs (30d)
53

Description

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

Contributor guide

Open the contributing guide

Research direction

Start with the existing AttachmentStore seam described in #2196, tracing put, get, and #collect and then resolveAttachments to understand the current SQLite-only lifecycle. Before implementation, resolve the threshold, read mode, durability ordering, and host configuration questions; done would mean an agreed R2-backed tier that preserves reference-based cleanup without changing callers.

Written by the indexing model from the issue text.

Assessment

Tech stack
sqlite, typescript
Domain
backend, cloud, databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.