Pasted content is replayed in full on every turn, outside every history guard
Problem
data-pastedContent parts are converted to model input verbatim and replayed on every later turn of a thread, exactly like a file attachment. Unlike a file attachment, nothing bounds them.
The prepare pipeline in lib/streaming/create-chat-stream-response.ts is compactHistoricalMessages -> capHistoricalAttachments -> dedupeAttachments. Every one of them branches on isFilePart (or on cited-source context). A pasted blob is neither, so:
- the count cap (
HISTORY_ATTACHMENT_REPLAY_LIMIT) and the weight budget (HISTORY_ATTACHMENT_TOKEN_BUDGET) incapHistoricalAttachmentsnever see it, dedupeAttachmentscannot collapse two identical pastes,compactHistoricalMessagesbounds cited source excerpts (MAX_SOURCE_CONTEXT_CHARS = 800) and nothing else.
The only remaining bound is truncateMessages at the context-window ceiling, which is a last-resort whole-message drop rather than a weight policy.
This is the argument #950 made for attachments, applied to the part type those guards do not recognize. It matters because the composer routes any sufficiently large paste into this part type, so pasting a file's contents instead of uploading it is an ordinary path, not an edge case, and it is the path with no guard on it.
Evidence
lib/streaming/helpers/convert-data-part.tsemits the full pasted text wrapped in nonce delimiters. Its own doc comment states the converter "runs over the entire history on every request", so the blob is re-sent every turn for the life of the thread.lib/streaming/helpers/cap-historical-attachments.tsandlib/streaming/helpers/dedupe-attachments.tsboth filter withisFilePart; adata-*part is invisible to them.- Reproduction: paste a large text blob in turn 1, continue the thread for several turns, and inspect the converted model messages. The blob is present in full in every request, and no
[Attachment omitted from history:or[Duplicate attachment omitted:placeholder is ever produced, at any thread length. - Related observability gap: the root
researchspan records the turn's input throughdescribeTurnInput, which summarizes structured parts only when the turn carries no typed text. A turn that carries both typed text and a paste records the text alone, so the carried paste weight cannot be separated from attachment weight in tracing today.
Proposed direction
These options are not equivalent and the choice needs a decision.
- Generalize
capHistoricalAttachmentsinto a carried-context cap that also weighsdata-pastedContent/data-quotedContext/data-noteContext, replacing dropped blocks with a placeholder naming them the way attachments already are. One policy, one knob, and it inherits the block quantization that keeps the prompt-cache prefix intact. - Give pasted content its own budget separate from attachments. Different content class, different natural threshold, at the cost of a second knob.
- Collapse successive near-identical pastes instead of dropping the oldest. This fits the iterate-on-one-file pattern better, but "near-identical" needs a definition and is not free to compute over a long history.
Whichever is chosen: the newest user message must stay uncapped, and the boundary must move in quantized blocks. A boundary that moves every turn rewrites the prompt prefix and voids the provider cache on exactly the long threads this is meant to make cheaper, which is what #970 fixed for the attachment budget.
Acceptance criteria
- A thread carrying a large pasted blob through many turns stops growing its replayed context without bound, while the most recent paste still reaches the model in full.
- A dropped block becomes a placeholder naming it, so the model can ask for a re-paste rather than silently losing the reference.
- Prefix stability between boundary crossings is asserted on the converted messages, in the same shape as the existing
capHistoricalAttachmentstests. - Tracing can attribute a turn's carried weight to attachments and to pasted text separately, so the effect of any of the three options can be read after deploy.
Source: miurla/morphic